├── .gitignore ├── CHNAGELOG.md ├── FlycoTabLayout_Lib ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── flyco │ │ └── tablayout │ │ ├── CommonTabLayout.java │ │ ├── SegmentTabLayout.java │ │ ├── SlidingTabLayout.java │ │ ├── listener │ │ ├── CustomTabEntity.java │ │ └── OnTabSelectListener.java │ │ ├── utils │ │ ├── FragmentChangeManager.java │ │ └── UnreadMsgUtils.java │ │ └── widget │ │ └── MsgView.java │ └── res │ ├── layout │ ├── layout_tab.xml │ ├── layout_tab_bottom.xml │ ├── layout_tab_left.xml │ ├── layout_tab_right.xml │ ├── layout_tab_segment.xml │ └── layout_tab_top.xml │ └── values │ └── attrs.xml ├── LICENSE ├── README.md ├── README_CN.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── flyco │ │ └── tablayoutsamples │ │ ├── adapter │ │ └── SimpleHomeAdapter.java │ │ ├── entity │ │ └── TabEntity.java │ │ ├── ui │ │ ├── CommonTabActivity.java │ │ ├── SegmentTabActivity.java │ │ ├── SimpleCardFragment.java │ │ ├── SimpleHomeActivity.java │ │ └── SlidingTabActivity.java │ │ └── utils │ │ └── ViewFindUtils.java │ └── res │ ├── drawable │ └── background_card.9.png │ ├── layout │ ├── activity_common_tab.xml │ ├── activity_segment_tab.xml │ ├── activity_sliding_tab.xml │ └── fr_simple_card.xml │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── tab_contact_select.png │ ├── tab_contact_unselect.png │ ├── tab_home_select.png │ ├── tab_home_unselect.png │ ├── tab_more_select.png │ ├── tab_more_unselect.png │ ├── tab_speech_select.png │ └── tab_speech_unselect.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── dependencies.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── preview_1.gif ├── preview_2.gif ├── preview_3.gif └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | build 4 | .idea 5 | *.iml 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /CHNAGELOG.md: -------------------------------------------------------------------------------- 1 | #Change Log 2 | Version 1.1.2 *(2015-10-14)* 3 | ---------------------------- 4 | * add method setViewPager(ViewPager vp, String[] titles) for the condition that you do not want set titles in page adapter 5 | 6 | Version 1.1.4 *(2015-10-16)* 7 | ---------------------------- 8 | * fix bug: indicator not show if you do not call viewpager's setCurrentItem method during initialization. 9 | 10 | Version 1.1.6 *(2015-10-18)* 11 | ---------------------------- 12 | * add method setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList fragments) 13 | for the condition that you even do not want to instantiate page adapter by yourself 14 | * add listener OnTabSelectedListener 15 | 16 | Version 1.1.8 *(2015-10-19)* 17 | ---------------------------- 18 | * add block indicator 19 | 20 | Version 1.2.0 *(2015-10-20)* 21 | ---------------------------- 22 | * add unread msg dot (TipView) 23 | 24 | Version 1.3.0 *(2015-10-22)* 25 | ---------------------------- 26 | * new added View: CommonTabLayout is a tablayout without dependence of ViewPager 27 | 28 | Version 1.3.2 *(2015-10-28)* 29 | ---------------------------- 30 | * extract common attributes 31 | 32 | Version 1.3.4 *(2015-11-5)* 33 | ---------------------------- 34 | * replace TipView with RoundTextView 35 | 36 | Version 1.3.6 *(2015-11-10)* 37 | ---------------------------- 38 | * new add attr tl_indicator_width_equal_title 39 | 40 | Version 1.4.0 *(2015-11-11)* 41 | ---------------------------- 42 | * fix bug: attr tl_indicator_width_equal_title sometime invalid 43 | 44 | Version 1.4.2 *(2015-12-9)* 45 | ---------------------------- 46 | * new added tablayout 47 | 48 | Version 1.4.4 *(2015-12-11)* 49 | ---------------------------- 50 | * fix bug: CommonTabLayout first setCurrentTab() cause indicator not show 51 | 52 | Version 1.4.6 *(2015-12-11)* 53 | ---------------------------- 54 | * fix bug: CommonTabLayout first setCurrentTab() cause indicator not show 55 | 56 | Version 1.5.0 *(2015-12-11)* 57 | ---------------------------- 58 | * change code style 59 | 60 | Version 2.0.0 *(2016.3.1)* 61 | ---------------------------- 62 | * remove the dependence of FlycoRoundView 63 | * new added method getIconView and getTitleView 64 | 65 | Version 2.0.2 *(2016.4.23)* 66 | ---------------------------- 67 | * remove the dependence of NineOldAnimation(only support 3.0+) 68 | 69 | Version 2.0.6 *(2016.5.21)* 70 | ---------------------------- 71 | * remove CustomTabProvider in SlidingTabLayout 72 | * new added method 'addNewTab(String title)' for SlidingTabLayout 73 | 74 | Version 2.0.8 *(2016.7.26)* 75 | --------------------------- 76 | * Fix #27,#31(new added method 'setCurrentTab(int currentTab, boolean smoothScroll)' for SlidingTabLayout and redefine attr 'tl_textBold') 77 | 78 | Version 2.1.0 *(2016.10.28)* 79 | ---------------------------- 80 | * remove Application label in manifest 81 | * add method to make viewpager snap on tab click 82 | 83 | Version 2.1.2 *(2017.1.23)* 84 | ---------------------------- 85 | * update compileSdkVersion to 25, buildToolsVersion to "25.0.2" 86 | 87 | Version 3.0.0 *(2021.09.30)* 88 | ---------------------------- 89 | * AndroidX & publish on Maven Central 90 | 91 | 92 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode rootProject.ext.versionCode 11 | versionName rootProject.ext.versionName 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility = JavaVersion.VERSION_1_8 23 | targetCompatibility = JavaVersion.VERSION_1_8 24 | } 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation support.appcompat 30 | implementation support.v4 31 | implementation support.design 32 | } 33 | 34 | buildscript { 35 | repositories { 36 | mavenCentral() 37 | } 38 | dependencies { 39 | classpath 'com.vanniktech:gradle-maven-publish-plugin:0.18.0' 40 | } 41 | } 42 | 43 | allprojects { 44 | plugins.withId("com.vanniktech.maven.publish") { 45 | mavenPublish { 46 | sonatypeHost = "S01" 47 | } 48 | } 49 | } 50 | 51 | apply plugin: "com.vanniktech.maven.publish" -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/lihui/work/AndroidStudio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/java/com/flyco/tablayout/CommonTabLayout.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout; 2 | 3 | import android.animation.TypeEvaluator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Path; 11 | import android.graphics.Rect; 12 | import android.graphics.drawable.GradientDrawable; 13 | import android.os.Bundle; 14 | import android.os.Parcelable; 15 | import android.util.AttributeSet; 16 | import android.util.SparseArray; 17 | import android.util.TypedValue; 18 | import android.view.Gravity; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | import android.view.animation.OvershootInterpolator; 22 | import android.widget.FrameLayout; 23 | import android.widget.ImageView; 24 | import android.widget.LinearLayout; 25 | import android.widget.TextView; 26 | 27 | import androidx.fragment.app.Fragment; 28 | import androidx.fragment.app.FragmentActivity; 29 | 30 | import com.flyco.tablayout.listener.CustomTabEntity; 31 | import com.flyco.tablayout.listener.OnTabSelectListener; 32 | import com.flyco.tablayout.utils.FragmentChangeManager; 33 | import com.flyco.tablayout.utils.UnreadMsgUtils; 34 | import com.flyco.tablayout.widget.MsgView; 35 | 36 | import java.util.ArrayList; 37 | 38 | /** 没有继承HorizontalScrollView不能滑动,对于ViewPager无依赖 */ 39 | public class CommonTabLayout extends FrameLayout implements ValueAnimator.AnimatorUpdateListener { 40 | private Context mContext; 41 | private ArrayList mTabEntitys = new ArrayList<>(); 42 | private LinearLayout mTabsContainer; 43 | private int mCurrentTab; 44 | private int mLastTab; 45 | private int mTabCount; 46 | /** 用于绘制显示器 */ 47 | private Rect mIndicatorRect = new Rect(); 48 | private GradientDrawable mIndicatorDrawable = new GradientDrawable(); 49 | 50 | private Paint mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 51 | private Paint mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 52 | private Paint mTrianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 53 | private Path mTrianglePath = new Path(); 54 | private static final int STYLE_NORMAL = 0; 55 | private static final int STYLE_TRIANGLE = 1; 56 | private static final int STYLE_BLOCK = 2; 57 | private int mIndicatorStyle = STYLE_NORMAL; 58 | 59 | private float mTabPadding; 60 | private boolean mTabSpaceEqual; 61 | private float mTabWidth; 62 | 63 | /** indicator */ 64 | private int mIndicatorColor; 65 | private float mIndicatorHeight; 66 | private float mIndicatorWidth; 67 | private float mIndicatorCornerRadius; 68 | private float mIndicatorMarginLeft; 69 | private float mIndicatorMarginTop; 70 | private float mIndicatorMarginRight; 71 | private float mIndicatorMarginBottom; 72 | private long mIndicatorAnimDuration; 73 | private boolean mIndicatorAnimEnable; 74 | private boolean mIndicatorBounceEnable; 75 | private int mIndicatorGravity; 76 | 77 | /** underline */ 78 | private int mUnderlineColor; 79 | private float mUnderlineHeight; 80 | private int mUnderlineGravity; 81 | 82 | /** divider */ 83 | private int mDividerColor; 84 | private float mDividerWidth; 85 | private float mDividerPadding; 86 | 87 | /** title */ 88 | private static final int TEXT_BOLD_NONE = 0; 89 | private static final int TEXT_BOLD_WHEN_SELECT = 1; 90 | private static final int TEXT_BOLD_BOTH = 2; 91 | private float mTextsize; 92 | private int mTextSelectColor; 93 | private int mTextUnselectColor; 94 | private int mTextBold; 95 | private boolean mTextAllCaps; 96 | 97 | /** icon */ 98 | private boolean mIconVisible; 99 | private int mIconGravity; 100 | private float mIconWidth; 101 | private float mIconHeight; 102 | private float mIconMargin; 103 | 104 | private int mHeight; 105 | 106 | /** anim */ 107 | private ValueAnimator mValueAnimator; 108 | private OvershootInterpolator mInterpolator = new OvershootInterpolator(1.5f); 109 | 110 | private FragmentChangeManager mFragmentChangeManager; 111 | 112 | public CommonTabLayout(Context context) { 113 | this(context, null, 0); 114 | } 115 | 116 | public CommonTabLayout(Context context, AttributeSet attrs) { 117 | this(context, attrs, 0); 118 | } 119 | 120 | public CommonTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { 121 | super(context, attrs, defStyleAttr); 122 | setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag 123 | setClipChildren(false); 124 | setClipToPadding(false); 125 | 126 | this.mContext = context; 127 | mTabsContainer = new LinearLayout(context); 128 | addView(mTabsContainer); 129 | 130 | obtainAttributes(context, attrs); 131 | 132 | //get layout_height 133 | String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height"); 134 | 135 | //create ViewPager 136 | if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) { 137 | } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) { 138 | } else { 139 | int[] systemAttrs = {android.R.attr.layout_height}; 140 | TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs); 141 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT); 142 | a.recycle(); 143 | } 144 | 145 | mValueAnimator = ValueAnimator.ofObject(new PointEvaluator(), mLastP, mCurrentP); 146 | mValueAnimator.addUpdateListener(this); 147 | } 148 | 149 | private void obtainAttributes(Context context, AttributeSet attrs) { 150 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CommonTabLayout); 151 | 152 | mIndicatorStyle = ta.getInt(R.styleable.CommonTabLayout_tl_indicator_style, 0); 153 | mIndicatorColor = ta.getColor(R.styleable.CommonTabLayout_tl_indicator_color, Color.parseColor(mIndicatorStyle == STYLE_BLOCK ? "#4B6A87" : "#ffffff")); 154 | mIndicatorHeight = ta.getDimension(R.styleable.CommonTabLayout_tl_indicator_height, 155 | dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 4 : (mIndicatorStyle == STYLE_BLOCK ? -1 : 2))); 156 | mIndicatorWidth = ta.getDimension(R.styleable.CommonTabLayout_tl_indicator_width, dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 10 : -1)); 157 | mIndicatorCornerRadius = ta.getDimension(R.styleable.CommonTabLayout_tl_indicator_corner_radius, dp2px(mIndicatorStyle == STYLE_BLOCK ? -1 : 0)); 158 | mIndicatorMarginLeft = ta.getDimension(R.styleable.CommonTabLayout_tl_indicator_margin_left, dp2px(0)); 159 | mIndicatorMarginTop = ta.getDimension(R.styleable.CommonTabLayout_tl_indicator_margin_top, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0)); 160 | mIndicatorMarginRight = ta.getDimension(R.styleable.CommonTabLayout_tl_indicator_margin_right, dp2px(0)); 161 | mIndicatorMarginBottom = ta.getDimension(R.styleable.CommonTabLayout_tl_indicator_margin_bottom, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0)); 162 | mIndicatorAnimEnable = ta.getBoolean(R.styleable.CommonTabLayout_tl_indicator_anim_enable, true); 163 | mIndicatorBounceEnable = ta.getBoolean(R.styleable.CommonTabLayout_tl_indicator_bounce_enable, true); 164 | mIndicatorAnimDuration = ta.getInt(R.styleable.CommonTabLayout_tl_indicator_anim_duration, -1); 165 | mIndicatorGravity = ta.getInt(R.styleable.CommonTabLayout_tl_indicator_gravity, Gravity.BOTTOM); 166 | 167 | mUnderlineColor = ta.getColor(R.styleable.CommonTabLayout_tl_underline_color, Color.parseColor("#ffffff")); 168 | mUnderlineHeight = ta.getDimension(R.styleable.CommonTabLayout_tl_underline_height, dp2px(0)); 169 | mUnderlineGravity = ta.getInt(R.styleable.CommonTabLayout_tl_underline_gravity, Gravity.BOTTOM); 170 | 171 | mDividerColor = ta.getColor(R.styleable.CommonTabLayout_tl_divider_color, Color.parseColor("#ffffff")); 172 | mDividerWidth = ta.getDimension(R.styleable.CommonTabLayout_tl_divider_width, dp2px(0)); 173 | mDividerPadding = ta.getDimension(R.styleable.CommonTabLayout_tl_divider_padding, dp2px(12)); 174 | 175 | mTextsize = ta.getDimension(R.styleable.CommonTabLayout_tl_textsize, sp2px(13f)); 176 | mTextSelectColor = ta.getColor(R.styleable.CommonTabLayout_tl_textSelectColor, Color.parseColor("#ffffff")); 177 | mTextUnselectColor = ta.getColor(R.styleable.CommonTabLayout_tl_textUnselectColor, Color.parseColor("#AAffffff")); 178 | mTextBold = ta.getInt(R.styleable.CommonTabLayout_tl_textBold, TEXT_BOLD_NONE); 179 | mTextAllCaps = ta.getBoolean(R.styleable.CommonTabLayout_tl_textAllCaps, false); 180 | 181 | mIconVisible = ta.getBoolean(R.styleable.CommonTabLayout_tl_iconVisible, true); 182 | mIconGravity = ta.getInt(R.styleable.CommonTabLayout_tl_iconGravity, Gravity.TOP); 183 | mIconWidth = ta.getDimension(R.styleable.CommonTabLayout_tl_iconWidth, dp2px(0)); 184 | mIconHeight = ta.getDimension(R.styleable.CommonTabLayout_tl_iconHeight, dp2px(0)); 185 | mIconMargin = ta.getDimension(R.styleable.CommonTabLayout_tl_iconMargin, dp2px(2.5f)); 186 | 187 | mTabSpaceEqual = ta.getBoolean(R.styleable.CommonTabLayout_tl_tab_space_equal, true); 188 | mTabWidth = ta.getDimension(R.styleable.CommonTabLayout_tl_tab_width, dp2px(-1)); 189 | mTabPadding = ta.getDimension(R.styleable.CommonTabLayout_tl_tab_padding, mTabSpaceEqual || mTabWidth > 0 ? dp2px(0) : dp2px(10)); 190 | 191 | ta.recycle(); 192 | } 193 | 194 | public void setTabData(ArrayList tabEntitys) { 195 | if (tabEntitys == null || tabEntitys.size() == 0) { 196 | throw new IllegalStateException("TabEntitys can not be NULL or EMPTY !"); 197 | } 198 | 199 | this.mTabEntitys.clear(); 200 | this.mTabEntitys.addAll(tabEntitys); 201 | 202 | notifyDataSetChanged(); 203 | } 204 | 205 | /** 关联数据支持同时切换fragments */ 206 | public void setTabData(ArrayList tabEntitys, FragmentActivity fa, int containerViewId, ArrayList fragments) { 207 | mFragmentChangeManager = new FragmentChangeManager(fa.getSupportFragmentManager(), containerViewId, fragments); 208 | setTabData(tabEntitys); 209 | } 210 | 211 | /** 更新数据 */ 212 | public void notifyDataSetChanged() { 213 | mTabsContainer.removeAllViews(); 214 | this.mTabCount = mTabEntitys.size(); 215 | View tabView; 216 | for (int i = 0; i < mTabCount; i++) { 217 | if (mIconGravity == Gravity.LEFT) { 218 | tabView = View.inflate(mContext, R.layout.layout_tab_left, null); 219 | } else if (mIconGravity == Gravity.RIGHT) { 220 | tabView = View.inflate(mContext, R.layout.layout_tab_right, null); 221 | } else if (mIconGravity == Gravity.BOTTOM) { 222 | tabView = View.inflate(mContext, R.layout.layout_tab_bottom, null); 223 | } else { 224 | tabView = View.inflate(mContext, R.layout.layout_tab_top, null); 225 | } 226 | 227 | tabView.setTag(i); 228 | addTab(i, tabView); 229 | } 230 | 231 | updateTabStyles(); 232 | } 233 | 234 | /** 创建并添加tab */ 235 | private void addTab(final int position, View tabView) { 236 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 237 | tv_tab_title.setText(mTabEntitys.get(position).getTabTitle()); 238 | ImageView iv_tab_icon = (ImageView) tabView.findViewById(R.id.iv_tab_icon); 239 | iv_tab_icon.setImageResource(mTabEntitys.get(position).getTabUnselectedIcon()); 240 | 241 | tabView.setOnClickListener(new OnClickListener() { 242 | @Override 243 | public void onClick(View v) { 244 | int position = (Integer) v.getTag(); 245 | if (mCurrentTab != position) { 246 | setCurrentTab(position); 247 | if (mListener != null) { 248 | mListener.onTabSelect(position); 249 | } 250 | } else { 251 | if (mListener != null) { 252 | mListener.onTabReselect(position); 253 | } 254 | } 255 | } 256 | }); 257 | 258 | /** 每一个Tab的布局参数 */ 259 | LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ? 260 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : 261 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 262 | if (mTabWidth > 0) { 263 | lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT); 264 | } 265 | mTabsContainer.addView(tabView, position, lp_tab); 266 | } 267 | 268 | private void updateTabStyles() { 269 | for (int i = 0; i < mTabCount; i++) { 270 | View tabView = mTabsContainer.getChildAt(i); 271 | tabView.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 272 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 273 | tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor); 274 | tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize); 275 | // tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 276 | if (mTextAllCaps) { 277 | tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase()); 278 | } 279 | 280 | if (mTextBold == TEXT_BOLD_BOTH) { 281 | tv_tab_title.getPaint().setFakeBoldText(true); 282 | } else if (mTextBold == TEXT_BOLD_NONE) { 283 | tv_tab_title.getPaint().setFakeBoldText(false); 284 | } 285 | 286 | ImageView iv_tab_icon = (ImageView) tabView.findViewById(R.id.iv_tab_icon); 287 | if (mIconVisible) { 288 | iv_tab_icon.setVisibility(View.VISIBLE); 289 | CustomTabEntity tabEntity = mTabEntitys.get(i); 290 | iv_tab_icon.setImageResource(i == mCurrentTab ? tabEntity.getTabSelectedIcon() : tabEntity.getTabUnselectedIcon()); 291 | LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 292 | mIconWidth <= 0 ? LinearLayout.LayoutParams.WRAP_CONTENT : (int) mIconWidth, 293 | mIconHeight <= 0 ? LinearLayout.LayoutParams.WRAP_CONTENT : (int) mIconHeight); 294 | if (mIconGravity == Gravity.LEFT) { 295 | lp.rightMargin = (int) mIconMargin; 296 | } else if (mIconGravity == Gravity.RIGHT) { 297 | lp.leftMargin = (int) mIconMargin; 298 | } else if (mIconGravity == Gravity.BOTTOM) { 299 | lp.topMargin = (int) mIconMargin; 300 | } else { 301 | lp.bottomMargin = (int) mIconMargin; 302 | } 303 | 304 | iv_tab_icon.setLayoutParams(lp); 305 | } else { 306 | iv_tab_icon.setVisibility(View.GONE); 307 | } 308 | } 309 | } 310 | 311 | private void updateTabSelection(int position) { 312 | for (int i = 0; i < mTabCount; ++i) { 313 | View tabView = mTabsContainer.getChildAt(i); 314 | final boolean isSelect = i == position; 315 | TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 316 | tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor); 317 | ImageView iv_tab_icon = (ImageView) tabView.findViewById(R.id.iv_tab_icon); 318 | CustomTabEntity tabEntity = mTabEntitys.get(i); 319 | iv_tab_icon.setImageResource(isSelect ? tabEntity.getTabSelectedIcon() : tabEntity.getTabUnselectedIcon()); 320 | if (mTextBold == TEXT_BOLD_WHEN_SELECT) { 321 | tab_title.getPaint().setFakeBoldText(isSelect); 322 | } 323 | } 324 | } 325 | 326 | private void calcOffset() { 327 | final View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 328 | mCurrentP.left = currentTabView.getLeft(); 329 | mCurrentP.right = currentTabView.getRight(); 330 | 331 | final View lastTabView = mTabsContainer.getChildAt(this.mLastTab); 332 | mLastP.left = lastTabView.getLeft(); 333 | mLastP.right = lastTabView.getRight(); 334 | 335 | // Log.d("AAA", "mLastP--->" + mLastP.left + "&" + mLastP.right); 336 | // Log.d("AAA", "mCurrentP--->" + mCurrentP.left + "&" + mCurrentP.right); 337 | if (mLastP.left == mCurrentP.left && mLastP.right == mCurrentP.right) { 338 | invalidate(); 339 | } else { 340 | mValueAnimator.setObjectValues(mLastP, mCurrentP); 341 | if (mIndicatorBounceEnable) { 342 | mValueAnimator.setInterpolator(mInterpolator); 343 | } 344 | 345 | if (mIndicatorAnimDuration < 0) { 346 | mIndicatorAnimDuration = mIndicatorBounceEnable ? 500 : 250; 347 | } 348 | mValueAnimator.setDuration(mIndicatorAnimDuration); 349 | mValueAnimator.start(); 350 | } 351 | } 352 | 353 | private void calcIndicatorRect() { 354 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 355 | float left = currentTabView.getLeft(); 356 | float right = currentTabView.getRight(); 357 | 358 | mIndicatorRect.left = (int) left; 359 | mIndicatorRect.right = (int) right; 360 | 361 | if (mIndicatorWidth < 0) { //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip 362 | 363 | } else {//indicatorWidth大于0时,圆角矩形以及三角形 364 | float indicatorLeft = currentTabView.getLeft() + (currentTabView.getWidth() - mIndicatorWidth) / 2; 365 | 366 | mIndicatorRect.left = (int) indicatorLeft; 367 | mIndicatorRect.right = (int) (mIndicatorRect.left + mIndicatorWidth); 368 | } 369 | } 370 | 371 | @Override 372 | public void onAnimationUpdate(ValueAnimator animation) { 373 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 374 | IndicatorPoint p = (IndicatorPoint) animation.getAnimatedValue(); 375 | mIndicatorRect.left = (int) p.left; 376 | mIndicatorRect.right = (int) p.right; 377 | 378 | if (mIndicatorWidth < 0) { //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip 379 | 380 | } else {//indicatorWidth大于0时,圆角矩形以及三角形 381 | float indicatorLeft = p.left + (currentTabView.getWidth() - mIndicatorWidth) / 2; 382 | 383 | mIndicatorRect.left = (int) indicatorLeft; 384 | mIndicatorRect.right = (int) (mIndicatorRect.left + mIndicatorWidth); 385 | } 386 | invalidate(); 387 | } 388 | 389 | private boolean mIsFirstDraw = true; 390 | 391 | @Override 392 | protected void onDraw(Canvas canvas) { 393 | super.onDraw(canvas); 394 | 395 | if (isInEditMode() || mTabCount <= 0) { 396 | return; 397 | } 398 | 399 | int height = getHeight(); 400 | int paddingLeft = getPaddingLeft(); 401 | // draw divider 402 | if (mDividerWidth > 0) { 403 | mDividerPaint.setStrokeWidth(mDividerWidth); 404 | mDividerPaint.setColor(mDividerColor); 405 | for (int i = 0; i < mTabCount - 1; i++) { 406 | View tab = mTabsContainer.getChildAt(i); 407 | canvas.drawLine(paddingLeft + tab.getRight(), mDividerPadding, paddingLeft + tab.getRight(), height - mDividerPadding, mDividerPaint); 408 | } 409 | } 410 | 411 | // draw underline 412 | if (mUnderlineHeight > 0) { 413 | mRectPaint.setColor(mUnderlineColor); 414 | if (mUnderlineGravity == Gravity.BOTTOM) { 415 | canvas.drawRect(paddingLeft, height - mUnderlineHeight, mTabsContainer.getWidth() + paddingLeft, height, mRectPaint); 416 | } else { 417 | canvas.drawRect(paddingLeft, 0, mTabsContainer.getWidth() + paddingLeft, mUnderlineHeight, mRectPaint); 418 | } 419 | } 420 | 421 | //draw indicator line 422 | if (mIndicatorAnimEnable) { 423 | if (mIsFirstDraw) { 424 | mIsFirstDraw = false; 425 | calcIndicatorRect(); 426 | } 427 | } else { 428 | calcIndicatorRect(); 429 | } 430 | 431 | 432 | if (mIndicatorStyle == STYLE_TRIANGLE) { 433 | if (mIndicatorHeight > 0) { 434 | mTrianglePaint.setColor(mIndicatorColor); 435 | mTrianglePath.reset(); 436 | mTrianglePath.moveTo(paddingLeft + mIndicatorRect.left, height); 437 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.left / 2 + mIndicatorRect.right / 2, height - mIndicatorHeight); 438 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.right, height); 439 | mTrianglePath.close(); 440 | canvas.drawPath(mTrianglePath, mTrianglePaint); 441 | } 442 | } else if (mIndicatorStyle == STYLE_BLOCK) { 443 | if (mIndicatorHeight < 0) { 444 | mIndicatorHeight = height - mIndicatorMarginTop - mIndicatorMarginBottom; 445 | } else { 446 | 447 | } 448 | 449 | if (mIndicatorHeight > 0) { 450 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) { 451 | mIndicatorCornerRadius = mIndicatorHeight / 2; 452 | } 453 | 454 | mIndicatorDrawable.setColor(mIndicatorColor); 455 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 456 | (int) mIndicatorMarginTop, (int) (paddingLeft + mIndicatorRect.right - mIndicatorMarginRight), 457 | (int) (mIndicatorMarginTop + mIndicatorHeight)); 458 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius); 459 | mIndicatorDrawable.draw(canvas); 460 | } 461 | } else { 462 | /* mRectPaint.setColor(mIndicatorColor); 463 | calcIndicatorRect(); 464 | canvas.drawRect(getPaddingLeft() + mIndicatorRect.left, getHeight() - mIndicatorHeight, 465 | mIndicatorRect.right + getPaddingLeft(), getHeight(), mRectPaint);*/ 466 | 467 | if (mIndicatorHeight > 0) { 468 | mIndicatorDrawable.setColor(mIndicatorColor); 469 | if (mIndicatorGravity == Gravity.BOTTOM) { 470 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 471 | height - (int) mIndicatorHeight - (int) mIndicatorMarginBottom, 472 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight, 473 | height - (int) mIndicatorMarginBottom); 474 | } else { 475 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 476 | (int) mIndicatorMarginTop, 477 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight, 478 | (int) mIndicatorHeight + (int) mIndicatorMarginTop); 479 | } 480 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius); 481 | mIndicatorDrawable.draw(canvas); 482 | } 483 | } 484 | } 485 | 486 | //setter and getter 487 | public void setCurrentTab(int currentTab) { 488 | mLastTab = this.mCurrentTab; 489 | this.mCurrentTab = currentTab; 490 | updateTabSelection(currentTab); 491 | if (mFragmentChangeManager != null) { 492 | mFragmentChangeManager.setFragments(currentTab); 493 | } 494 | if (mIndicatorAnimEnable) { 495 | calcOffset(); 496 | } else { 497 | invalidate(); 498 | } 499 | } 500 | 501 | public void setIndicatorStyle(int indicatorStyle) { 502 | this.mIndicatorStyle = indicatorStyle; 503 | invalidate(); 504 | } 505 | 506 | public void setTabPadding(float tabPadding) { 507 | this.mTabPadding = dp2px(tabPadding); 508 | updateTabStyles(); 509 | } 510 | 511 | public void setTabSpaceEqual(boolean tabSpaceEqual) { 512 | this.mTabSpaceEqual = tabSpaceEqual; 513 | updateTabStyles(); 514 | } 515 | 516 | public void setTabWidth(float tabWidth) { 517 | this.mTabWidth = dp2px(tabWidth); 518 | updateTabStyles(); 519 | } 520 | 521 | public void setIndicatorColor(int indicatorColor) { 522 | this.mIndicatorColor = indicatorColor; 523 | invalidate(); 524 | } 525 | 526 | public void setIndicatorHeight(float indicatorHeight) { 527 | this.mIndicatorHeight = dp2px(indicatorHeight); 528 | invalidate(); 529 | } 530 | 531 | public void setIndicatorWidth(float indicatorWidth) { 532 | this.mIndicatorWidth = dp2px(indicatorWidth); 533 | invalidate(); 534 | } 535 | 536 | public void setIndicatorCornerRadius(float indicatorCornerRadius) { 537 | this.mIndicatorCornerRadius = dp2px(indicatorCornerRadius); 538 | invalidate(); 539 | } 540 | 541 | public void setIndicatorGravity(int indicatorGravity) { 542 | this.mIndicatorGravity = indicatorGravity; 543 | invalidate(); 544 | } 545 | 546 | public void setIndicatorMargin(float indicatorMarginLeft, float indicatorMarginTop, 547 | float indicatorMarginRight, float indicatorMarginBottom) { 548 | this.mIndicatorMarginLeft = dp2px(indicatorMarginLeft); 549 | this.mIndicatorMarginTop = dp2px(indicatorMarginTop); 550 | this.mIndicatorMarginRight = dp2px(indicatorMarginRight); 551 | this.mIndicatorMarginBottom = dp2px(indicatorMarginBottom); 552 | invalidate(); 553 | } 554 | 555 | public void setIndicatorAnimDuration(long indicatorAnimDuration) { 556 | this.mIndicatorAnimDuration = indicatorAnimDuration; 557 | } 558 | 559 | public void setIndicatorAnimEnable(boolean indicatorAnimEnable) { 560 | this.mIndicatorAnimEnable = indicatorAnimEnable; 561 | } 562 | 563 | public void setIndicatorBounceEnable(boolean indicatorBounceEnable) { 564 | this.mIndicatorBounceEnable = indicatorBounceEnable; 565 | } 566 | 567 | public void setUnderlineColor(int underlineColor) { 568 | this.mUnderlineColor = underlineColor; 569 | invalidate(); 570 | } 571 | 572 | public void setUnderlineHeight(float underlineHeight) { 573 | this.mUnderlineHeight = dp2px(underlineHeight); 574 | invalidate(); 575 | } 576 | 577 | public void setUnderlineGravity(int underlineGravity) { 578 | this.mUnderlineGravity = underlineGravity; 579 | invalidate(); 580 | } 581 | 582 | public void setDividerColor(int dividerColor) { 583 | this.mDividerColor = dividerColor; 584 | invalidate(); 585 | } 586 | 587 | public void setDividerWidth(float dividerWidth) { 588 | this.mDividerWidth = dp2px(dividerWidth); 589 | invalidate(); 590 | } 591 | 592 | public void setDividerPadding(float dividerPadding) { 593 | this.mDividerPadding = dp2px(dividerPadding); 594 | invalidate(); 595 | } 596 | 597 | public void setTextsize(float textsize) { 598 | this.mTextsize = sp2px(textsize); 599 | updateTabStyles(); 600 | } 601 | 602 | public void setTextSelectColor(int textSelectColor) { 603 | this.mTextSelectColor = textSelectColor; 604 | updateTabStyles(); 605 | } 606 | 607 | public void setTextUnselectColor(int textUnselectColor) { 608 | this.mTextUnselectColor = textUnselectColor; 609 | updateTabStyles(); 610 | } 611 | 612 | public void setTextBold(int textBold) { 613 | this.mTextBold = textBold; 614 | updateTabStyles(); 615 | } 616 | 617 | public void setIconVisible(boolean iconVisible) { 618 | this.mIconVisible = iconVisible; 619 | updateTabStyles(); 620 | } 621 | 622 | public void setIconGravity(int iconGravity) { 623 | this.mIconGravity = iconGravity; 624 | notifyDataSetChanged(); 625 | } 626 | 627 | public void setIconWidth(float iconWidth) { 628 | this.mIconWidth = dp2px(iconWidth); 629 | updateTabStyles(); 630 | } 631 | 632 | public void setIconHeight(float iconHeight) { 633 | this.mIconHeight = dp2px(iconHeight); 634 | updateTabStyles(); 635 | } 636 | 637 | public void setIconMargin(float iconMargin) { 638 | this.mIconMargin = dp2px(iconMargin); 639 | updateTabStyles(); 640 | } 641 | 642 | public void setTextAllCaps(boolean textAllCaps) { 643 | this.mTextAllCaps = textAllCaps; 644 | updateTabStyles(); 645 | } 646 | 647 | 648 | public int getTabCount() { 649 | return mTabCount; 650 | } 651 | 652 | public int getCurrentTab() { 653 | return mCurrentTab; 654 | } 655 | 656 | public int getIndicatorStyle() { 657 | return mIndicatorStyle; 658 | } 659 | 660 | public float getTabPadding() { 661 | return mTabPadding; 662 | } 663 | 664 | public boolean isTabSpaceEqual() { 665 | return mTabSpaceEqual; 666 | } 667 | 668 | public float getTabWidth() { 669 | return mTabWidth; 670 | } 671 | 672 | public int getIndicatorColor() { 673 | return mIndicatorColor; 674 | } 675 | 676 | public float getIndicatorHeight() { 677 | return mIndicatorHeight; 678 | } 679 | 680 | public float getIndicatorWidth() { 681 | return mIndicatorWidth; 682 | } 683 | 684 | public float getIndicatorCornerRadius() { 685 | return mIndicatorCornerRadius; 686 | } 687 | 688 | public float getIndicatorMarginLeft() { 689 | return mIndicatorMarginLeft; 690 | } 691 | 692 | public float getIndicatorMarginTop() { 693 | return mIndicatorMarginTop; 694 | } 695 | 696 | public float getIndicatorMarginRight() { 697 | return mIndicatorMarginRight; 698 | } 699 | 700 | public float getIndicatorMarginBottom() { 701 | return mIndicatorMarginBottom; 702 | } 703 | 704 | public long getIndicatorAnimDuration() { 705 | return mIndicatorAnimDuration; 706 | } 707 | 708 | public boolean isIndicatorAnimEnable() { 709 | return mIndicatorAnimEnable; 710 | } 711 | 712 | public boolean isIndicatorBounceEnable() { 713 | return mIndicatorBounceEnable; 714 | } 715 | 716 | public int getUnderlineColor() { 717 | return mUnderlineColor; 718 | } 719 | 720 | public float getUnderlineHeight() { 721 | return mUnderlineHeight; 722 | } 723 | 724 | public int getDividerColor() { 725 | return mDividerColor; 726 | } 727 | 728 | public float getDividerWidth() { 729 | return mDividerWidth; 730 | } 731 | 732 | public float getDividerPadding() { 733 | return mDividerPadding; 734 | } 735 | 736 | public float getTextsize() { 737 | return mTextsize; 738 | } 739 | 740 | public int getTextSelectColor() { 741 | return mTextSelectColor; 742 | } 743 | 744 | public int getTextUnselectColor() { 745 | return mTextUnselectColor; 746 | } 747 | 748 | public int getTextBold() { 749 | return mTextBold; 750 | } 751 | 752 | public boolean isTextAllCaps() { 753 | return mTextAllCaps; 754 | } 755 | 756 | public int getIconGravity() { 757 | return mIconGravity; 758 | } 759 | 760 | public float getIconWidth() { 761 | return mIconWidth; 762 | } 763 | 764 | public float getIconHeight() { 765 | return mIconHeight; 766 | } 767 | 768 | public float getIconMargin() { 769 | return mIconMargin; 770 | } 771 | 772 | public boolean isIconVisible() { 773 | return mIconVisible; 774 | } 775 | 776 | 777 | public ImageView getIconView(int tab) { 778 | View tabView = mTabsContainer.getChildAt(tab); 779 | ImageView iv_tab_icon = (ImageView) tabView.findViewById(R.id.iv_tab_icon); 780 | return iv_tab_icon; 781 | } 782 | 783 | public TextView getTitleView(int tab) { 784 | View tabView = mTabsContainer.getChildAt(tab); 785 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 786 | return tv_tab_title; 787 | } 788 | 789 | //setter and getter 790 | 791 | // show MsgTipView 792 | private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 793 | private SparseArray mInitSetMap = new SparseArray<>(); 794 | 795 | /** 796 | * 显示未读消息 797 | * 798 | * @param position 显示tab位置 799 | * @param num num小于等于0显示红点,num大于0显示数字 800 | */ 801 | public void showMsg(int position, int num) { 802 | if (position >= mTabCount) { 803 | position = mTabCount - 1; 804 | } 805 | 806 | View tabView = mTabsContainer.getChildAt(position); 807 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 808 | if (tipView != null) { 809 | UnreadMsgUtils.show(tipView, num); 810 | 811 | if (mInitSetMap.get(position) != null && mInitSetMap.get(position)) { 812 | return; 813 | } 814 | 815 | if (!mIconVisible) { 816 | setMsgMargin(position, 2, 2); 817 | } else { 818 | setMsgMargin(position, 0, 819 | mIconGravity == Gravity.LEFT || mIconGravity == Gravity.RIGHT ? 4 : 0); 820 | } 821 | 822 | mInitSetMap.put(position, true); 823 | } 824 | } 825 | 826 | /** 827 | * 显示未读红点 828 | * 829 | * @param position 显示tab位置 830 | */ 831 | public void showDot(int position) { 832 | if (position >= mTabCount) { 833 | position = mTabCount - 1; 834 | } 835 | showMsg(position, 0); 836 | } 837 | 838 | public void hideMsg(int position) { 839 | if (position >= mTabCount) { 840 | position = mTabCount - 1; 841 | } 842 | 843 | View tabView = mTabsContainer.getChildAt(position); 844 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 845 | if (tipView != null) { 846 | tipView.setVisibility(View.GONE); 847 | } 848 | } 849 | 850 | /** 851 | * 设置提示红点偏移,注意 852 | * 1.控件为固定高度:参照点为tab内容的右上角 853 | * 2.控件高度不固定(WRAP_CONTENT):参照点为tab内容的右上角,此时高度已是红点的最高显示范围,所以这时bottomPadding其实就是topPadding 854 | */ 855 | public void setMsgMargin(int position, float leftPadding, float bottomPadding) { 856 | if (position >= mTabCount) { 857 | position = mTabCount - 1; 858 | } 859 | View tabView = mTabsContainer.getChildAt(position); 860 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 861 | if (tipView != null) { 862 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 863 | mTextPaint.setTextSize(mTextsize); 864 | float textWidth = mTextPaint.measureText(tv_tab_title.getText().toString()); 865 | float textHeight = mTextPaint.descent() - mTextPaint.ascent(); 866 | MarginLayoutParams lp = (MarginLayoutParams) tipView.getLayoutParams(); 867 | 868 | float iconH = mIconHeight; 869 | float margin = 0; 870 | if (mIconVisible) { 871 | if (iconH <= 0) { 872 | iconH = mContext.getResources().getDrawable(mTabEntitys.get(position).getTabSelectedIcon()).getIntrinsicHeight(); 873 | } 874 | margin = mIconMargin; 875 | } 876 | 877 | if (mIconGravity == Gravity.TOP || mIconGravity == Gravity.BOTTOM) { 878 | lp.leftMargin = dp2px(leftPadding); 879 | lp.topMargin = mHeight > 0 ? (int) (mHeight - textHeight - iconH - margin) / 2 - dp2px(bottomPadding) : dp2px(bottomPadding); 880 | } else { 881 | lp.leftMargin = dp2px(leftPadding); 882 | lp.topMargin = mHeight > 0 ? (int) (mHeight - Math.max(textHeight, iconH)) / 2 - dp2px(bottomPadding) : dp2px(bottomPadding); 883 | } 884 | 885 | tipView.setLayoutParams(lp); 886 | } 887 | } 888 | 889 | /** 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置 */ 890 | public MsgView getMsgView(int position) { 891 | if (position >= mTabCount) { 892 | position = mTabCount - 1; 893 | } 894 | View tabView = mTabsContainer.getChildAt(position); 895 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 896 | return tipView; 897 | } 898 | 899 | private OnTabSelectListener mListener; 900 | 901 | public void setOnTabSelectListener(OnTabSelectListener listener) { 902 | this.mListener = listener; 903 | } 904 | 905 | 906 | @Override 907 | protected Parcelable onSaveInstanceState() { 908 | Bundle bundle = new Bundle(); 909 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 910 | bundle.putInt("mCurrentTab", mCurrentTab); 911 | return bundle; 912 | } 913 | 914 | @Override 915 | protected void onRestoreInstanceState(Parcelable state) { 916 | if (state instanceof Bundle) { 917 | Bundle bundle = (Bundle) state; 918 | mCurrentTab = bundle.getInt("mCurrentTab"); 919 | state = bundle.getParcelable("instanceState"); 920 | if (mCurrentTab != 0 && mTabsContainer.getChildCount() > 0) { 921 | updateTabSelection(mCurrentTab); 922 | } 923 | } 924 | super.onRestoreInstanceState(state); 925 | } 926 | 927 | class IndicatorPoint { 928 | public float left; 929 | public float right; 930 | } 931 | 932 | private IndicatorPoint mCurrentP = new IndicatorPoint(); 933 | private IndicatorPoint mLastP = new IndicatorPoint(); 934 | 935 | class PointEvaluator implements TypeEvaluator { 936 | @Override 937 | public IndicatorPoint evaluate(float fraction, IndicatorPoint startValue, IndicatorPoint endValue) { 938 | float left = startValue.left + fraction * (endValue.left - startValue.left); 939 | float right = startValue.right + fraction * (endValue.right - startValue.right); 940 | IndicatorPoint point = new IndicatorPoint(); 941 | point.left = left; 942 | point.right = right; 943 | return point; 944 | } 945 | } 946 | 947 | 948 | protected int dp2px(float dp) { 949 | final float scale = mContext.getResources().getDisplayMetrics().density; 950 | return (int) (dp * scale + 0.5f); 951 | } 952 | 953 | protected int sp2px(float sp) { 954 | final float scale = this.mContext.getResources().getDisplayMetrics().scaledDensity; 955 | return (int) (sp * scale + 0.5f); 956 | } 957 | 958 | } 959 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/java/com/flyco/tablayout/SegmentTabLayout.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout; 2 | 3 | import android.animation.TypeEvaluator; 4 | import android.animation.ValueAnimator; 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.graphics.Color; 9 | import android.graphics.Paint; 10 | import android.graphics.Rect; 11 | import android.graphics.drawable.GradientDrawable; 12 | import android.os.Bundle; 13 | import android.os.Parcelable; 14 | import android.util.AttributeSet; 15 | import android.util.SparseArray; 16 | import android.util.TypedValue; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.view.animation.OvershootInterpolator; 20 | import android.widget.FrameLayout; 21 | import android.widget.LinearLayout; 22 | import android.widget.TextView; 23 | 24 | import androidx.fragment.app.Fragment; 25 | import androidx.fragment.app.FragmentActivity; 26 | 27 | import com.flyco.tablayout.listener.OnTabSelectListener; 28 | import com.flyco.tablayout.utils.FragmentChangeManager; 29 | import com.flyco.tablayout.utils.UnreadMsgUtils; 30 | import com.flyco.tablayout.widget.MsgView; 31 | 32 | import java.util.ArrayList; 33 | 34 | public class SegmentTabLayout extends FrameLayout implements ValueAnimator.AnimatorUpdateListener { 35 | private Context mContext; 36 | private String[] mTitles; 37 | private LinearLayout mTabsContainer; 38 | private int mCurrentTab; 39 | private int mLastTab; 40 | private int mTabCount; 41 | /** 用于绘制显示器 */ 42 | private Rect mIndicatorRect = new Rect(); 43 | private GradientDrawable mIndicatorDrawable = new GradientDrawable(); 44 | private GradientDrawable mRectDrawable = new GradientDrawable(); 45 | 46 | private Paint mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 47 | 48 | private float mTabPadding; 49 | private boolean mTabSpaceEqual; 50 | private float mTabWidth; 51 | 52 | /** indicator */ 53 | private int mIndicatorColor; 54 | private float mIndicatorHeight; 55 | private float mIndicatorCornerRadius; 56 | private float mIndicatorMarginLeft; 57 | private float mIndicatorMarginTop; 58 | private float mIndicatorMarginRight; 59 | private float mIndicatorMarginBottom; 60 | private long mIndicatorAnimDuration; 61 | private boolean mIndicatorAnimEnable; 62 | private boolean mIndicatorBounceEnable; 63 | 64 | /** divider */ 65 | private int mDividerColor; 66 | private float mDividerWidth; 67 | private float mDividerPadding; 68 | 69 | /** title */ 70 | private static final int TEXT_BOLD_NONE = 0; 71 | private static final int TEXT_BOLD_WHEN_SELECT = 1; 72 | private static final int TEXT_BOLD_BOTH = 2; 73 | private float mTextsize; 74 | private int mTextSelectColor; 75 | private int mTextUnselectColor; 76 | private int mTextBold; 77 | private boolean mTextAllCaps; 78 | 79 | private int mBarColor; 80 | private int mBarStrokeColor; 81 | private float mBarStrokeWidth; 82 | 83 | private int mHeight; 84 | 85 | /** anim */ 86 | private ValueAnimator mValueAnimator; 87 | private OvershootInterpolator mInterpolator = new OvershootInterpolator(0.8f); 88 | 89 | private FragmentChangeManager mFragmentChangeManager; 90 | private float[] mRadiusArr = new float[8]; 91 | 92 | public SegmentTabLayout(Context context) { 93 | this(context, null, 0); 94 | } 95 | 96 | public SegmentTabLayout(Context context, AttributeSet attrs) { 97 | this(context, attrs, 0); 98 | } 99 | 100 | public SegmentTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { 101 | super(context, attrs, defStyleAttr); 102 | setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag 103 | setClipChildren(false); 104 | setClipToPadding(false); 105 | 106 | this.mContext = context; 107 | mTabsContainer = new LinearLayout(context); 108 | addView(mTabsContainer); 109 | 110 | obtainAttributes(context, attrs); 111 | 112 | //get layout_height 113 | String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height"); 114 | 115 | //create ViewPager 116 | if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) { 117 | } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) { 118 | } else { 119 | int[] systemAttrs = {android.R.attr.layout_height}; 120 | TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs); 121 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT); 122 | a.recycle(); 123 | } 124 | 125 | mValueAnimator = ValueAnimator.ofObject(new PointEvaluator(), mLastP, mCurrentP); 126 | mValueAnimator.addUpdateListener(this); 127 | } 128 | 129 | private void obtainAttributes(Context context, AttributeSet attrs) { 130 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SegmentTabLayout); 131 | 132 | mIndicatorColor = ta.getColor(R.styleable.SegmentTabLayout_tl_indicator_color, Color.parseColor("#222831")); 133 | mIndicatorHeight = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_height, -1); 134 | mIndicatorCornerRadius = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_corner_radius, -1); 135 | mIndicatorMarginLeft = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_left, dp2px(0)); 136 | mIndicatorMarginTop = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_top, 0); 137 | mIndicatorMarginRight = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_right, dp2px(0)); 138 | mIndicatorMarginBottom = ta.getDimension(R.styleable.SegmentTabLayout_tl_indicator_margin_bottom, 0); 139 | mIndicatorAnimEnable = ta.getBoolean(R.styleable.SegmentTabLayout_tl_indicator_anim_enable, false); 140 | mIndicatorBounceEnable = ta.getBoolean(R.styleable.SegmentTabLayout_tl_indicator_bounce_enable, true); 141 | mIndicatorAnimDuration = ta.getInt(R.styleable.SegmentTabLayout_tl_indicator_anim_duration, -1); 142 | 143 | mDividerColor = ta.getColor(R.styleable.SegmentTabLayout_tl_divider_color, mIndicatorColor); 144 | mDividerWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_divider_width, dp2px(1)); 145 | mDividerPadding = ta.getDimension(R.styleable.SegmentTabLayout_tl_divider_padding, 0); 146 | 147 | mTextsize = ta.getDimension(R.styleable.SegmentTabLayout_tl_textsize, sp2px(13f)); 148 | mTextSelectColor = ta.getColor(R.styleable.SegmentTabLayout_tl_textSelectColor, Color.parseColor("#ffffff")); 149 | mTextUnselectColor = ta.getColor(R.styleable.SegmentTabLayout_tl_textUnselectColor, mIndicatorColor); 150 | mTextBold = ta.getInt(R.styleable.SegmentTabLayout_tl_textBold, TEXT_BOLD_NONE); 151 | mTextAllCaps = ta.getBoolean(R.styleable.SegmentTabLayout_tl_textAllCaps, false); 152 | 153 | mTabSpaceEqual = ta.getBoolean(R.styleable.SegmentTabLayout_tl_tab_space_equal, true); 154 | mTabWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_tab_width, dp2px(-1)); 155 | mTabPadding = ta.getDimension(R.styleable.SegmentTabLayout_tl_tab_padding, mTabSpaceEqual || mTabWidth > 0 ? dp2px(0) : dp2px(10)); 156 | 157 | mBarColor = ta.getColor(R.styleable.SegmentTabLayout_tl_bar_color, Color.TRANSPARENT); 158 | mBarStrokeColor = ta.getColor(R.styleable.SegmentTabLayout_tl_bar_stroke_color, mIndicatorColor); 159 | mBarStrokeWidth = ta.getDimension(R.styleable.SegmentTabLayout_tl_bar_stroke_width, dp2px(1)); 160 | 161 | ta.recycle(); 162 | } 163 | 164 | public void setTabData(String[] titles) { 165 | if (titles == null || titles.length == 0) { 166 | throw new IllegalStateException("Titles can not be NULL or EMPTY !"); 167 | } 168 | 169 | this.mTitles = titles; 170 | 171 | notifyDataSetChanged(); 172 | } 173 | 174 | /** 关联数据支持同时切换fragments */ 175 | public void setTabData(String[] titles, FragmentActivity fa, int containerViewId, ArrayList fragments) { 176 | mFragmentChangeManager = new FragmentChangeManager(fa.getSupportFragmentManager(), containerViewId, fragments); 177 | setTabData(titles); 178 | } 179 | 180 | /** 更新数据 */ 181 | public void notifyDataSetChanged() { 182 | mTabsContainer.removeAllViews(); 183 | this.mTabCount = mTitles.length; 184 | View tabView; 185 | for (int i = 0; i < mTabCount; i++) { 186 | tabView = View.inflate(mContext, R.layout.layout_tab_segment, null); 187 | tabView.setTag(i); 188 | addTab(i, tabView); 189 | } 190 | 191 | updateTabStyles(); 192 | } 193 | 194 | /** 创建并添加tab */ 195 | private void addTab(final int position, View tabView) { 196 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 197 | tv_tab_title.setText(mTitles[position]); 198 | 199 | tabView.setOnClickListener(new OnClickListener() { 200 | @Override 201 | public void onClick(View v) { 202 | int position = (Integer) v.getTag(); 203 | if (mCurrentTab != position) { 204 | setCurrentTab(position); 205 | if (mListener != null) { 206 | mListener.onTabSelect(position); 207 | } 208 | } else { 209 | if (mListener != null) { 210 | mListener.onTabReselect(position); 211 | } 212 | } 213 | } 214 | }); 215 | 216 | /** 每一个Tab的布局参数 */ 217 | LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ? 218 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : 219 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 220 | if (mTabWidth > 0) { 221 | lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT); 222 | } 223 | mTabsContainer.addView(tabView, position, lp_tab); 224 | } 225 | 226 | private void updateTabStyles() { 227 | for (int i = 0; i < mTabCount; i++) { 228 | View tabView = mTabsContainer.getChildAt(i); 229 | tabView.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 230 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 231 | tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor); 232 | tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize); 233 | // tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 234 | if (mTextAllCaps) { 235 | tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase()); 236 | } 237 | 238 | if (mTextBold == TEXT_BOLD_BOTH) { 239 | tv_tab_title.getPaint().setFakeBoldText(true); 240 | } else if (mTextBold == TEXT_BOLD_NONE) { 241 | tv_tab_title.getPaint().setFakeBoldText(false); 242 | } 243 | } 244 | } 245 | 246 | private void updateTabSelection(int position) { 247 | for (int i = 0; i < mTabCount; ++i) { 248 | View tabView = mTabsContainer.getChildAt(i); 249 | final boolean isSelect = i == position; 250 | TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 251 | tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor); 252 | if (mTextBold == TEXT_BOLD_WHEN_SELECT) { 253 | tab_title.getPaint().setFakeBoldText(isSelect); 254 | } 255 | } 256 | } 257 | 258 | private void calcOffset() { 259 | final View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 260 | mCurrentP.left = currentTabView.getLeft(); 261 | mCurrentP.right = currentTabView.getRight(); 262 | 263 | final View lastTabView = mTabsContainer.getChildAt(this.mLastTab); 264 | mLastP.left = lastTabView.getLeft(); 265 | mLastP.right = lastTabView.getRight(); 266 | 267 | // Log.d("AAA", "mLastP--->" + mLastP.left + "&" + mLastP.right); 268 | // Log.d("AAA", "mCurrentP--->" + mCurrentP.left + "&" + mCurrentP.right); 269 | if (mLastP.left == mCurrentP.left && mLastP.right == mCurrentP.right) { 270 | invalidate(); 271 | } else { 272 | mValueAnimator.setObjectValues(mLastP, mCurrentP); 273 | if (mIndicatorBounceEnable) { 274 | mValueAnimator.setInterpolator(mInterpolator); 275 | } 276 | 277 | if (mIndicatorAnimDuration < 0) { 278 | mIndicatorAnimDuration = mIndicatorBounceEnable ? 500 : 250; 279 | } 280 | mValueAnimator.setDuration(mIndicatorAnimDuration); 281 | mValueAnimator.start(); 282 | } 283 | } 284 | 285 | private void calcIndicatorRect() { 286 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 287 | float left = currentTabView.getLeft(); 288 | float right = currentTabView.getRight(); 289 | 290 | mIndicatorRect.left = (int) left; 291 | mIndicatorRect.right = (int) right; 292 | 293 | if (!mIndicatorAnimEnable) { 294 | if (mCurrentTab == 0) { 295 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/ 296 | mRadiusArr[0] = mIndicatorCornerRadius; 297 | mRadiusArr[1] = mIndicatorCornerRadius; 298 | mRadiusArr[2] = 0; 299 | mRadiusArr[3] = 0; 300 | mRadiusArr[4] = 0; 301 | mRadiusArr[5] = 0; 302 | mRadiusArr[6] = mIndicatorCornerRadius; 303 | mRadiusArr[7] = mIndicatorCornerRadius; 304 | } else if (mCurrentTab == mTabCount - 1) { 305 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/ 306 | mRadiusArr[0] = 0; 307 | mRadiusArr[1] = 0; 308 | mRadiusArr[2] = mIndicatorCornerRadius; 309 | mRadiusArr[3] = mIndicatorCornerRadius; 310 | mRadiusArr[4] = mIndicatorCornerRadius; 311 | mRadiusArr[5] = mIndicatorCornerRadius; 312 | mRadiusArr[6] = 0; 313 | mRadiusArr[7] = 0; 314 | } else { 315 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/ 316 | mRadiusArr[0] = 0; 317 | mRadiusArr[1] = 0; 318 | mRadiusArr[2] = 0; 319 | mRadiusArr[3] = 0; 320 | mRadiusArr[4] = 0; 321 | mRadiusArr[5] = 0; 322 | mRadiusArr[6] = 0; 323 | mRadiusArr[7] = 0; 324 | } 325 | } else { 326 | /**The corners are ordered top-left, top-right, bottom-right, bottom-left*/ 327 | mRadiusArr[0] = mIndicatorCornerRadius; 328 | mRadiusArr[1] = mIndicatorCornerRadius; 329 | mRadiusArr[2] = mIndicatorCornerRadius; 330 | mRadiusArr[3] = mIndicatorCornerRadius; 331 | mRadiusArr[4] = mIndicatorCornerRadius; 332 | mRadiusArr[5] = mIndicatorCornerRadius; 333 | mRadiusArr[6] = mIndicatorCornerRadius; 334 | mRadiusArr[7] = mIndicatorCornerRadius; 335 | } 336 | } 337 | 338 | @Override 339 | public void onAnimationUpdate(ValueAnimator animation) { 340 | IndicatorPoint p = (IndicatorPoint) animation.getAnimatedValue(); 341 | mIndicatorRect.left = (int) p.left; 342 | mIndicatorRect.right = (int) p.right; 343 | invalidate(); 344 | } 345 | 346 | private boolean mIsFirstDraw = true; 347 | 348 | @Override 349 | protected void onDraw(Canvas canvas) { 350 | super.onDraw(canvas); 351 | 352 | if (isInEditMode() || mTabCount <= 0) { 353 | return; 354 | } 355 | 356 | int height = getHeight(); 357 | int paddingLeft = getPaddingLeft(); 358 | 359 | if (mIndicatorHeight < 0) { 360 | mIndicatorHeight = height - mIndicatorMarginTop - mIndicatorMarginBottom; 361 | } 362 | 363 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) { 364 | mIndicatorCornerRadius = mIndicatorHeight / 2; 365 | } 366 | 367 | //draw rect 368 | mRectDrawable.setColor(mBarColor); 369 | mRectDrawable.setStroke((int) mBarStrokeWidth, mBarStrokeColor); 370 | mRectDrawable.setCornerRadius(mIndicatorCornerRadius); 371 | mRectDrawable.setBounds(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); 372 | mRectDrawable.draw(canvas); 373 | 374 | // draw divider 375 | if (!mIndicatorAnimEnable && mDividerWidth > 0) { 376 | mDividerPaint.setStrokeWidth(mDividerWidth); 377 | mDividerPaint.setColor(mDividerColor); 378 | for (int i = 0; i < mTabCount - 1; i++) { 379 | View tab = mTabsContainer.getChildAt(i); 380 | canvas.drawLine(paddingLeft + tab.getRight(), mDividerPadding, paddingLeft + tab.getRight(), height - mDividerPadding, mDividerPaint); 381 | } 382 | } 383 | 384 | 385 | //draw indicator line 386 | if (mIndicatorAnimEnable) { 387 | if (mIsFirstDraw) { 388 | mIsFirstDraw = false; 389 | calcIndicatorRect(); 390 | } 391 | } else { 392 | calcIndicatorRect(); 393 | } 394 | 395 | mIndicatorDrawable.setColor(mIndicatorColor); 396 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 397 | (int) mIndicatorMarginTop, (int) (paddingLeft + mIndicatorRect.right - mIndicatorMarginRight), 398 | (int) (mIndicatorMarginTop + mIndicatorHeight)); 399 | mIndicatorDrawable.setCornerRadii(mRadiusArr); 400 | mIndicatorDrawable.draw(canvas); 401 | 402 | } 403 | 404 | //setter and getter 405 | public void setCurrentTab(int currentTab) { 406 | mLastTab = this.mCurrentTab; 407 | this.mCurrentTab = currentTab; 408 | updateTabSelection(currentTab); 409 | if (mFragmentChangeManager != null) { 410 | mFragmentChangeManager.setFragments(currentTab); 411 | } 412 | if (mIndicatorAnimEnable) { 413 | calcOffset(); 414 | } else { 415 | invalidate(); 416 | } 417 | } 418 | 419 | public void setTabPadding(float tabPadding) { 420 | this.mTabPadding = dp2px(tabPadding); 421 | updateTabStyles(); 422 | } 423 | 424 | public void setTabSpaceEqual(boolean tabSpaceEqual) { 425 | this.mTabSpaceEqual = tabSpaceEqual; 426 | updateTabStyles(); 427 | } 428 | 429 | public void setTabWidth(float tabWidth) { 430 | this.mTabWidth = dp2px(tabWidth); 431 | updateTabStyles(); 432 | } 433 | 434 | public void setIndicatorColor(int indicatorColor) { 435 | this.mIndicatorColor = indicatorColor; 436 | invalidate(); 437 | } 438 | 439 | public void setIndicatorHeight(float indicatorHeight) { 440 | this.mIndicatorHeight = dp2px(indicatorHeight); 441 | invalidate(); 442 | } 443 | 444 | public void setIndicatorCornerRadius(float indicatorCornerRadius) { 445 | this.mIndicatorCornerRadius = dp2px(indicatorCornerRadius); 446 | invalidate(); 447 | } 448 | 449 | public void setIndicatorMargin(float indicatorMarginLeft, float indicatorMarginTop, 450 | float indicatorMarginRight, float indicatorMarginBottom) { 451 | this.mIndicatorMarginLeft = dp2px(indicatorMarginLeft); 452 | this.mIndicatorMarginTop = dp2px(indicatorMarginTop); 453 | this.mIndicatorMarginRight = dp2px(indicatorMarginRight); 454 | this.mIndicatorMarginBottom = dp2px(indicatorMarginBottom); 455 | invalidate(); 456 | } 457 | 458 | public void setIndicatorAnimDuration(long indicatorAnimDuration) { 459 | this.mIndicatorAnimDuration = indicatorAnimDuration; 460 | } 461 | 462 | public void setIndicatorAnimEnable(boolean indicatorAnimEnable) { 463 | this.mIndicatorAnimEnable = indicatorAnimEnable; 464 | } 465 | 466 | public void setIndicatorBounceEnable(boolean indicatorBounceEnable) { 467 | this.mIndicatorBounceEnable = indicatorBounceEnable; 468 | } 469 | 470 | public void setDividerColor(int dividerColor) { 471 | this.mDividerColor = dividerColor; 472 | invalidate(); 473 | } 474 | 475 | public void setDividerWidth(float dividerWidth) { 476 | this.mDividerWidth = dp2px(dividerWidth); 477 | invalidate(); 478 | } 479 | 480 | public void setDividerPadding(float dividerPadding) { 481 | this.mDividerPadding = dp2px(dividerPadding); 482 | invalidate(); 483 | } 484 | 485 | public void setTextsize(float textsize) { 486 | this.mTextsize = sp2px(textsize); 487 | updateTabStyles(); 488 | } 489 | 490 | public void setTextSelectColor(int textSelectColor) { 491 | this.mTextSelectColor = textSelectColor; 492 | updateTabStyles(); 493 | } 494 | 495 | public void setTextUnselectColor(int textUnselectColor) { 496 | this.mTextUnselectColor = textUnselectColor; 497 | updateTabStyles(); 498 | } 499 | 500 | public void setTextBold(int textBold) { 501 | this.mTextBold = textBold; 502 | updateTabStyles(); 503 | } 504 | 505 | public void setTextAllCaps(boolean textAllCaps) { 506 | this.mTextAllCaps = textAllCaps; 507 | updateTabStyles(); 508 | } 509 | 510 | public int getTabCount() { 511 | return mTabCount; 512 | } 513 | 514 | public int getCurrentTab() { 515 | return mCurrentTab; 516 | } 517 | 518 | public float getTabPadding() { 519 | return mTabPadding; 520 | } 521 | 522 | public boolean isTabSpaceEqual() { 523 | return mTabSpaceEqual; 524 | } 525 | 526 | public float getTabWidth() { 527 | return mTabWidth; 528 | } 529 | 530 | public int getIndicatorColor() { 531 | return mIndicatorColor; 532 | } 533 | 534 | public float getIndicatorHeight() { 535 | return mIndicatorHeight; 536 | } 537 | 538 | public float getIndicatorCornerRadius() { 539 | return mIndicatorCornerRadius; 540 | } 541 | 542 | public float getIndicatorMarginLeft() { 543 | return mIndicatorMarginLeft; 544 | } 545 | 546 | public float getIndicatorMarginTop() { 547 | return mIndicatorMarginTop; 548 | } 549 | 550 | public float getIndicatorMarginRight() { 551 | return mIndicatorMarginRight; 552 | } 553 | 554 | public float getIndicatorMarginBottom() { 555 | return mIndicatorMarginBottom; 556 | } 557 | 558 | public long getIndicatorAnimDuration() { 559 | return mIndicatorAnimDuration; 560 | } 561 | 562 | public boolean isIndicatorAnimEnable() { 563 | return mIndicatorAnimEnable; 564 | } 565 | 566 | public boolean isIndicatorBounceEnable() { 567 | return mIndicatorBounceEnable; 568 | } 569 | 570 | public int getDividerColor() { 571 | return mDividerColor; 572 | } 573 | 574 | public float getDividerWidth() { 575 | return mDividerWidth; 576 | } 577 | 578 | public float getDividerPadding() { 579 | return mDividerPadding; 580 | } 581 | 582 | public float getTextsize() { 583 | return mTextsize; 584 | } 585 | 586 | public int getTextSelectColor() { 587 | return mTextSelectColor; 588 | } 589 | 590 | public int getTextUnselectColor() { 591 | return mTextUnselectColor; 592 | } 593 | 594 | public int getTextBold() { 595 | return mTextBold; 596 | } 597 | 598 | public boolean isTextAllCaps() { 599 | return mTextAllCaps; 600 | } 601 | 602 | public TextView getTitleView(int tab) { 603 | View tabView = mTabsContainer.getChildAt(tab); 604 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 605 | return tv_tab_title; 606 | } 607 | 608 | //setter and getter 609 | // show MsgTipView 610 | private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 611 | private SparseArray mInitSetMap = new SparseArray<>(); 612 | 613 | /** 614 | * 显示未读消息 615 | * 616 | * @param position 显示tab位置 617 | * @param num num小于等于0显示红点,num大于0显示数字 618 | */ 619 | public void showMsg(int position, int num) { 620 | if (position >= mTabCount) { 621 | position = mTabCount - 1; 622 | } 623 | 624 | View tabView = mTabsContainer.getChildAt(position); 625 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 626 | if (tipView != null) { 627 | UnreadMsgUtils.show(tipView, num); 628 | 629 | if (mInitSetMap.get(position) != null && mInitSetMap.get(position)) { 630 | return; 631 | } 632 | 633 | setMsgMargin(position, 2, 2); 634 | 635 | mInitSetMap.put(position, true); 636 | } 637 | } 638 | 639 | /** 640 | * 显示未读红点 641 | * 642 | * @param position 显示tab位置 643 | */ 644 | public void showDot(int position) { 645 | if (position >= mTabCount) { 646 | position = mTabCount - 1; 647 | } 648 | showMsg(position, 0); 649 | } 650 | 651 | public void hideMsg(int position) { 652 | if (position >= mTabCount) { 653 | position = mTabCount - 1; 654 | } 655 | 656 | View tabView = mTabsContainer.getChildAt(position); 657 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 658 | if (tipView != null) { 659 | tipView.setVisibility(View.GONE); 660 | } 661 | } 662 | 663 | /** 664 | * 设置提示红点偏移,注意 665 | * 1.控件为固定高度:参照点为tab内容的右上角 666 | * 2.控件高度不固定(WRAP_CONTENT):参照点为tab内容的右上角,此时高度已是红点的最高显示范围,所以这时bottomPadding其实就是topPadding 667 | */ 668 | public void setMsgMargin(int position, float leftPadding, float bottomPadding) { 669 | if (position >= mTabCount) { 670 | position = mTabCount - 1; 671 | } 672 | View tabView = mTabsContainer.getChildAt(position); 673 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 674 | if (tipView != null) { 675 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 676 | mTextPaint.setTextSize(mTextsize); 677 | float textWidth = mTextPaint.measureText(tv_tab_title.getText().toString()); 678 | float textHeight = mTextPaint.descent() - mTextPaint.ascent(); 679 | MarginLayoutParams lp = (MarginLayoutParams) tipView.getLayoutParams(); 680 | 681 | lp.leftMargin = dp2px(leftPadding); 682 | lp.topMargin = mHeight > 0 ? (int) (mHeight - textHeight) / 2 - dp2px(bottomPadding) : dp2px(bottomPadding); 683 | 684 | tipView.setLayoutParams(lp); 685 | } 686 | } 687 | 688 | /** 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置 */ 689 | public MsgView getMsgView(int position) { 690 | if (position >= mTabCount) { 691 | position = mTabCount - 1; 692 | } 693 | View tabView = mTabsContainer.getChildAt(position); 694 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 695 | return tipView; 696 | } 697 | 698 | private OnTabSelectListener mListener; 699 | 700 | public void setOnTabSelectListener(OnTabSelectListener listener) { 701 | this.mListener = listener; 702 | } 703 | 704 | @Override 705 | protected Parcelable onSaveInstanceState() { 706 | Bundle bundle = new Bundle(); 707 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 708 | bundle.putInt("mCurrentTab", mCurrentTab); 709 | return bundle; 710 | } 711 | 712 | @Override 713 | protected void onRestoreInstanceState(Parcelable state) { 714 | if (state instanceof Bundle) { 715 | Bundle bundle = (Bundle) state; 716 | mCurrentTab = bundle.getInt("mCurrentTab"); 717 | state = bundle.getParcelable("instanceState"); 718 | if (mCurrentTab != 0 && mTabsContainer.getChildCount() > 0) { 719 | updateTabSelection(mCurrentTab); 720 | } 721 | } 722 | super.onRestoreInstanceState(state); 723 | } 724 | 725 | class IndicatorPoint { 726 | public float left; 727 | public float right; 728 | } 729 | 730 | private IndicatorPoint mCurrentP = new IndicatorPoint(); 731 | private IndicatorPoint mLastP = new IndicatorPoint(); 732 | 733 | class PointEvaluator implements TypeEvaluator { 734 | @Override 735 | public IndicatorPoint evaluate(float fraction, IndicatorPoint startValue, IndicatorPoint endValue) { 736 | float left = startValue.left + fraction * (endValue.left - startValue.left); 737 | float right = startValue.right + fraction * (endValue.right - startValue.right); 738 | IndicatorPoint point = new IndicatorPoint(); 739 | point.left = left; 740 | point.right = right; 741 | return point; 742 | } 743 | } 744 | 745 | protected int dp2px(float dp) { 746 | final float scale = mContext.getResources().getDisplayMetrics().density; 747 | return (int) (dp * scale + 0.5f); 748 | } 749 | 750 | protected int sp2px(float sp) { 751 | final float scale = this.mContext.getResources().getDisplayMetrics().scaledDensity; 752 | return (int) (sp * scale + 0.5f); 753 | } 754 | } 755 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/java/com/flyco/tablayout/SlidingTabLayout.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.Rect; 10 | import android.graphics.drawable.GradientDrawable; 11 | import android.os.Bundle; 12 | import android.os.Parcelable; 13 | import android.util.AttributeSet; 14 | import android.util.SparseArray; 15 | import android.util.TypedValue; 16 | import android.view.Gravity; 17 | import android.view.View; 18 | import android.view.ViewGroup; 19 | import android.widget.HorizontalScrollView; 20 | import android.widget.LinearLayout; 21 | import android.widget.TextView; 22 | 23 | import androidx.fragment.app.Fragment; 24 | import androidx.fragment.app.FragmentActivity; 25 | import androidx.fragment.app.FragmentManager; 26 | import androidx.fragment.app.FragmentPagerAdapter; 27 | import androidx.viewpager.widget.PagerAdapter; 28 | import androidx.viewpager.widget.ViewPager; 29 | 30 | import com.flyco.tablayout.listener.OnTabSelectListener; 31 | import com.flyco.tablayout.utils.UnreadMsgUtils; 32 | import com.flyco.tablayout.widget.MsgView; 33 | 34 | import java.util.ArrayList; 35 | import java.util.Collections; 36 | 37 | /** 滑动TabLayout,对于ViewPager的依赖性强 */ 38 | public class SlidingTabLayout extends HorizontalScrollView implements ViewPager.OnPageChangeListener { 39 | private Context mContext; 40 | private ViewPager mViewPager; 41 | private ArrayList mTitles; 42 | private LinearLayout mTabsContainer; 43 | private int mCurrentTab; 44 | private float mCurrentPositionOffset; 45 | private int mTabCount; 46 | /** 用于绘制显示器 */ 47 | private Rect mIndicatorRect = new Rect(); 48 | /** 用于实现滚动居中 */ 49 | private Rect mTabRect = new Rect(); 50 | private GradientDrawable mIndicatorDrawable = new GradientDrawable(); 51 | 52 | private Paint mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 53 | private Paint mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 54 | private Paint mTrianglePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 55 | private Path mTrianglePath = new Path(); 56 | private static final int STYLE_NORMAL = 0; 57 | private static final int STYLE_TRIANGLE = 1; 58 | private static final int STYLE_BLOCK = 2; 59 | private int mIndicatorStyle = STYLE_NORMAL; 60 | 61 | private float mTabPadding; 62 | private boolean mTabSpaceEqual; 63 | private float mTabWidth; 64 | 65 | /** indicator */ 66 | private int mIndicatorColor; 67 | private float mIndicatorHeight; 68 | private float mIndicatorWidth; 69 | private float mIndicatorCornerRadius; 70 | private float mIndicatorMarginLeft; 71 | private float mIndicatorMarginTop; 72 | private float mIndicatorMarginRight; 73 | private float mIndicatorMarginBottom; 74 | private int mIndicatorGravity; 75 | private boolean mIndicatorWidthEqualTitle; 76 | 77 | /** underline */ 78 | private int mUnderlineColor; 79 | private float mUnderlineHeight; 80 | private int mUnderlineGravity; 81 | 82 | /** divider */ 83 | private int mDividerColor; 84 | private float mDividerWidth; 85 | private float mDividerPadding; 86 | 87 | /** title */ 88 | private static final int TEXT_BOLD_NONE = 0; 89 | private static final int TEXT_BOLD_WHEN_SELECT = 1; 90 | private static final int TEXT_BOLD_BOTH = 2; 91 | private float mTextsize; 92 | private int mTextSelectColor; 93 | private int mTextUnselectColor; 94 | private int mTextBold; 95 | private boolean mTextAllCaps; 96 | 97 | private int mLastScrollX; 98 | private int mHeight; 99 | private boolean mSnapOnTabClick; 100 | 101 | public SlidingTabLayout(Context context) { 102 | this(context, null, 0); 103 | } 104 | 105 | public SlidingTabLayout(Context context, AttributeSet attrs) { 106 | this(context, attrs, 0); 107 | } 108 | 109 | public SlidingTabLayout(Context context, AttributeSet attrs, int defStyleAttr) { 110 | super(context, attrs, defStyleAttr); 111 | setFillViewport(true);//设置滚动视图是否可以伸缩其内容以填充视口 112 | setWillNotDraw(false);//重写onDraw方法,需要调用这个方法来清除flag 113 | setClipChildren(false); 114 | setClipToPadding(false); 115 | 116 | this.mContext = context; 117 | mTabsContainer = new LinearLayout(context); 118 | addView(mTabsContainer); 119 | 120 | obtainAttributes(context, attrs); 121 | 122 | //get layout_height 123 | String height = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "layout_height"); 124 | 125 | if (height.equals(ViewGroup.LayoutParams.MATCH_PARENT + "")) { 126 | } else if (height.equals(ViewGroup.LayoutParams.WRAP_CONTENT + "")) { 127 | } else { 128 | int[] systemAttrs = {android.R.attr.layout_height}; 129 | TypedArray a = context.obtainStyledAttributes(attrs, systemAttrs); 130 | mHeight = a.getDimensionPixelSize(0, ViewGroup.LayoutParams.WRAP_CONTENT); 131 | a.recycle(); 132 | } 133 | } 134 | 135 | private void obtainAttributes(Context context, AttributeSet attrs) { 136 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SlidingTabLayout); 137 | 138 | mIndicatorStyle = ta.getInt(R.styleable.SlidingTabLayout_tl_indicator_style, STYLE_NORMAL); 139 | mIndicatorColor = ta.getColor(R.styleable.SlidingTabLayout_tl_indicator_color, Color.parseColor(mIndicatorStyle == STYLE_BLOCK ? "#4B6A87" : "#ffffff")); 140 | mIndicatorHeight = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_height, 141 | dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 4 : (mIndicatorStyle == STYLE_BLOCK ? -1 : 2))); 142 | mIndicatorWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_width, dp2px(mIndicatorStyle == STYLE_TRIANGLE ? 10 : -1)); 143 | mIndicatorCornerRadius = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_corner_radius, dp2px(mIndicatorStyle == STYLE_BLOCK ? -1 : 0)); 144 | mIndicatorMarginLeft = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_left, dp2px(0)); 145 | mIndicatorMarginTop = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_top, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0)); 146 | mIndicatorMarginRight = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_right, dp2px(0)); 147 | mIndicatorMarginBottom = ta.getDimension(R.styleable.SlidingTabLayout_tl_indicator_margin_bottom, dp2px(mIndicatorStyle == STYLE_BLOCK ? 7 : 0)); 148 | mIndicatorGravity = ta.getInt(R.styleable.SlidingTabLayout_tl_indicator_gravity, Gravity.BOTTOM); 149 | mIndicatorWidthEqualTitle = ta.getBoolean(R.styleable.SlidingTabLayout_tl_indicator_width_equal_title, false); 150 | 151 | mUnderlineColor = ta.getColor(R.styleable.SlidingTabLayout_tl_underline_color, Color.parseColor("#ffffff")); 152 | mUnderlineHeight = ta.getDimension(R.styleable.SlidingTabLayout_tl_underline_height, dp2px(0)); 153 | mUnderlineGravity = ta.getInt(R.styleable.SlidingTabLayout_tl_underline_gravity, Gravity.BOTTOM); 154 | 155 | mDividerColor = ta.getColor(R.styleable.SlidingTabLayout_tl_divider_color, Color.parseColor("#ffffff")); 156 | mDividerWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_divider_width, dp2px(0)); 157 | mDividerPadding = ta.getDimension(R.styleable.SlidingTabLayout_tl_divider_padding, dp2px(12)); 158 | 159 | mTextsize = ta.getDimension(R.styleable.SlidingTabLayout_tl_textsize, sp2px(14)); 160 | mTextSelectColor = ta.getColor(R.styleable.SlidingTabLayout_tl_textSelectColor, Color.parseColor("#ffffff")); 161 | mTextUnselectColor = ta.getColor(R.styleable.SlidingTabLayout_tl_textUnselectColor, Color.parseColor("#AAffffff")); 162 | mTextBold = ta.getInt(R.styleable.SlidingTabLayout_tl_textBold, TEXT_BOLD_NONE); 163 | mTextAllCaps = ta.getBoolean(R.styleable.SlidingTabLayout_tl_textAllCaps, false); 164 | 165 | mTabSpaceEqual = ta.getBoolean(R.styleable.SlidingTabLayout_tl_tab_space_equal, false); 166 | mTabWidth = ta.getDimension(R.styleable.SlidingTabLayout_tl_tab_width, dp2px(-1)); 167 | mTabPadding = ta.getDimension(R.styleable.SlidingTabLayout_tl_tab_padding, mTabSpaceEqual || mTabWidth > 0 ? dp2px(0) : dp2px(20)); 168 | 169 | ta.recycle(); 170 | } 171 | 172 | /** 关联ViewPager */ 173 | public void setViewPager(ViewPager vp) { 174 | if (vp == null || vp.getAdapter() == null) { 175 | throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !"); 176 | } 177 | 178 | this.mViewPager = vp; 179 | 180 | this.mViewPager.removeOnPageChangeListener(this); 181 | this.mViewPager.addOnPageChangeListener(this); 182 | notifyDataSetChanged(); 183 | } 184 | 185 | /** 关联ViewPager,用于不想在ViewPager适配器中设置titles数据的情况 */ 186 | public void setViewPager(ViewPager vp, String[] titles) { 187 | if (vp == null || vp.getAdapter() == null) { 188 | throw new IllegalStateException("ViewPager or ViewPager adapter can not be NULL !"); 189 | } 190 | 191 | if (titles == null || titles.length == 0) { 192 | throw new IllegalStateException("Titles can not be EMPTY !"); 193 | } 194 | 195 | if (titles.length != vp.getAdapter().getCount()) { 196 | throw new IllegalStateException("Titles length must be the same as the page count !"); 197 | } 198 | 199 | this.mViewPager = vp; 200 | mTitles = new ArrayList<>(); 201 | Collections.addAll(mTitles, titles); 202 | 203 | this.mViewPager.removeOnPageChangeListener(this); 204 | this.mViewPager.addOnPageChangeListener(this); 205 | notifyDataSetChanged(); 206 | } 207 | 208 | /** 关联ViewPager,用于连适配器都不想自己实例化的情况 */ 209 | public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList fragments) { 210 | if (vp == null) { 211 | throw new IllegalStateException("ViewPager can not be NULL !"); 212 | } 213 | 214 | if (titles == null || titles.length == 0) { 215 | throw new IllegalStateException("Titles can not be EMPTY !"); 216 | } 217 | 218 | this.mViewPager = vp; 219 | this.mViewPager.setAdapter(new InnerPagerAdapter(fa.getSupportFragmentManager(), fragments, titles)); 220 | 221 | this.mViewPager.removeOnPageChangeListener(this); 222 | this.mViewPager.addOnPageChangeListener(this); 223 | notifyDataSetChanged(); 224 | } 225 | 226 | /** 更新数据 */ 227 | public void notifyDataSetChanged() { 228 | mTabsContainer.removeAllViews(); 229 | this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size(); 230 | View tabView; 231 | for (int i = 0; i < mTabCount; i++) { 232 | tabView = View.inflate(mContext, R.layout.layout_tab, null); 233 | CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(i) : mTitles.get(i); 234 | addTab(i, pageTitle.toString(), tabView); 235 | } 236 | 237 | updateTabStyles(); 238 | } 239 | 240 | public void addNewTab(String title) { 241 | View tabView = View.inflate(mContext, R.layout.layout_tab, null); 242 | if (mTitles != null) { 243 | mTitles.add(title); 244 | } 245 | 246 | CharSequence pageTitle = mTitles == null ? mViewPager.getAdapter().getPageTitle(mTabCount) : mTitles.get(mTabCount); 247 | addTab(mTabCount, pageTitle.toString(), tabView); 248 | this.mTabCount = mTitles == null ? mViewPager.getAdapter().getCount() : mTitles.size(); 249 | 250 | updateTabStyles(); 251 | } 252 | 253 | /** 创建并添加tab */ 254 | private void addTab(final int position, String title, View tabView) { 255 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 256 | if (tv_tab_title != null) { 257 | if (title != null) tv_tab_title.setText(title); 258 | } 259 | 260 | tabView.setOnClickListener(new OnClickListener() { 261 | @Override 262 | public void onClick(View v) { 263 | int position = mTabsContainer.indexOfChild(v); 264 | if (position != -1) { 265 | if (mViewPager.getCurrentItem() != position) { 266 | if (mSnapOnTabClick) { 267 | mViewPager.setCurrentItem(position, false); 268 | } else { 269 | mViewPager.setCurrentItem(position); 270 | } 271 | 272 | if (mListener != null) { 273 | mListener.onTabSelect(position); 274 | } 275 | } else { 276 | if (mListener != null) { 277 | mListener.onTabReselect(position); 278 | } 279 | } 280 | } 281 | } 282 | }); 283 | 284 | /** 每一个Tab的布局参数 */ 285 | LinearLayout.LayoutParams lp_tab = mTabSpaceEqual ? 286 | new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) : 287 | new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); 288 | if (mTabWidth > 0) { 289 | lp_tab = new LinearLayout.LayoutParams((int) mTabWidth, LayoutParams.MATCH_PARENT); 290 | } 291 | 292 | mTabsContainer.addView(tabView, position, lp_tab); 293 | } 294 | 295 | private void updateTabStyles() { 296 | for (int i = 0; i < mTabCount; i++) { 297 | View v = mTabsContainer.getChildAt(i); 298 | // v.setPadding((int) mTabPadding, v.getPaddingTop(), (int) mTabPadding, v.getPaddingBottom()); 299 | TextView tv_tab_title = (TextView) v.findViewById(R.id.tv_tab_title); 300 | if (tv_tab_title != null) { 301 | tv_tab_title.setTextColor(i == mCurrentTab ? mTextSelectColor : mTextUnselectColor); 302 | tv_tab_title.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextsize); 303 | tv_tab_title.setPadding((int) mTabPadding, 0, (int) mTabPadding, 0); 304 | if (mTextAllCaps) { 305 | tv_tab_title.setText(tv_tab_title.getText().toString().toUpperCase()); 306 | } 307 | 308 | if (mTextBold == TEXT_BOLD_BOTH) { 309 | tv_tab_title.getPaint().setFakeBoldText(true); 310 | } else if (mTextBold == TEXT_BOLD_NONE) { 311 | tv_tab_title.getPaint().setFakeBoldText(false); 312 | } 313 | } 314 | } 315 | } 316 | 317 | @Override 318 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 319 | /** 320 | * position:当前View的位置 321 | * mCurrentPositionOffset:当前View的偏移量比例.[0,1) 322 | */ 323 | this.mCurrentTab = position; 324 | this.mCurrentPositionOffset = positionOffset; 325 | scrollToCurrentTab(); 326 | invalidate(); 327 | } 328 | 329 | @Override 330 | public void onPageSelected(int position) { 331 | updateTabSelection(position); 332 | } 333 | 334 | @Override 335 | public void onPageScrollStateChanged(int state) { 336 | } 337 | 338 | /** HorizontalScrollView滚到当前tab,并且居中显示 */ 339 | private void scrollToCurrentTab() { 340 | if (mTabCount <= 0) { 341 | return; 342 | } 343 | 344 | int offset = (int) (mCurrentPositionOffset * mTabsContainer.getChildAt(mCurrentTab).getWidth()); 345 | /**当前Tab的left+当前Tab的Width乘以positionOffset*/ 346 | int newScrollX = mTabsContainer.getChildAt(mCurrentTab).getLeft() + offset; 347 | 348 | if (mCurrentTab > 0 || offset > 0) { 349 | /**HorizontalScrollView移动到当前tab,并居中*/ 350 | newScrollX -= getWidth() / 2 - getPaddingLeft(); 351 | calcIndicatorRect(); 352 | newScrollX += ((mTabRect.right - mTabRect.left) / 2); 353 | } 354 | 355 | if (newScrollX != mLastScrollX) { 356 | mLastScrollX = newScrollX; 357 | /** scrollTo(int x,int y):x,y代表的不是坐标点,而是偏移量 358 | * x:表示离起始位置的x水平方向的偏移量 359 | * y:表示离起始位置的y垂直方向的偏移量 360 | */ 361 | scrollTo(newScrollX, 0); 362 | } 363 | } 364 | 365 | private void updateTabSelection(int position) { 366 | for (int i = 0; i < mTabCount; ++i) { 367 | View tabView = mTabsContainer.getChildAt(i); 368 | final boolean isSelect = i == position; 369 | TextView tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 370 | 371 | if (tab_title != null) { 372 | tab_title.setTextColor(isSelect ? mTextSelectColor : mTextUnselectColor); 373 | if (mTextBold == TEXT_BOLD_WHEN_SELECT) { 374 | tab_title.getPaint().setFakeBoldText(isSelect); 375 | } 376 | } 377 | } 378 | } 379 | 380 | private float margin; 381 | 382 | private void calcIndicatorRect() { 383 | View currentTabView = mTabsContainer.getChildAt(this.mCurrentTab); 384 | float left = currentTabView.getLeft(); 385 | float right = currentTabView.getRight(); 386 | 387 | //for mIndicatorWidthEqualTitle 388 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 389 | TextView tab_title = (TextView) currentTabView.findViewById(R.id.tv_tab_title); 390 | mTextPaint.setTextSize(mTextsize); 391 | float textWidth = mTextPaint.measureText(tab_title.getText().toString()); 392 | margin = (right - left - textWidth) / 2; 393 | } 394 | 395 | if (this.mCurrentTab < mTabCount - 1) { 396 | View nextTabView = mTabsContainer.getChildAt(this.mCurrentTab + 1); 397 | float nextTabLeft = nextTabView.getLeft(); 398 | float nextTabRight = nextTabView.getRight(); 399 | 400 | left = left + mCurrentPositionOffset * (nextTabLeft - left); 401 | right = right + mCurrentPositionOffset * (nextTabRight - right); 402 | 403 | //for mIndicatorWidthEqualTitle 404 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 405 | TextView next_tab_title = (TextView) nextTabView.findViewById(R.id.tv_tab_title); 406 | mTextPaint.setTextSize(mTextsize); 407 | float nextTextWidth = mTextPaint.measureText(next_tab_title.getText().toString()); 408 | float nextMargin = (nextTabRight - nextTabLeft - nextTextWidth) / 2; 409 | margin = margin + mCurrentPositionOffset * (nextMargin - margin); 410 | } 411 | } 412 | 413 | mIndicatorRect.left = (int) left; 414 | mIndicatorRect.right = (int) right; 415 | //for mIndicatorWidthEqualTitle 416 | if (mIndicatorStyle == STYLE_NORMAL && mIndicatorWidthEqualTitle) { 417 | mIndicatorRect.left = (int) (left + margin - 1); 418 | mIndicatorRect.right = (int) (right - margin - 1); 419 | } 420 | 421 | mTabRect.left = (int) left; 422 | mTabRect.right = (int) right; 423 | 424 | if (mIndicatorWidth < 0) { //indicatorWidth小于0时,原jpardogo's PagerSlidingTabStrip 425 | 426 | } else {//indicatorWidth大于0时,圆角矩形以及三角形 427 | float indicatorLeft = currentTabView.getLeft() + (currentTabView.getWidth() - mIndicatorWidth) / 2; 428 | 429 | if (this.mCurrentTab < mTabCount - 1) { 430 | View nextTab = mTabsContainer.getChildAt(this.mCurrentTab + 1); 431 | indicatorLeft = indicatorLeft + mCurrentPositionOffset * (currentTabView.getWidth() / 2 + nextTab.getWidth() / 2); 432 | } 433 | 434 | mIndicatorRect.left = (int) indicatorLeft; 435 | mIndicatorRect.right = (int) (mIndicatorRect.left + mIndicatorWidth); 436 | } 437 | } 438 | 439 | @Override 440 | protected void onDraw(Canvas canvas) { 441 | super.onDraw(canvas); 442 | 443 | if (isInEditMode() || mTabCount <= 0) { 444 | return; 445 | } 446 | 447 | int height = getHeight(); 448 | int paddingLeft = getPaddingLeft(); 449 | // draw divider 450 | if (mDividerWidth > 0) { 451 | mDividerPaint.setStrokeWidth(mDividerWidth); 452 | mDividerPaint.setColor(mDividerColor); 453 | for (int i = 0; i < mTabCount - 1; i++) { 454 | View tab = mTabsContainer.getChildAt(i); 455 | canvas.drawLine(paddingLeft + tab.getRight(), mDividerPadding, paddingLeft + tab.getRight(), height - mDividerPadding, mDividerPaint); 456 | } 457 | } 458 | 459 | // draw underline 460 | if (mUnderlineHeight > 0) { 461 | mRectPaint.setColor(mUnderlineColor); 462 | if (mUnderlineGravity == Gravity.BOTTOM) { 463 | canvas.drawRect(paddingLeft, height - mUnderlineHeight, mTabsContainer.getWidth() + paddingLeft, height, mRectPaint); 464 | } else { 465 | canvas.drawRect(paddingLeft, 0, mTabsContainer.getWidth() + paddingLeft, mUnderlineHeight, mRectPaint); 466 | } 467 | } 468 | 469 | //draw indicator line 470 | 471 | calcIndicatorRect(); 472 | if (mIndicatorStyle == STYLE_TRIANGLE) { 473 | if (mIndicatorHeight > 0) { 474 | mTrianglePaint.setColor(mIndicatorColor); 475 | mTrianglePath.reset(); 476 | mTrianglePath.moveTo(paddingLeft + mIndicatorRect.left, height); 477 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.left / 2 + mIndicatorRect.right / 2, height - mIndicatorHeight); 478 | mTrianglePath.lineTo(paddingLeft + mIndicatorRect.right, height); 479 | mTrianglePath.close(); 480 | canvas.drawPath(mTrianglePath, mTrianglePaint); 481 | } 482 | } else if (mIndicatorStyle == STYLE_BLOCK) { 483 | if (mIndicatorHeight < 0) { 484 | mIndicatorHeight = height - mIndicatorMarginTop - mIndicatorMarginBottom; 485 | } else { 486 | 487 | } 488 | 489 | if (mIndicatorHeight > 0) { 490 | if (mIndicatorCornerRadius < 0 || mIndicatorCornerRadius > mIndicatorHeight / 2) { 491 | mIndicatorCornerRadius = mIndicatorHeight / 2; 492 | } 493 | 494 | mIndicatorDrawable.setColor(mIndicatorColor); 495 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 496 | (int) mIndicatorMarginTop, (int) (paddingLeft + mIndicatorRect.right - mIndicatorMarginRight), 497 | (int) (mIndicatorMarginTop + mIndicatorHeight)); 498 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius); 499 | mIndicatorDrawable.draw(canvas); 500 | } 501 | } else { 502 | /* mRectPaint.setColor(mIndicatorColor); 503 | calcIndicatorRect(); 504 | canvas.drawRect(getPaddingLeft() + mIndicatorRect.left, getHeight() - mIndicatorHeight, 505 | mIndicatorRect.right + getPaddingLeft(), getHeight(), mRectPaint);*/ 506 | 507 | if (mIndicatorHeight > 0) { 508 | mIndicatorDrawable.setColor(mIndicatorColor); 509 | 510 | if (mIndicatorGravity == Gravity.BOTTOM) { 511 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 512 | height - (int) mIndicatorHeight - (int) mIndicatorMarginBottom, 513 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight, 514 | height - (int) mIndicatorMarginBottom); 515 | } else { 516 | mIndicatorDrawable.setBounds(paddingLeft + (int) mIndicatorMarginLeft + mIndicatorRect.left, 517 | (int) mIndicatorMarginTop, 518 | paddingLeft + mIndicatorRect.right - (int) mIndicatorMarginRight, 519 | (int) mIndicatorHeight + (int) mIndicatorMarginTop); 520 | } 521 | mIndicatorDrawable.setCornerRadius(mIndicatorCornerRadius); 522 | mIndicatorDrawable.draw(canvas); 523 | } 524 | } 525 | } 526 | 527 | //setter and getter 528 | public void setCurrentTab(int currentTab) { 529 | this.mCurrentTab = currentTab; 530 | mViewPager.setCurrentItem(currentTab); 531 | 532 | } 533 | 534 | public void setCurrentTab(int currentTab, boolean smoothScroll) { 535 | this.mCurrentTab = currentTab; 536 | mViewPager.setCurrentItem(currentTab, smoothScroll); 537 | } 538 | 539 | public void setIndicatorStyle(int indicatorStyle) { 540 | this.mIndicatorStyle = indicatorStyle; 541 | invalidate(); 542 | } 543 | 544 | public void setTabPadding(float tabPadding) { 545 | this.mTabPadding = dp2px(tabPadding); 546 | updateTabStyles(); 547 | } 548 | 549 | public void setTabSpaceEqual(boolean tabSpaceEqual) { 550 | this.mTabSpaceEqual = tabSpaceEqual; 551 | updateTabStyles(); 552 | } 553 | 554 | public void setTabWidth(float tabWidth) { 555 | this.mTabWidth = dp2px(tabWidth); 556 | updateTabStyles(); 557 | } 558 | 559 | public void setIndicatorColor(int indicatorColor) { 560 | this.mIndicatorColor = indicatorColor; 561 | invalidate(); 562 | } 563 | 564 | public void setIndicatorHeight(float indicatorHeight) { 565 | this.mIndicatorHeight = dp2px(indicatorHeight); 566 | invalidate(); 567 | } 568 | 569 | public void setIndicatorWidth(float indicatorWidth) { 570 | this.mIndicatorWidth = dp2px(indicatorWidth); 571 | invalidate(); 572 | } 573 | 574 | public void setIndicatorCornerRadius(float indicatorCornerRadius) { 575 | this.mIndicatorCornerRadius = dp2px(indicatorCornerRadius); 576 | invalidate(); 577 | } 578 | 579 | public void setIndicatorGravity(int indicatorGravity) { 580 | this.mIndicatorGravity = indicatorGravity; 581 | invalidate(); 582 | } 583 | 584 | public void setIndicatorMargin(float indicatorMarginLeft, float indicatorMarginTop, 585 | float indicatorMarginRight, float indicatorMarginBottom) { 586 | this.mIndicatorMarginLeft = dp2px(indicatorMarginLeft); 587 | this.mIndicatorMarginTop = dp2px(indicatorMarginTop); 588 | this.mIndicatorMarginRight = dp2px(indicatorMarginRight); 589 | this.mIndicatorMarginBottom = dp2px(indicatorMarginBottom); 590 | invalidate(); 591 | } 592 | 593 | public void setIndicatorWidthEqualTitle(boolean indicatorWidthEqualTitle) { 594 | this.mIndicatorWidthEqualTitle = indicatorWidthEqualTitle; 595 | invalidate(); 596 | } 597 | 598 | public void setUnderlineColor(int underlineColor) { 599 | this.mUnderlineColor = underlineColor; 600 | invalidate(); 601 | } 602 | 603 | public void setUnderlineHeight(float underlineHeight) { 604 | this.mUnderlineHeight = dp2px(underlineHeight); 605 | invalidate(); 606 | } 607 | 608 | public void setUnderlineGravity(int underlineGravity) { 609 | this.mUnderlineGravity = underlineGravity; 610 | invalidate(); 611 | } 612 | 613 | public void setDividerColor(int dividerColor) { 614 | this.mDividerColor = dividerColor; 615 | invalidate(); 616 | } 617 | 618 | public void setDividerWidth(float dividerWidth) { 619 | this.mDividerWidth = dp2px(dividerWidth); 620 | invalidate(); 621 | } 622 | 623 | public void setDividerPadding(float dividerPadding) { 624 | this.mDividerPadding = dp2px(dividerPadding); 625 | invalidate(); 626 | } 627 | 628 | public void setTextsize(float textsize) { 629 | this.mTextsize = sp2px(textsize); 630 | updateTabStyles(); 631 | } 632 | 633 | public void setTextSelectColor(int textSelectColor) { 634 | this.mTextSelectColor = textSelectColor; 635 | updateTabStyles(); 636 | } 637 | 638 | public void setTextUnselectColor(int textUnselectColor) { 639 | this.mTextUnselectColor = textUnselectColor; 640 | updateTabStyles(); 641 | } 642 | 643 | public void setTextBold(int textBold) { 644 | this.mTextBold = textBold; 645 | updateTabStyles(); 646 | } 647 | 648 | public void setTextAllCaps(boolean textAllCaps) { 649 | this.mTextAllCaps = textAllCaps; 650 | updateTabStyles(); 651 | } 652 | 653 | public void setSnapOnTabClick(boolean snapOnTabClick) { 654 | mSnapOnTabClick = snapOnTabClick; 655 | } 656 | 657 | 658 | public int getTabCount() { 659 | return mTabCount; 660 | } 661 | 662 | public int getCurrentTab() { 663 | return mCurrentTab; 664 | } 665 | 666 | public int getIndicatorStyle() { 667 | return mIndicatorStyle; 668 | } 669 | 670 | public float getTabPadding() { 671 | return mTabPadding; 672 | } 673 | 674 | public boolean isTabSpaceEqual() { 675 | return mTabSpaceEqual; 676 | } 677 | 678 | public float getTabWidth() { 679 | return mTabWidth; 680 | } 681 | 682 | public int getIndicatorColor() { 683 | return mIndicatorColor; 684 | } 685 | 686 | public float getIndicatorHeight() { 687 | return mIndicatorHeight; 688 | } 689 | 690 | public float getIndicatorWidth() { 691 | return mIndicatorWidth; 692 | } 693 | 694 | public float getIndicatorCornerRadius() { 695 | return mIndicatorCornerRadius; 696 | } 697 | 698 | public float getIndicatorMarginLeft() { 699 | return mIndicatorMarginLeft; 700 | } 701 | 702 | public float getIndicatorMarginTop() { 703 | return mIndicatorMarginTop; 704 | } 705 | 706 | public float getIndicatorMarginRight() { 707 | return mIndicatorMarginRight; 708 | } 709 | 710 | public float getIndicatorMarginBottom() { 711 | return mIndicatorMarginBottom; 712 | } 713 | 714 | public int getUnderlineColor() { 715 | return mUnderlineColor; 716 | } 717 | 718 | public float getUnderlineHeight() { 719 | return mUnderlineHeight; 720 | } 721 | 722 | public int getDividerColor() { 723 | return mDividerColor; 724 | } 725 | 726 | public float getDividerWidth() { 727 | return mDividerWidth; 728 | } 729 | 730 | public float getDividerPadding() { 731 | return mDividerPadding; 732 | } 733 | 734 | public float getTextsize() { 735 | return mTextsize; 736 | } 737 | 738 | public int getTextSelectColor() { 739 | return mTextSelectColor; 740 | } 741 | 742 | public int getTextUnselectColor() { 743 | return mTextUnselectColor; 744 | } 745 | 746 | public int getTextBold() { 747 | return mTextBold; 748 | } 749 | 750 | public boolean isTextAllCaps() { 751 | return mTextAllCaps; 752 | } 753 | 754 | public TextView getTitleView(int tab) { 755 | View tabView = mTabsContainer.getChildAt(tab); 756 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 757 | return tv_tab_title; 758 | } 759 | 760 | //setter and getter 761 | 762 | // show MsgTipView 763 | private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 764 | private SparseArray mInitSetMap = new SparseArray<>(); 765 | 766 | /** 767 | * 显示未读消息 768 | * 769 | * @param position 显示tab位置 770 | * @param num num小于等于0显示红点,num大于0显示数字 771 | */ 772 | public void showMsg(int position, int num) { 773 | if (position >= mTabCount) { 774 | position = mTabCount - 1; 775 | } 776 | 777 | View tabView = mTabsContainer.getChildAt(position); 778 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 779 | if (tipView != null) { 780 | UnreadMsgUtils.show(tipView, num); 781 | 782 | if (mInitSetMap.get(position) != null && mInitSetMap.get(position)) { 783 | return; 784 | } 785 | 786 | setMsgMargin(position, 4, 2); 787 | mInitSetMap.put(position, true); 788 | } 789 | } 790 | 791 | /** 792 | * 显示未读红点 793 | * 794 | * @param position 显示tab位置 795 | */ 796 | public void showDot(int position) { 797 | if (position >= mTabCount) { 798 | position = mTabCount - 1; 799 | } 800 | showMsg(position, 0); 801 | } 802 | 803 | /** 隐藏未读消息 */ 804 | public void hideMsg(int position) { 805 | if (position >= mTabCount) { 806 | position = mTabCount - 1; 807 | } 808 | 809 | View tabView = mTabsContainer.getChildAt(position); 810 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 811 | if (tipView != null) { 812 | tipView.setVisibility(View.GONE); 813 | } 814 | } 815 | 816 | /** 设置未读消息偏移,原点为文字的右上角.当控件高度固定,消息提示位置易控制,显示效果佳 */ 817 | public void setMsgMargin(int position, float leftPadding, float bottomPadding) { 818 | if (position >= mTabCount) { 819 | position = mTabCount - 1; 820 | } 821 | View tabView = mTabsContainer.getChildAt(position); 822 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 823 | if (tipView != null) { 824 | TextView tv_tab_title = (TextView) tabView.findViewById(R.id.tv_tab_title); 825 | mTextPaint.setTextSize(mTextsize); 826 | float textWidth = mTextPaint.measureText(tv_tab_title.getText().toString()); 827 | float textHeight = mTextPaint.descent() - mTextPaint.ascent(); 828 | MarginLayoutParams lp = (MarginLayoutParams) tipView.getLayoutParams(); 829 | lp.leftMargin = mTabWidth >= 0 ? (int) (mTabWidth / 2 + textWidth / 2 + dp2px(leftPadding)) : (int) (mTabPadding + textWidth + dp2px(leftPadding)); 830 | lp.topMargin = mHeight > 0 ? (int) (mHeight - textHeight) / 2 - dp2px(bottomPadding) : 0; 831 | tipView.setLayoutParams(lp); 832 | } 833 | } 834 | 835 | /** 当前类只提供了少许设置未读消息属性的方法,可以通过该方法获取MsgView对象从而各种设置 */ 836 | public MsgView getMsgView(int position) { 837 | if (position >= mTabCount) { 838 | position = mTabCount - 1; 839 | } 840 | View tabView = mTabsContainer.getChildAt(position); 841 | MsgView tipView = (MsgView) tabView.findViewById(R.id.rtv_msg_tip); 842 | return tipView; 843 | } 844 | 845 | private OnTabSelectListener mListener; 846 | 847 | public void setOnTabSelectListener(OnTabSelectListener listener) { 848 | this.mListener = listener; 849 | } 850 | 851 | class InnerPagerAdapter extends FragmentPagerAdapter { 852 | private ArrayList fragments = new ArrayList<>(); 853 | private String[] titles; 854 | 855 | public InnerPagerAdapter(FragmentManager fm, ArrayList fragments, String[] titles) { 856 | super(fm); 857 | this.fragments = fragments; 858 | this.titles = titles; 859 | } 860 | 861 | @Override 862 | public int getCount() { 863 | return fragments.size(); 864 | } 865 | 866 | @Override 867 | public CharSequence getPageTitle(int position) { 868 | return titles[position]; 869 | } 870 | 871 | @Override 872 | public Fragment getItem(int position) { 873 | return fragments.get(position); 874 | } 875 | 876 | @Override 877 | public void destroyItem(ViewGroup container, int position, Object object) { 878 | // 覆写destroyItem并且空实现,这样每个Fragment中的视图就不会被销毁 879 | // super.destroyItem(container, position, object); 880 | } 881 | 882 | @Override 883 | public int getItemPosition(Object object) { 884 | return PagerAdapter.POSITION_NONE; 885 | } 886 | } 887 | 888 | @Override 889 | protected Parcelable onSaveInstanceState() { 890 | Bundle bundle = new Bundle(); 891 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 892 | bundle.putInt("mCurrentTab", mCurrentTab); 893 | return bundle; 894 | } 895 | 896 | @Override 897 | protected void onRestoreInstanceState(Parcelable state) { 898 | if (state instanceof Bundle) { 899 | Bundle bundle = (Bundle) state; 900 | mCurrentTab = bundle.getInt("mCurrentTab"); 901 | state = bundle.getParcelable("instanceState"); 902 | if (mCurrentTab != 0 && mTabsContainer.getChildCount() > 0) { 903 | updateTabSelection(mCurrentTab); 904 | scrollToCurrentTab(); 905 | } 906 | } 907 | super.onRestoreInstanceState(state); 908 | } 909 | 910 | protected int dp2px(float dp) { 911 | final float scale = mContext.getResources().getDisplayMetrics().density; 912 | return (int) (dp * scale + 0.5f); 913 | } 914 | 915 | protected int sp2px(float sp) { 916 | final float scale = this.mContext.getResources().getDisplayMetrics().scaledDensity; 917 | return (int) (sp * scale + 0.5f); 918 | } 919 | } 920 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/java/com/flyco/tablayout/listener/CustomTabEntity.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.listener; 2 | 3 | import androidx.annotation.DrawableRes; 4 | 5 | public interface CustomTabEntity { 6 | String getTabTitle(); 7 | 8 | @DrawableRes 9 | int getTabSelectedIcon(); 10 | 11 | @DrawableRes 12 | int getTabUnselectedIcon(); 13 | } -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/java/com/flyco/tablayout/listener/OnTabSelectListener.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.listener; 2 | 3 | public interface OnTabSelectListener { 4 | void onTabSelect(int position); 5 | void onTabReselect(int position); 6 | } -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/java/com/flyco/tablayout/utils/FragmentChangeManager.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.utils; 2 | 3 | import androidx.fragment.app.Fragment; 4 | import androidx.fragment.app.FragmentManager; 5 | import androidx.fragment.app.FragmentTransaction; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class FragmentChangeManager { 10 | private FragmentManager mFragmentManager; 11 | private int mContainerViewId; 12 | /** Fragment切换数组 */ 13 | private ArrayList mFragments; 14 | /** 当前选中的Tab */ 15 | private int mCurrentTab; 16 | 17 | public FragmentChangeManager(FragmentManager fm, int containerViewId, ArrayList fragments) { 18 | this.mFragmentManager = fm; 19 | this.mContainerViewId = containerViewId; 20 | this.mFragments = fragments; 21 | initFragments(); 22 | } 23 | 24 | /** 初始化fragments */ 25 | private void initFragments() { 26 | for (Fragment fragment : mFragments) { 27 | mFragmentManager.beginTransaction().add(mContainerViewId, fragment).hide(fragment).commit(); 28 | } 29 | 30 | setFragments(0); 31 | } 32 | 33 | /** 界面切换控制 */ 34 | public void setFragments(int index) { 35 | for (int i = 0; i < mFragments.size(); i++) { 36 | FragmentTransaction ft = mFragmentManager.beginTransaction(); 37 | Fragment fragment = mFragments.get(i); 38 | if (i == index) { 39 | ft.show(fragment); 40 | } else { 41 | ft.hide(fragment); 42 | } 43 | ft.commit(); 44 | } 45 | mCurrentTab = index; 46 | } 47 | 48 | public int getCurrentTab() { 49 | return mCurrentTab; 50 | } 51 | 52 | public Fragment getCurrentFragment() { 53 | return mFragments.get(mCurrentTab); 54 | } 55 | } -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/java/com/flyco/tablayout/utils/UnreadMsgUtils.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.utils; 2 | 3 | 4 | import android.util.DisplayMetrics; 5 | import android.view.View; 6 | import android.widget.RelativeLayout; 7 | 8 | import com.flyco.tablayout.widget.MsgView; 9 | 10 | /** 11 | * 未读消息提示View,显示小红点或者带有数字的红点: 12 | * 数字一位,圆 13 | * 数字两位,圆角矩形,圆角是高度的一半 14 | * 数字超过两位,显示99+ 15 | */ 16 | public class UnreadMsgUtils { 17 | public static void show(MsgView msgView, int num) { 18 | if (msgView == null) { 19 | return; 20 | } 21 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) msgView.getLayoutParams(); 22 | DisplayMetrics dm = msgView.getResources().getDisplayMetrics(); 23 | msgView.setVisibility(View.VISIBLE); 24 | if (num <= 0) {//圆点,设置默认宽高 25 | msgView.setStrokeWidth(0); 26 | msgView.setText(""); 27 | 28 | lp.width = (int) (5 * dm.density); 29 | lp.height = (int) (5 * dm.density); 30 | msgView.setLayoutParams(lp); 31 | } else { 32 | lp.height = (int) (18 * dm.density); 33 | if (num > 0 && num < 10) {//圆 34 | lp.width = (int) (18 * dm.density); 35 | msgView.setText(num + ""); 36 | } else if (num > 9 && num < 100) {//圆角矩形,圆角是高度的一半,设置默认padding 37 | lp.width = RelativeLayout.LayoutParams.WRAP_CONTENT; 38 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0); 39 | msgView.setText(num + ""); 40 | } else {//数字超过两位,显示99+ 41 | lp.width = RelativeLayout.LayoutParams.WRAP_CONTENT; 42 | msgView.setPadding((int) (6 * dm.density), 0, (int) (6 * dm.density), 0); 43 | msgView.setText("99+"); 44 | } 45 | msgView.setLayoutParams(lp); 46 | } 47 | } 48 | 49 | public static void setSize(MsgView rtv, int size) { 50 | if (rtv == null) { 51 | return; 52 | } 53 | RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) rtv.getLayoutParams(); 54 | lp.width = size; 55 | lp.height = size; 56 | rtv.setLayoutParams(lp); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/java/com/flyco/tablayout/widget/MsgView.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayout.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Color; 6 | import android.graphics.drawable.GradientDrawable; 7 | import android.graphics.drawable.StateListDrawable; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.widget.TextView; 11 | 12 | import com.flyco.tablayout.R; 13 | 14 | /** 用于需要圆角矩形框背景的TextView的情况,减少直接使用TextView时引入的shape资源文件 */ 15 | public class MsgView extends TextView { 16 | private Context context; 17 | private GradientDrawable gd_background = new GradientDrawable(); 18 | private int backgroundColor; 19 | private int cornerRadius; 20 | private int strokeWidth; 21 | private int strokeColor; 22 | private boolean isRadiusHalfHeight; 23 | private boolean isWidthHeightEqual; 24 | 25 | public MsgView(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public MsgView(Context context, AttributeSet attrs) { 30 | this(context, attrs, 0); 31 | } 32 | 33 | public MsgView(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | this.context = context; 36 | obtainAttributes(context, attrs); 37 | } 38 | 39 | private void obtainAttributes(Context context, AttributeSet attrs) { 40 | TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MsgView); 41 | backgroundColor = ta.getColor(R.styleable.MsgView_mv_backgroundColor, Color.TRANSPARENT); 42 | cornerRadius = ta.getDimensionPixelSize(R.styleable.MsgView_mv_cornerRadius, 0); 43 | strokeWidth = ta.getDimensionPixelSize(R.styleable.MsgView_mv_strokeWidth, 0); 44 | strokeColor = ta.getColor(R.styleable.MsgView_mv_strokeColor, Color.TRANSPARENT); 45 | isRadiusHalfHeight = ta.getBoolean(R.styleable.MsgView_mv_isRadiusHalfHeight, false); 46 | isWidthHeightEqual = ta.getBoolean(R.styleable.MsgView_mv_isWidthHeightEqual, false); 47 | 48 | ta.recycle(); 49 | } 50 | 51 | @Override 52 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 53 | if (isWidthHeightEqual() && getWidth() > 0 && getHeight() > 0) { 54 | int max = Math.max(getWidth(), getHeight()); 55 | int measureSpec = MeasureSpec.makeMeasureSpec(max, MeasureSpec.EXACTLY); 56 | super.onMeasure(measureSpec, measureSpec); 57 | return; 58 | } 59 | 60 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 61 | } 62 | 63 | @Override 64 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 65 | super.onLayout(changed, left, top, right, bottom); 66 | if (isRadiusHalfHeight()) { 67 | setCornerRadius(getHeight() / 2); 68 | } else { 69 | setBgSelector(); 70 | } 71 | } 72 | 73 | 74 | public void setBackgroundColor(int backgroundColor) { 75 | this.backgroundColor = backgroundColor; 76 | setBgSelector(); 77 | } 78 | 79 | public void setCornerRadius(int cornerRadius) { 80 | this.cornerRadius = dp2px(cornerRadius); 81 | setBgSelector(); 82 | } 83 | 84 | public void setStrokeWidth(int strokeWidth) { 85 | this.strokeWidth = dp2px(strokeWidth); 86 | setBgSelector(); 87 | } 88 | 89 | public void setStrokeColor(int strokeColor) { 90 | this.strokeColor = strokeColor; 91 | setBgSelector(); 92 | } 93 | 94 | public void setIsRadiusHalfHeight(boolean isRadiusHalfHeight) { 95 | this.isRadiusHalfHeight = isRadiusHalfHeight; 96 | setBgSelector(); 97 | } 98 | 99 | public void setIsWidthHeightEqual(boolean isWidthHeightEqual) { 100 | this.isWidthHeightEqual = isWidthHeightEqual; 101 | setBgSelector(); 102 | } 103 | 104 | public int getBackgroundColor() { 105 | return backgroundColor; 106 | } 107 | 108 | public int getCornerRadius() { 109 | return cornerRadius; 110 | } 111 | 112 | public int getStrokeWidth() { 113 | return strokeWidth; 114 | } 115 | 116 | public int getStrokeColor() { 117 | return strokeColor; 118 | } 119 | 120 | public boolean isRadiusHalfHeight() { 121 | return isRadiusHalfHeight; 122 | } 123 | 124 | public boolean isWidthHeightEqual() { 125 | return isWidthHeightEqual; 126 | } 127 | 128 | protected int dp2px(float dp) { 129 | final float scale = context.getResources().getDisplayMetrics().density; 130 | return (int) (dp * scale + 0.5f); 131 | } 132 | 133 | protected int sp2px(float sp) { 134 | final float scale = this.context.getResources().getDisplayMetrics().scaledDensity; 135 | return (int) (sp * scale + 0.5f); 136 | } 137 | 138 | private void setDrawable(GradientDrawable gd, int color, int strokeColor) { 139 | gd.setColor(color); 140 | gd.setCornerRadius(cornerRadius); 141 | gd.setStroke(strokeWidth, strokeColor); 142 | } 143 | 144 | public void setBgSelector() { 145 | StateListDrawable bg = new StateListDrawable(); 146 | 147 | setDrawable(gd_background, backgroundColor, strokeColor); 148 | bg.addState(new int[]{-android.R.attr.state_pressed}, gd_background); 149 | 150 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {//16 151 | setBackground(bg); 152 | } else { 153 | //noinspection deprecation 154 | setBackgroundDrawable(bg); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/res/layout/layout_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 30 | 31 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/res/layout/layout_tab_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 23 | 27 | 28 | 29 | 30 | 44 | 45 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/res/layout/layout_tab_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 21 | 22 | 27 | 28 | 29 | 43 | 44 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/res/layout/layout_tab_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 23 | 27 | 28 | 29 | 30 | 44 | 45 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/res/layout/layout_tab_segment.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 22 | 23 | 24 | 38 | 39 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/res/layout/layout_tab_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 21 | 22 | 27 | 28 | 29 | 43 | 44 | -------------------------------------------------------------------------------- /FlycoTabLayout_Lib/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 H07000223 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlycoTabLayout 2 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-FlycoTabLayout-green.svg?style=true)](https://android-arsenal.com/details/1/2756) 3 | #### [中文版](https://github.com/H07000223/FlycoTabLayout/blob/master/README_CN.md) 4 | An Android TabLayout Lib has 3 kinds of TabLayout at present. 5 | 6 | * SlidingTabLayout: deeply modified from [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip). 7 | * new added attribute 8 | * new added kinds of indicators 9 | * new added unread msg tip 10 | * new added method for convenience 11 | 12 | ```java 13 | /** no need to set titles in adapter */ 14 | public void setViewPager(ViewPager vp, String[] titles) 15 | 16 | /** no need to initialize even adapter */ 17 | public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList fragments) 18 | ``` 19 | 20 | * CommonTabLayout:unlike SlidingTabLayout's dependence on ViewPager,it is a tabLayout without dependence on ViewPager and 21 | can be used freely with other widgets together. 22 | * support kinds of indicators and indicator animation 23 | * support unread msg tip 24 | * support icon and icon gravity. 25 | * new added method for convenience 26 | 27 | ```java 28 | /** support switch fragments itself */ 29 | public void setTabData(ArrayList tabEntitys, FragmentManager fm, int containerViewId, ArrayList fragments) 30 | ``` 31 | 32 | * SegmentTabLayout 33 | 34 | ## Demo 35 | ![](https://github.com/H07000223/FlycoTabLayout/blob/master/preview_1.gif) 36 | 37 | ![](https://github.com/H07000223/FlycoTabLayout/blob/master/preview_2.gif) 38 | 39 | ![](https://github.com/H07000223/FlycoTabLayout/blob/master/preview_3.gif) 40 | 41 | 42 | ## Change Log 43 | 44 | > 3.0.0 (2021-09-30) 45 | - upgrade to AndroidX 46 | - publish on Maven Central 47 | 48 | 49 | ## Gradle 50 | 51 | ```groovy 52 | // AndroidX 53 | dependencies { 54 | implementation 'io.github.h07000223:flycoTabLayout:3.0.0' 55 | } 56 | 57 | // Old 58 | dependencies{ 59 | compile 'com.android.support:support-v4:23.1.1' 60 | compile 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar' 61 | } 62 | ``` 63 | 64 | ## Attributes 65 | 66 | |name|format|description| 67 | |:---:|:---:|:---:| 68 | | tl_indicator_color | color |set indicator color 69 | | tl_indicator_height | dimension |set indicator height 70 | | tl_indicator_width | dimension |set indicator width 71 | | tl_indicator_margin_left | dimension |set indicator margin,invalid when indicator width is greater than 0. 72 | | tl_indicator_margin_top | dimension |set indicator margin,invalid when indicator width is greater than 0. 73 | | tl_indicator_margin_right | dimension |set indicator margin,invalid when indicator width is greater than 0. 74 | | tl_indicator_margin_bottom | dimension |set indicator margin,invalid when indicator width is greater than 0. 75 | | tl_indicator_corner_radius | dimension |set indicator corner radius 76 | | tl_indicator_gravity | enum |set indicator gravity TOP or BOTTOM. 77 | | tl_indicator_style | enum |set indicator style NORMAL or TRIANGLE or BLOCK 78 | | tl_underline_color | color |set underline color 79 | | tl_underline_height | dimension |set underline height 80 | | tl_underline_gravity | enum |set underline gravity TOP or BOTTOM 81 | | tl_divider_color | color |set divider color 82 | | tl_divider_width | dimension |set divider width 83 | | tl_divider_padding |dimension| set divider paddingTop and paddingBottom 84 | | tl_tab_padding |dimension| set tab paddingLeft and paddingRight 85 | | tl_tab_space_equal |boolean| set tab space equal 86 | | tl_tab_width |dimension| set tab width 87 | | tl_textsize |dimension| set text size 88 | | tl_textSelectColor |color| set text select color 89 | | tl_textUnselectColor |color| set text unselect color 90 | | tl_textBold |boolean| set text is bold 91 | | tl_iconWidth |dimension| set icon width(only for CommonTabLayout) 92 | | tl_iconHeight |dimension|set icon height(only for CommonTabLayout) 93 | | tl_iconVisible |boolean| set icon is visible(only for CommonTabLayout) 94 | | tl_iconGravity |enum| set icon gravity LEFT or TOP or RIGHT or BOTTOM(only for CommonTabLayout) 95 | | tl_iconMargin |dimension| set icon margin with text(only for CommonTabLayout) 96 | | tl_indicator_anim_enable |boolean| set indicator support animation(only for CommonTabLayout) 97 | | tl_indicator_anim_duration |integer| set indicator animation duration(only for CommonTabLayout) 98 | | tl_indicator_bounce_enable |boolean| set indicator aniamtion with bounce effect(only for CommonTabLayout) 99 | | tl_indicator_width_equal_title |boolean| set indicator width same as text(only for SlidingTabLayout) 100 | 101 | ## Dependence 102 | * [NineOldAndroids](https://github.com/JakeWharton/NineOldAndroids) 103 | * [FlycoRoundView](https://github.com/H07000223/FlycoRoundView) 104 | 105 | ## Thanks 106 | * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) 107 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # FlycoTabLayout 2 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-FlycoTabLayout-green.svg?style=true)](https://android-arsenal.com/details/1/2756) 3 | 4 | 一个Android TabLayout库,目前有3个TabLayout 5 | 6 | * SlidingTabLayout:参照[PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip)进行大量修改. 7 | * 新增部分属性 8 | * 新增支持多种Indicator显示器 9 | * 新增支持未读消息显示 10 | * 新增方法for懒癌患者 11 | 12 | ```java 13 | /** 关联ViewPager,用于不想在ViewPager适配器中设置titles数据的情况 */ 14 | public void setViewPager(ViewPager vp, String[] titles) 15 | 16 | /** 关联ViewPager,用于连适配器都不想自己实例化的情况 */ 17 | public void setViewPager(ViewPager vp, String[] titles, FragmentActivity fa, ArrayList fragments) 18 | ``` 19 | 20 | * CommonTabLayout:不同于SlidingTabLayout对ViewPager依赖,它是一个不依赖ViewPager可以与其他控件自由搭配使用的TabLayout. 21 | * 支持多种Indicator显示器,以及Indicator动画 22 | * 支持未读消息显示 23 | * 支持Icon以及Icon位置 24 | * 新增方法for懒癌患者 25 | 26 | ```java 27 | /** 关联数据支持同时切换fragments */ 28 | public void setTabData(ArrayList tabEntitys, FragmentManager fm, int containerViewId, ArrayList fragments) 29 | ``` 30 | 31 | * SegmentTabLayout 32 | 33 | ## Demo 34 | ![](https://github.com/H07000223/FlycoTabLayout/blob/master/preview_1.gif) 35 | 36 | ![](https://github.com/H07000223/FlycoTabLayout/blob/master/preview_2.gif) 37 | 38 | ![](https://github.com/H07000223/FlycoTabLayout/blob/master/preview_3.gif) 39 | 40 | 41 | ## Change Log 42 | 43 | > 3.0.0 (2021-09-30) 44 | - 升级到AndroidX 45 | - 发布到Maven 46 | 47 | 48 | ## Gradle 49 | 50 | ```groovy 51 | // AndroidX 52 | dependencies { 53 | implementation 'io.github.h07000223:flycoTabLayout:3.0.0' 54 | } 55 | 56 | // Old 57 | dependencies{ 58 | compile 'com.android.support:support-v4:23.1.1' 59 | compile 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar' 60 | } 61 | ``` 62 | 63 | ## Attributes 64 | 65 | |name|format|description| 66 | |:---:|:---:|:---:| 67 | | tl_indicator_color | color |设置显示器颜色 68 | | tl_indicator_height | dimension |设置显示器高度 69 | | tl_indicator_width | dimension |设置显示器固定宽度 70 | | tl_indicator_margin_left | dimension |设置显示器margin,当indicator_width大于0,无效 71 | | tl_indicator_margin_top | dimension |设置显示器margin,当indicator_width大于0,无效 72 | | tl_indicator_margin_right | dimension |设置显示器margin,当indicator_width大于0,无效 73 | | tl_indicator_margin_bottom | dimension |设置显示器margin,当indicator_width大于0,无效 74 | | tl_indicator_corner_radius | dimension |设置显示器圆角弧度 75 | | tl_indicator_gravity | enum |设置显示器上方(TOP)还是下方(BOTTOM),只对常规显示器有用 76 | | tl_indicator_style | enum |设置显示器为常规(NORMAL)或三角形(TRIANGLE)或背景色块(BLOCK) 77 | | tl_underline_color | color |设置下划线颜色 78 | | tl_underline_height | dimension |设置下划线高度 79 | | tl_underline_gravity | enum |设置下划线上方(TOP)还是下方(BOTTOM) 80 | | tl_divider_color | color |设置分割线颜色 81 | | tl_divider_width | dimension |设置分割线宽度 82 | | tl_divider_padding |dimension| 设置分割线的paddingTop和paddingBottom 83 | | tl_tab_padding |dimension| 设置tab的paddingLeft和paddingRight 84 | | tl_tab_space_equal |boolean| 设置tab大小等分 85 | | tl_tab_width |dimension| 设置tab固定大小 86 | | tl_textsize |dimension| 设置字体大小 87 | | tl_textSelectColor |color| 设置字体选中颜色 88 | | tl_textUnselectColor |color| 设置字体未选中颜色 89 | | tl_textBold |boolean| 设置字体加粗 90 | | tl_iconWidth |dimension| 设置icon宽度(仅支持CommonTabLayout) 91 | | tl_iconHeight |dimension|设置icon高度(仅支持CommonTabLayout) 92 | | tl_iconVisible |boolean| 设置icon是否可见(仅支持CommonTabLayout) 93 | | tl_iconGravity |enum| 设置icon显示位置,对应Gravity中常量值,左上右下(仅支持CommonTabLayout) 94 | | tl_iconMargin |dimension| 设置icon与文字间距(仅支持CommonTabLayout) 95 | | tl_indicator_anim_enable |boolean| 设置显示器支持动画(only for CommonTabLayout) 96 | | tl_indicator_anim_duration |integer| 设置显示器动画时间(only for CommonTabLayout) 97 | | tl_indicator_bounce_enable |boolean| 设置显示器支持动画回弹效果(only for CommonTabLayout) 98 | | tl_indicator_width_equal_title |boolean| 设置显示器与标题一样长(only for SlidingTabLayout) 99 | 100 | ## Dependence 101 | * [NineOldAndroids](https://github.com/JakeWharton/NineOldAndroids) 102 | * [FlycoRoundView](https://github.com/H07000223/FlycoRoundView) 103 | 104 | ## Thanks 105 | * [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) 106 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | applicationId "com.flyco.tablayoutsamples" 9 | minSdkVersion rootProject.ext.minSdkVersion 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | versionCode rootProject.ext.versionCode 12 | versionName rootProject.ext.versionName 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation support.appcompat 25 | implementation support.v4 26 | implementation support.design 27 | // implementation project(':FlycoTabLayout_Lib') 28 | implementation 'io.github.h07000223:flycoTabLayout:3.0.0' 29 | } 30 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/lihui/work/AndroidStudio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 28 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/flyco/tablayoutsamples/adapter/SimpleHomeAdapter.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayoutsamples.adapter; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.graphics.Color; 6 | import android.util.DisplayMetrics; 7 | import android.util.TypedValue; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.AbsListView; 11 | import android.widget.BaseAdapter; 12 | import android.widget.TextView; 13 | 14 | public class SimpleHomeAdapter extends BaseAdapter { 15 | private Context mContext; 16 | private String[] mItems; 17 | private DisplayMetrics mDisplayMetrics; 18 | 19 | public SimpleHomeAdapter(Context context, String[] items) { 20 | this.mContext = context; 21 | this.mItems = items; 22 | mDisplayMetrics = new DisplayMetrics(); 23 | ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics); 24 | } 25 | 26 | @Override 27 | public int getCount() { 28 | return mItems.length; 29 | } 30 | 31 | @Override 32 | public Object getItem(int position) { 33 | return null; 34 | } 35 | 36 | @Override 37 | public long getItemId(int position) { 38 | return position; 39 | } 40 | 41 | @Override 42 | public View getView(int position, View convertView, ViewGroup parent) { 43 | 44 | int padding = (int) (mDisplayMetrics.density * 10); 45 | 46 | TextView tv = new TextView(mContext); 47 | tv.setText(mItems[position]); 48 | tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); 49 | tv.setTextColor(Color.parseColor("#468ED0")); 50 | // tv.setGravity(Gravity.CENTER); 51 | tv.setPadding(padding, padding, padding, padding); 52 | AbsListView.LayoutParams lp = new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, 53 | AbsListView.LayoutParams.WRAP_CONTENT); 54 | tv.setLayoutParams(lp); 55 | return tv; 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/flyco/tablayoutsamples/entity/TabEntity.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayoutsamples.entity; 2 | 3 | import com.flyco.tablayout.listener.CustomTabEntity; 4 | 5 | public class TabEntity implements CustomTabEntity { 6 | public String title; 7 | public int selectedIcon; 8 | public int unSelectedIcon; 9 | 10 | public TabEntity(String title, int selectedIcon, int unSelectedIcon) { 11 | this.title = title; 12 | this.selectedIcon = selectedIcon; 13 | this.unSelectedIcon = unSelectedIcon; 14 | } 15 | 16 | @Override 17 | public String getTabTitle() { 18 | return title; 19 | } 20 | 21 | @Override 22 | public int getTabSelectedIcon() { 23 | return selectedIcon; 24 | } 25 | 26 | @Override 27 | public int getTabUnselectedIcon() { 28 | return unSelectedIcon; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/flyco/tablayoutsamples/ui/CommonTabActivity.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayoutsamples.ui; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | 8 | import androidx.appcompat.app.AppCompatActivity; 9 | import androidx.fragment.app.Fragment; 10 | import androidx.fragment.app.FragmentManager; 11 | import androidx.fragment.app.FragmentPagerAdapter; 12 | import androidx.viewpager.widget.ViewPager; 13 | 14 | import com.flyco.tablayout.CommonTabLayout; 15 | import com.flyco.tablayout.listener.CustomTabEntity; 16 | import com.flyco.tablayout.listener.OnTabSelectListener; 17 | import com.flyco.tablayout.utils.UnreadMsgUtils; 18 | import com.flyco.tablayout.widget.MsgView; 19 | import com.flyco.tablayoutsamples.R; 20 | import com.flyco.tablayoutsamples.entity.TabEntity; 21 | import com.flyco.tablayoutsamples.utils.ViewFindUtils; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Random; 25 | 26 | public class CommonTabActivity extends AppCompatActivity { 27 | private Context mContext = this; 28 | private ArrayList mFragments = new ArrayList<>(); 29 | private ArrayList mFragments2 = new ArrayList<>(); 30 | 31 | private String[] mTitles = {"首页", "消息", "联系人", "更多"}; 32 | private int[] mIconUnselectIds = { 33 | R.mipmap.tab_home_unselect, R.mipmap.tab_speech_unselect, 34 | R.mipmap.tab_contact_unselect, R.mipmap.tab_more_unselect}; 35 | private int[] mIconSelectIds = { 36 | R.mipmap.tab_home_select, R.mipmap.tab_speech_select, 37 | R.mipmap.tab_contact_select, R.mipmap.tab_more_select}; 38 | private ArrayList mTabEntities = new ArrayList<>(); 39 | private View mDecorView; 40 | private ViewPager mViewPager; 41 | private CommonTabLayout mTabLayout_1; 42 | private CommonTabLayout mTabLayout_2; 43 | private CommonTabLayout mTabLayout_3; 44 | private CommonTabLayout mTabLayout_4; 45 | private CommonTabLayout mTabLayout_5; 46 | private CommonTabLayout mTabLayout_6; 47 | private CommonTabLayout mTabLayout_7; 48 | private CommonTabLayout mTabLayout_8; 49 | 50 | @Override 51 | protected void onCreate(Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | setContentView(R.layout.activity_common_tab); 54 | 55 | for (String title : mTitles) { 56 | mFragments.add(SimpleCardFragment.getInstance("Switch ViewPager " + title)); 57 | mFragments2.add(SimpleCardFragment.getInstance("Switch Fragment " + title)); 58 | } 59 | 60 | 61 | for (int i = 0; i < mTitles.length; i++) { 62 | mTabEntities.add(new TabEntity(mTitles[i], mIconSelectIds[i], mIconUnselectIds[i])); 63 | } 64 | 65 | mDecorView = getWindow().getDecorView(); 66 | mViewPager = ViewFindUtils.find(mDecorView, R.id.vp_2); 67 | mViewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager())); 68 | /** with nothing */ 69 | mTabLayout_1 = ViewFindUtils.find(mDecorView, R.id.tl_1); 70 | /** with ViewPager */ 71 | mTabLayout_2 = ViewFindUtils.find(mDecorView, R.id.tl_2); 72 | /** with Fragments */ 73 | mTabLayout_3 = ViewFindUtils.find(mDecorView, R.id.tl_3); 74 | /** indicator固定宽度 */ 75 | mTabLayout_4 = ViewFindUtils.find(mDecorView, R.id.tl_4); 76 | /** indicator固定宽度 */ 77 | mTabLayout_5 = ViewFindUtils.find(mDecorView, R.id.tl_5); 78 | /** indicator矩形圆角 */ 79 | mTabLayout_6 = ViewFindUtils.find(mDecorView, R.id.tl_6); 80 | /** indicator三角形 */ 81 | mTabLayout_7 = ViewFindUtils.find(mDecorView, R.id.tl_7); 82 | /** indicator圆角色块 */ 83 | mTabLayout_8 = ViewFindUtils.find(mDecorView, R.id.tl_8); 84 | 85 | mTabLayout_1.setTabData(mTabEntities); 86 | tl_2(); 87 | mTabLayout_3.setTabData(mTabEntities, this, R.id.fl_change, mFragments2); 88 | mTabLayout_4.setTabData(mTabEntities); 89 | mTabLayout_5.setTabData(mTabEntities); 90 | mTabLayout_6.setTabData(mTabEntities); 91 | mTabLayout_7.setTabData(mTabEntities); 92 | mTabLayout_8.setTabData(mTabEntities); 93 | 94 | mTabLayout_3.setOnTabSelectListener(new OnTabSelectListener() { 95 | @Override 96 | public void onTabSelect(int position) { 97 | mTabLayout_1.setCurrentTab(position); 98 | mTabLayout_2.setCurrentTab(position); 99 | mTabLayout_4.setCurrentTab(position); 100 | mTabLayout_5.setCurrentTab(position); 101 | mTabLayout_6.setCurrentTab(position); 102 | mTabLayout_7.setCurrentTab(position); 103 | mTabLayout_8.setCurrentTab(position); 104 | } 105 | 106 | @Override 107 | public void onTabReselect(int position) { 108 | 109 | } 110 | }); 111 | mTabLayout_8.setCurrentTab(2); 112 | mTabLayout_3.setCurrentTab(1); 113 | 114 | //显示未读红点 115 | mTabLayout_1.showDot(2); 116 | mTabLayout_3.showDot(1); 117 | mTabLayout_4.showDot(1); 118 | 119 | //两位数 120 | mTabLayout_2.showMsg(0, 55); 121 | mTabLayout_2.setMsgMargin(0, -5, 5); 122 | 123 | //三位数 124 | mTabLayout_2.showMsg(1, 100); 125 | mTabLayout_2.setMsgMargin(1, -5, 5); 126 | 127 | //设置未读消息红点 128 | mTabLayout_2.showDot(2); 129 | MsgView rtv_2_2 = mTabLayout_2.getMsgView(2); 130 | if (rtv_2_2 != null) { 131 | UnreadMsgUtils.setSize(rtv_2_2, dp2px(7.5f)); 132 | } 133 | 134 | //设置未读消息背景 135 | mTabLayout_2.showMsg(3, 5); 136 | mTabLayout_2.setMsgMargin(3, 0, 5); 137 | MsgView rtv_2_3 = mTabLayout_2.getMsgView(3); 138 | if (rtv_2_3 != null) { 139 | rtv_2_3.setBackgroundColor(Color.parseColor("#6D8FB0")); 140 | } 141 | } 142 | 143 | Random mRandom = new Random(); 144 | 145 | private void tl_2() { 146 | mTabLayout_2.setTabData(mTabEntities); 147 | mTabLayout_2.setOnTabSelectListener(new OnTabSelectListener() { 148 | @Override 149 | public void onTabSelect(int position) { 150 | mViewPager.setCurrentItem(position); 151 | } 152 | 153 | @Override 154 | public void onTabReselect(int position) { 155 | if (position == 0) { 156 | mTabLayout_2.showMsg(0, mRandom.nextInt(100) + 1); 157 | // UnreadMsgUtils.show(mTabLayout_2.getMsgView(0), mRandom.nextInt(100) + 1); 158 | } 159 | } 160 | }); 161 | 162 | mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 163 | @Override 164 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 165 | 166 | } 167 | 168 | @Override 169 | public void onPageSelected(int position) { 170 | mTabLayout_2.setCurrentTab(position); 171 | } 172 | 173 | @Override 174 | public void onPageScrollStateChanged(int state) { 175 | 176 | } 177 | }); 178 | 179 | mViewPager.setCurrentItem(1); 180 | } 181 | 182 | private class MyPagerAdapter extends FragmentPagerAdapter { 183 | public MyPagerAdapter(FragmentManager fm) { 184 | super(fm); 185 | } 186 | 187 | @Override 188 | public int getCount() { 189 | return mFragments.size(); 190 | } 191 | 192 | @Override 193 | public CharSequence getPageTitle(int position) { 194 | return mTitles[position]; 195 | } 196 | 197 | @Override 198 | public Fragment getItem(int position) { 199 | return mFragments.get(position); 200 | } 201 | } 202 | 203 | protected int dp2px(float dp) { 204 | final float scale = mContext.getResources().getDisplayMetrics().density; 205 | return (int) (dp * scale + 0.5f); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /app/src/main/java/com/flyco/tablayoutsamples/ui/SegmentTabActivity.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayoutsamples.ui; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import androidx.appcompat.app.AppCompatActivity; 8 | import androidx.fragment.app.Fragment; 9 | import androidx.fragment.app.FragmentManager; 10 | import androidx.fragment.app.FragmentPagerAdapter; 11 | import androidx.viewpager.widget.ViewPager; 12 | 13 | import com.flyco.tablayout.SegmentTabLayout; 14 | import com.flyco.tablayout.listener.OnTabSelectListener; 15 | import com.flyco.tablayout.widget.MsgView; 16 | import com.flyco.tablayoutsamples.R; 17 | import com.flyco.tablayoutsamples.utils.ViewFindUtils; 18 | 19 | import java.util.ArrayList; 20 | 21 | public class SegmentTabActivity extends AppCompatActivity { 22 | private ArrayList mFragments = new ArrayList<>(); 23 | private ArrayList mFragments2 = new ArrayList<>(); 24 | 25 | private String[] mTitles = {"首页", "消息"}; 26 | private String[] mTitles_2 = {"首页", "消息", "联系人"}; 27 | private String[] mTitles_3 = {"首页", "消息", "联系人", "更多"}; 28 | private View mDecorView; 29 | private SegmentTabLayout mTabLayout_3; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_segment_tab); 35 | 36 | for (String title : mTitles_3) { 37 | mFragments.add(SimpleCardFragment.getInstance("Switch ViewPager " + title)); 38 | } 39 | 40 | for (String title : mTitles_2) { 41 | mFragments2.add(SimpleCardFragment.getInstance("Switch Fragment " + title)); 42 | } 43 | 44 | mDecorView = getWindow().getDecorView(); 45 | 46 | SegmentTabLayout tabLayout_1 = ViewFindUtils.find(mDecorView, R.id.tl_1); 47 | SegmentTabLayout tabLayout_2 = ViewFindUtils.find(mDecorView, R.id.tl_2); 48 | mTabLayout_3 = ViewFindUtils.find(mDecorView, R.id.tl_3); 49 | SegmentTabLayout tabLayout_4 = ViewFindUtils.find(mDecorView, R.id.tl_4); 50 | SegmentTabLayout tabLayout_5 = ViewFindUtils.find(mDecorView, R.id.tl_5); 51 | 52 | tabLayout_1.setTabData(mTitles); 53 | tabLayout_2.setTabData(mTitles_2); 54 | tl_3(); 55 | tabLayout_4.setTabData(mTitles_2, this, R.id.fl_change, mFragments2); 56 | tabLayout_5.setTabData(mTitles_3); 57 | 58 | //显示未读红点 59 | tabLayout_1.showDot(2); 60 | tabLayout_2.showDot(2); 61 | mTabLayout_3.showDot(1); 62 | tabLayout_4.showDot(1); 63 | 64 | //设置未读消息红点 65 | mTabLayout_3.showDot(2); 66 | MsgView rtv_3_2 = mTabLayout_3.getMsgView(2); 67 | if (rtv_3_2 != null) { 68 | rtv_3_2.setBackgroundColor(Color.parseColor("#6D8FB0")); 69 | } 70 | } 71 | 72 | private void tl_3() { 73 | final ViewPager vp_3 = ViewFindUtils.find(mDecorView, R.id.vp_2); 74 | vp_3.setAdapter(new MyPagerAdapter(getSupportFragmentManager())); 75 | 76 | mTabLayout_3.setTabData(mTitles_3); 77 | mTabLayout_3.setOnTabSelectListener(new OnTabSelectListener() { 78 | @Override 79 | public void onTabSelect(int position) { 80 | vp_3.setCurrentItem(position); 81 | } 82 | 83 | @Override 84 | public void onTabReselect(int position) { 85 | } 86 | }); 87 | 88 | vp_3.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { 89 | @Override 90 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 91 | 92 | } 93 | 94 | @Override 95 | public void onPageSelected(int position) { 96 | mTabLayout_3.setCurrentTab(position); 97 | } 98 | 99 | @Override 100 | public void onPageScrollStateChanged(int state) { 101 | 102 | } 103 | }); 104 | vp_3.setCurrentItem(1); 105 | } 106 | 107 | private class MyPagerAdapter extends FragmentPagerAdapter { 108 | public MyPagerAdapter(FragmentManager fm) { 109 | super(fm); 110 | } 111 | 112 | @Override 113 | public int getCount() { 114 | return mFragments.size(); 115 | } 116 | 117 | @Override 118 | public CharSequence getPageTitle(int position) { 119 | return mTitles_3[position]; 120 | } 121 | 122 | @Override 123 | public Fragment getItem(int position) { 124 | return mFragments.get(position); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /app/src/main/java/com/flyco/tablayoutsamples/ui/SimpleCardFragment.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayoutsamples.ui; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import androidx.fragment.app.Fragment; 11 | 12 | import com.flyco.tablayoutsamples.R; 13 | 14 | @SuppressLint("ValidFragment") 15 | public class SimpleCardFragment extends Fragment { 16 | private String mTitle; 17 | 18 | public static SimpleCardFragment getInstance(String title) { 19 | SimpleCardFragment sf = new SimpleCardFragment(); 20 | sf.mTitle = title; 21 | return sf; 22 | } 23 | 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | } 28 | 29 | @Override 30 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 31 | View v = inflater.inflate(R.layout.fr_simple_card, null); 32 | TextView card_title_tv = (TextView) v.findViewById(R.id.card_title_tv); 33 | card_title_tv.setText(mTitle); 34 | 35 | return v; 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/flyco/tablayoutsamples/ui/SimpleHomeActivity.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayoutsamples.ui; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Color; 6 | import android.os.Bundle; 7 | import android.view.View; 8 | import android.widget.AdapterView; 9 | import android.widget.ListView; 10 | 11 | import androidx.appcompat.app.AppCompatActivity; 12 | 13 | import com.flyco.tablayoutsamples.adapter.SimpleHomeAdapter; 14 | 15 | public class SimpleHomeActivity extends AppCompatActivity { 16 | private Context mContext = this; 17 | private final String[] mItems = {"SlidingTabLayout", "CommonTabLayout", "SegmentTabLayout"}; 18 | private final Class[] mClasses = {SlidingTabActivity.class, CommonTabActivity.class, 19 | SegmentTabActivity.class}; 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | ListView lv = new ListView(mContext); 25 | lv.setCacheColorHint(Color.TRANSPARENT); 26 | lv.setFadingEdgeLength(0); 27 | lv.setAdapter(new SimpleHomeAdapter(mContext, mItems)); 28 | 29 | lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { 30 | @Override 31 | public void onItemClick(AdapterView parent, View view, int position, long id) { 32 | Intent intent = new Intent(mContext, mClasses[position]); 33 | startActivity(intent); 34 | } 35 | }); 36 | 37 | setContentView(lv); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/flyco/tablayoutsamples/ui/SlidingTabActivity.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayoutsamples.ui; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import androidx.fragment.app.Fragment; 11 | import androidx.fragment.app.FragmentManager; 12 | import androidx.fragment.app.FragmentPagerAdapter; 13 | import androidx.viewpager.widget.ViewPager; 14 | 15 | import com.flyco.tablayout.SlidingTabLayout; 16 | import com.flyco.tablayout.listener.OnTabSelectListener; 17 | import com.flyco.tablayout.widget.MsgView; 18 | import com.flyco.tablayoutsamples.R; 19 | import com.flyco.tablayoutsamples.utils.ViewFindUtils; 20 | 21 | import java.util.ArrayList; 22 | 23 | public class SlidingTabActivity extends AppCompatActivity implements OnTabSelectListener { 24 | private Context mContext = this; 25 | private ArrayList mFragments = new ArrayList<>(); 26 | private final String[] mTitles = { 27 | "热门", "iOS", "Android" 28 | , "前端", "后端", "设计", "工具资源" 29 | }; 30 | private MyPagerAdapter mAdapter; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_sliding_tab); 36 | 37 | for (String title : mTitles) { 38 | mFragments.add(SimpleCardFragment.getInstance(title)); 39 | } 40 | 41 | 42 | View decorView = getWindow().getDecorView(); 43 | ViewPager vp = ViewFindUtils.find(decorView, R.id.vp); 44 | mAdapter = new MyPagerAdapter(getSupportFragmentManager()); 45 | vp.setAdapter(mAdapter); 46 | 47 | /** 默认 */ 48 | SlidingTabLayout tabLayout_1 = ViewFindUtils.find(decorView, R.id.tl_1); 49 | /**自定义部分属性*/ 50 | SlidingTabLayout tabLayout_2 = ViewFindUtils.find(decorView, R.id.tl_2); 51 | /** 字体加粗,大写 */ 52 | SlidingTabLayout tabLayout_3 = ViewFindUtils.find(decorView, R.id.tl_3); 53 | /** tab固定宽度 */ 54 | SlidingTabLayout tabLayout_4 = ViewFindUtils.find(decorView, R.id.tl_4); 55 | /** indicator固定宽度 */ 56 | SlidingTabLayout tabLayout_5 = ViewFindUtils.find(decorView, R.id.tl_5); 57 | /** indicator圆 */ 58 | SlidingTabLayout tabLayout_6 = ViewFindUtils.find(decorView, R.id.tl_6); 59 | /** indicator矩形圆角 */ 60 | final SlidingTabLayout tabLayout_7 = ViewFindUtils.find(decorView, R.id.tl_7); 61 | /** indicator三角形 */ 62 | SlidingTabLayout tabLayout_8 = ViewFindUtils.find(decorView, R.id.tl_8); 63 | /** indicator圆角色块 */ 64 | SlidingTabLayout tabLayout_9 = ViewFindUtils.find(decorView, R.id.tl_9); 65 | /** indicator圆角色块 */ 66 | SlidingTabLayout tabLayout_10 = ViewFindUtils.find(decorView, R.id.tl_10); 67 | 68 | tabLayout_1.setViewPager(vp); 69 | tabLayout_2.setViewPager(vp); 70 | tabLayout_2.setOnTabSelectListener(this); 71 | tabLayout_3.setViewPager(vp); 72 | tabLayout_4.setViewPager(vp); 73 | tabLayout_5.setViewPager(vp); 74 | tabLayout_6.setViewPager(vp); 75 | tabLayout_7.setViewPager(vp, mTitles); 76 | tabLayout_8.setViewPager(vp, mTitles, this, mFragments); 77 | tabLayout_9.setViewPager(vp); 78 | tabLayout_10.setViewPager(vp); 79 | 80 | vp.setCurrentItem(4); 81 | 82 | tabLayout_1.showDot(4); 83 | tabLayout_3.showDot(4); 84 | tabLayout_2.showDot(4); 85 | 86 | tabLayout_2.showMsg(3, 5); 87 | tabLayout_2.setMsgMargin(3, 0, 10); 88 | MsgView rtv_2_3 = tabLayout_2.getMsgView(3); 89 | if (rtv_2_3 != null) { 90 | rtv_2_3.setBackgroundColor(Color.parseColor("#6D8FB0")); 91 | } 92 | 93 | tabLayout_2.showMsg(5, 5); 94 | tabLayout_2.setMsgMargin(5, 0, 10); 95 | 96 | // tabLayout_7.setOnTabSelectListener(new OnTabSelectListener() { 97 | // @Override 98 | // public void onTabSelect(int position) { 99 | // Toast.makeText(mContext, "onTabSelect&position--->" + position, Toast.LENGTH_SHORT).show(); 100 | // } 101 | // 102 | // @Override 103 | // public void onTabReselect(int position) { 104 | // mFragments.add(SimpleCardFragment.getInstance("后端")); 105 | // mAdapter.notifyDataSetChanged(); 106 | // tabLayout_7.addNewTab("后端"); 107 | // } 108 | // }); 109 | } 110 | 111 | @Override 112 | public void onTabSelect(int position) { 113 | Toast.makeText(mContext, "onTabSelect&position--->" + position, Toast.LENGTH_SHORT).show(); 114 | } 115 | 116 | @Override 117 | public void onTabReselect(int position) { 118 | Toast.makeText(mContext, "onTabReselect&position--->" + position, Toast.LENGTH_SHORT).show(); 119 | } 120 | 121 | private class MyPagerAdapter extends FragmentPagerAdapter { 122 | public MyPagerAdapter(FragmentManager fm) { 123 | super(fm); 124 | } 125 | 126 | @Override 127 | public int getCount() { 128 | return mFragments.size(); 129 | } 130 | 131 | @Override 132 | public CharSequence getPageTitle(int position) { 133 | return mTitles[position]; 134 | } 135 | 136 | @Override 137 | public Fragment getItem(int position) { 138 | return mFragments.get(position); 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/com/flyco/tablayoutsamples/utils/ViewFindUtils.java: -------------------------------------------------------------------------------- 1 | package com.flyco.tablayoutsamples.utils; 2 | 3 | import android.util.SparseArray; 4 | import android.view.View; 5 | 6 | @SuppressWarnings({"unchecked"}) 7 | public class ViewFindUtils { 8 | /** 9 | * ViewHolder简洁写法,避免适配器中重复定义ViewHolder,减少代码量 用法: 10 | * 11 | *
12 |      * if (convertView == null)
13 |      * {
14 |      * 	convertView = View.inflate(context, R.layout.ad_demo, null);
15 |      * }
16 |      * TextView tv_demo = ViewHolderUtils.get(convertView, R.id.tv_demo);
17 |      * ImageView iv_demo = ViewHolderUtils.get(convertView, R.id.iv_demo);
18 |      * 
19 | */ 20 | public static T hold(View view, int id) { 21 | SparseArray viewHolder = (SparseArray) view.getTag(); 22 | 23 | if (viewHolder == null) { 24 | viewHolder = new SparseArray(); 25 | view.setTag(viewHolder); 26 | } 27 | 28 | View childView = viewHolder.get(id); 29 | 30 | if (childView == null) { 31 | childView = view.findViewById(id); 32 | viewHolder.put(id, childView); 33 | } 34 | 35 | return (T) childView; 36 | } 37 | 38 | /** 39 | * 替代findviewById方法 40 | */ 41 | public static T find(View view, int id) { 42 | return (T) view.findViewById(id); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background_card.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/drawable/background_card.9.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_common_tab.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 26 | 27 | 31 | 32 | 46 | 47 | 51 | 52 | 53 | 71 | 72 | 81 | 82 | 93 | 94 | 104 | 105 | 117 | 118 | 130 | 131 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_segment_tab.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 24 | 25 | 37 | 38 | 53 | 54 | 58 | 59 | 74 | 75 | 79 | 80 | 94 | 95 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sliding_tab.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 18 | 19 | 31 | 32 | 49 | 50 | 58 | 59 | 67 | 68 | 74 | 75 | 83 | 84 | 92 | 93 | 100 | 101 | 111 | 112 | 124 | 125 | 126 | 127 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fr_simple_card.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tab_contact_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/tab_contact_select.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tab_contact_unselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/tab_contact_unselect.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tab_home_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/tab_home_select.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tab_home_unselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/tab_home_unselect.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tab_more_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/tab_more_select.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tab_more_unselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/tab_more_unselect.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tab_speech_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/tab_speech_select.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/tab_speech_unselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/H07000223/FlycoTabLayout/528fcffe88d54eeafbc85ffd1ce375df043b3a28/app/src/main/res/mipmap-xhdpi/tab_speech_unselect.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #8BC34A 4 | #689F38 5 | #FF4081 6 | #FFFFFF 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FlycoTabLayout 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 16 | 17 |