本文的微信公众号链接:关于TabLayout的indicator宽度的一次探索
关于TabLayout,做过android开发的都知道,在android开发中应用特别广泛。这里来简单记录一下前不久遇到的关于TabLayout的一个问题。
这是我们app首页顶部的部分截图,典型的TabLayout的应用场景,现在遇到一个问题,设计需要TabLayout的indicator的宽度能够与Tab的文字等宽,甚至需要比文字的宽度短。
那么现在就需要想办法解决这个问题了,另外需要注意的是,这里的TabLayout是与ViewPager绑定的,需要兼容原来的TabLayout的的逻辑。
首先,想想能否在原生TabLayout的基础上,做一些调整。查看TabLayout的源码,我们发现TabLayout的有一个私有内部类SlidingTabStrip,这个SlidingTabLayout继承自LinearLayout,TabLayout的每个Tab的indicator就是由这个类负责绘制的。
可以看到,Tab的indicator的是SlidingTabStrip绘制的一个Rect,这个Rect的宽度是由IndicatorLeft和mIndicatorRight来决定的,看到上面截图的第一个箭头所指的地方,注意到这两行代码left = selectedTitle.getLeft(); right = selectedTitle.getRight();基本可以确定每个Indicator的宽度是和每个Tab的宽度确定的。所以,可以考虑针对这个特点做点调整。
链接地址:方法一
主要代码如下:
tabLayoutThree.post(new Runnable() {
@Override
public void run() {
try {
Class> tablayout = tabLayoutThree.getClass();
Field tabStrip = tablayout.getDeclaredField("mTabStrip");
tabStrip.setAccessible(true);
LinearLayout ll_tab= (LinearLayout) tabStrip.get(tabLayoutThree);
for (int i = 0; i < ll_tab.getChildCount(); i++) {
View child = ll_tab.getChildAt(i);
child.setPadding(0,0,0,0);
LinearLayout.LayoutParams params = new
LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT,1);
params.setMarginStart(dip2px(MainActivity.this,1f));
params.setMarginEnd(dip2px(MainActivity.this,15f));
child.setLayoutParams(params);
child.invalidate();
}
} catch (Exception e) {
}
}
});
这种情况下,需要指定TabLayout的tabMode为fixed,如果设置为scrollable,那么这段代码是不生效的。
来看看这段代码的运行效果:
这种情况下基本可以满足需求,这里的indicator的宽度基本和Tab的文字是等宽的。这种解决方法勉强可以达到这种需求,但是这里有个问题是当Tab的文字不统一的时候,Indicator的宽度会由最大的那个Tab的宽度决定。
如上截图,我们发现,当第二个tab的文字变长的时候,第一个tab的Indicator的宽度马上就超过了tab文字的宽度,作为一个追求完美的程序员,这样还是显得很别扭,所以接下来我们再来看看第二种方法。
链接地址:方法二
贴上链接地址,这个作者针对第一种方式做了一些改善,我觉得文章写得很不错,不仅针对第一种方式给出了具体的方法,而且分析了为什么采取这种方式。
这里简单总结下,你也可以点击上面的链接看原作者的分析。我们注意到,TabLayout内部对Tab的宽度有个特殊的处理,在onMeasure方法中,如下面的截图,首先循环遍历,取出Tab的最大宽度,然后再循环将最大的宽度设置为每个Tab的宽度,这样tab的宽度总是以所有tab中最大宽度的那个值来决定,而我们每个Tab的Indicator的width是和每个Tab的宽度相关的.
我们来看下关键代码:
tabLayoutTwo.post(new Runnable() {
@Override
public void run() {
try {
//了解源码得知 线的宽度是根据 tabView的宽度来设置的
LinearLayout mTabStrip = (LinearLayout) tabLayoutTwo.getChildAt(0);
int dp10 = dip2px(tabLayoutTwo.getContext(), 10);
for (int i = 0; i < mTabStrip.getChildCount(); i++) {
View tabView = mTabStrip.getChildAt(i);
//拿到tabView的mTextView属性 tab的字数不固定一定用反射取mTextView
Field mTextViewField =
tabView.getClass().getDeclaredField("mTextView");
mTextViewField.setAccessible(true);
TextView mTextView = (TextView) mTextViewField.get(tabView);
tabView.setPadding(0, 0, 0, 0);
//因为我想要的效果是 字多宽线就多宽,所以测量mTextView的宽度
int width = 0;
width = mTextView.getWidth();
if (width == 0) {
mTextView.measure(0, 0);
width = mTextView.getMeasuredWidth();
}
//设置tab左右间距为10dp 注意这里不能使用Padding
// 因为源码中线的宽度是根据 tabView的宽度来设置的
LinearLayout.LayoutParams params =
(LinearLayout.LayoutParams) tabView.getLayoutParams();
params.width = width ;
params.leftMargin = dp10;
params.rightMargin = dp10;
tabView.setLayoutParams(params);
tabView.invalidate();
}
} catch (Exception e) {
}
}
});
通过反射拿到每个TabView的宽度,拿到TabView中每个TextView宽度,将TextView的宽度设置给每个TabView,这样我们的Tab的宽度就被压缩了,同时,每个Tab的Indicator的宽度就能和Tab的文字部分保持同样的宽度了。
这样看,是不是就没有上面的问题了。每个Tab的Indicator的width基本可以合Tab的文字宽度保持一致了。但是这里还有一个问题,就是采用这种方法虽然可以解决每个Tab的Indicator宽度太长的问题,但是,上面两种方法都有一个特点,就是通过反射将每个tab的padding设置为0,这样会导致每个tab的可点击区域变得很小,我么打开开发者模式里面显示布局边距这个选项来看下我们刚才的解决方法运行的截图:
通过上面的截图可以清楚地发现每个tab的可点击区域被压缩得很窄。虽然这个在某些方面看来是可以接受的,但是我们来看文章最开始这张图。
这里我么如果采取上面两种方式来压缩Tab来改变Indicator的width的话,就会导致每个Tab的边距变得很小,这样,就会导致Tab的可点击区域变小,这对于app的首页来说,肯定是不能接受的。所以,针对这一点来说,上面两种方式都是不符合要求的,所以需要找另一种方法来解决这个问题。
简单来说,就是将原生TabLayout代码复制一份,作为自己的代码,这样可以很容易地改动里面的代码,在使用的时候直接引用我们自己的这个TabLayout就行。
接下来我们需要改原生TabLayout的一些逻辑,在保留原生TabLayout一些自定义属性的基础上,添加可以自定义Indicator的width的属性,
在updateIndicatorPosition方法中,这里在处理Indicator的宽度的时候不是以Tab的左边坐标来算,将Indicator的left和right进行如下设置。
left = selectedTitle.getLeft() + (selectedTitle.getWidth() - mSelectedIndicatorWidth) / 2;
right = left + mSelectedIndicatorWidth;
并且,对于Indicator滑动中的状态也做相应的处理,即当mSelectionOffset大于0时对Indicator的left和right做同样的处理。
这里改动的是animateIndicatorToPosition方法,根据名字就可以基本知道这个是对Tab切换时Indicator的动画处理。
如上截图,可以知道这个targetView就是TabLayout的每个Tab,对于targetLeft和targetRight我们做上面同样的处理。
接下来我们可以验证下我们这样的改动方法是否有效,简单看下效果图:
可以看到,我们可以让TabLayout的Tab的可点击区域保持不变,并且可以修改Tab下面的Indicator的宽度,这里我们提供了TabLayout的Indicator的width的自定义。
Demo地址,如果觉得看文章不是特别清楚,可以直接去看Demo中的代码。
最后,感谢大家阅读这篇文章,如果觉得写得还不错的话,点个赞呗,实在不行,留个言也可以接受。