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