android自定义tab下划线变大,一篇文章带你解决Android TabLayout缺陷,不同方式带你改变Tab下划线宽度【实践总结】...

TabLayout我们再熟悉不过了,在开发中,像这种tab切换的需求都会用到TabLayout,它是由官方提供的一个控件,在support design 包中。使用起来非常简单方便,交互效果也很不错,能满足我们开发中95%的需求。但是它有一个缺陷:不能改变Tab下划线(Indicator)的宽度。本篇文章给你带来改变Tab下划线宽度的几种方式:

1 . 通过反射设置Tab下划线的宽度

2 . 通过TabLayout setCustomView 的方式

3 . 使用第三方开源库。

一、通过反射的方式,改变TabLayout下划线的宽度

首先我们看一下原生的TabLayout的效果(没有任何修改):

gif演示:

上图第一个固定模式(tabMode:fixed),下面是滚动模式(tabMode:scrollable),可以看到,所有Tab下方的线(即Indicator)是一样长的,不管Tab的内容是长还是短。Tab indicator的长度与最长的Tab保持一致。

TabLayout提供了tabIndicatorHeight 属性来设置indicator的高度,但是没有提供设置宽度的的api,要想改变indicator的宽度,就得去看看源码indicator是怎么实现的。简单的看一下源码:

如上思维导图,其中有两个重点的东西, TabView 和 SlidingTabStrip,TabView就是我们所看到的Tab,SlidingTabStrip是TabView的父容器,继承自LinearLayout,用来处理Tab滑动相关操作,如动画,绘制Indicator等。

我们要研究indicator是怎么添加的,重点就在SlidingTabStrip 里了,这里我们看到了mSelectedIndicatorHeight,这就是我们设置Indicator的高度,在draw方法里有如下代码:

@Override

public void draw(Canvas canvas) {

super.draw(canvas);

// Thick colored underline below the current selection

if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {

canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,

mIndicatorRight, getHeight(), mSelectedIndicatorPaint);

}

}

这就是绘制的选中Tab的Indicator,高度是mSelectedIndicatorHeight,宽是mIndicatorRight - mIndicatorLeft 。那么者两个值是从哪儿来的呢?在updateIndicatorPosition方法中:

private void updateIndicatorPosition() {

// 选中的TabView

final View selectedTitle = getChildAt(mSelectedPosition);

int left, right;

if (selectedTitle != null && selectedTitle.getWidth() > 0) {

// left 和right 的值

left = selectedTitle.getLeft();

right = selectedTitle.getRight();

if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {

// Draw the selection partway between the tabs

View nextTitle = getChildAt(mSelectedPosition + 1);

left = (int) (mSelectionOffset * nextTitle.getLeft() +

(1.0f - mSelectionOffset) * left);

right = (int) (mSelectionOffset * nextTitle.getRight() +

(1.0f - mSelectionOffset) * right);

}

} else {

left = right = -1;

}

// 设置mIndicatorLeft和mIndicatorRight

setIndicatorPosition(left, right);

}

void setIndicatorPosition(int left, int right) {

if (left != mIndicatorLeft || right != mIndicatorRight) {

// If the indicator's left/right has changed, invalidate

mIndicatorLeft = left;

mIndicatorRight = right;

ViewCompat.postInvalidateOnAnimation(this);

}

}

从上面的代码就可以看出,Indicator(Tab选中下划线)的宽度其实就是TabView的宽度,那么TabView的宽度是多少呢?在SlidingTabStrip的onMeasure方法中,为TabView设置了宽度。请看代码:

@Override

protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

...

//以上省略

if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {

final int count = getChildCount();

// First we'll find the widest tab

//google的工程师注释写的非常清楚:第一步,找出宽度最长的Tab

int largestTabWidth = 0;

for (int i = 0, z = count; i < z; i++) {

View child = getChildAt(i);

if (child.getVisibility() == VISIBLE) {

largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());

}

}

if (largestTabWidth <= 0) {

// If we don't have a largest child yet, skip until the next measure pass

return;

}

final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);

boolean remeasure = false;

if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {

// If the tabs fit within our width minus gutters, we will set all tabs to have

// the same width

// 第二步:将所有Tab的宽度都设置为largestTabWidth

for (int i = 0; i < count; i++) {

final LinearLayout.LayoutParams lp =

(LayoutParams) getChildAt(i).getLayoutParams();

if (lp.width != largestTabWidth || lp.weight != 0) {

lp.width = largestTabWidth;

lp.weight = 0;

remeasure = true;

}

}

} else {

// If the tabs will wrap to be larger than the width minus gutters, we need

// to switch to GRAVITY_FILL

mTabGravity = GRAVITY_FILL;

updateTab

你可能感兴趣的:(android自定义tab下划线变大,一篇文章带你解决Android TabLayout缺陷,不同方式带你改变Tab下划线宽度【实践总结】...)