转载请附原文链接:TabLayout 自定义 Indicator 指示线宽度样式
需求
最近写公司项目的时候遇到一个需求是两个界面滑动左右切换,点击标题界面也切换,效果如下:

这个 UI 界面第一想法就是用 TabLayout+Fragment去做,但是仔细观察这个 UI 效果你会发现一个比较蛋疼的问题,就是选中标题的指示线 Indicator 长度问题, TabLayout 默认情况下指示线会比标题文字长出一部分,并且没有设置指示线长度的 API 可调用。下面是 TabLayout 原生效果图:

解决方案
既然原生的 TabLayout 没有可以设置指示线宽度的 API ,我们有两种方案去实现这个效果:
方案 1 自定义 View
不用系统的 TabLayout ,自定义一个 View,然后给 View 设置点击事件并结合监听 ViewPager 的滑动,计算指示线的滑动位置,再给添加个动画就可以实现这个效果,很简单,但是这种方案不适合我们项目,因为我们项目上个版本就有这种 UI 效果,同事是用 TabLayout ,但是指示线就是原生的效果,这个版本设计要求全部改成全部改成需求第一个 Gif 图效果,如果我们自定义 View 实现的话,代码改动太大,这是我们程序员最不希望做的,所以这种方案这里我就不写了。感兴趣的自己玩玩。
方案 2 TabLayoutHelper 实现
TabLayout 源码
在 TabLayout 的基础上去实现,一开始想,如果那个指示线如果是一个 View 就好办了,我们可以通过 view.getChildAt(n) 方法找到这个 View,然后动态给这个指示线 View 设置一个宽带就解决了,所以先去源码,找了一下发现代码如下:
|
|
完犊子了,这个指示线是 draw画出来的,所以这种想法破灭了,既然看到这了,我们所以研究下这个指示器线宽度是根据啥画的。从头捋,先看一下 TabLayout 是个什么东西:
|
|
emmm, 继承自 HorizontalScrollView,这就是很多 Tab 时候可以滑动的原因了。这里面关键代码是 super.addView(mTabStrip,...) TabLayout 中添加了一个 SlidingTabStrip,那么其实主要方法基本就是看 SlidingTabStrip 类了。好了现在我们从使用 Tablayout 经常调用的 方法开始:
|
|
代码很简单,调用 addTab 最终会调用 mTabStrip.addView 果然是主要是看 SlidingTabStrip 这个类, 这里看一下 TabView :
|
|
TabView 是我们调用 addTabView 方法时候创建的,用于显示我们设置的 Tittle ,继承自 LinearLayout 但是没有记录 Indicator 代码,那么接着看 mTabStrip :
|
|
emmm,这里找到了在 draw 里面使用的变量 mIndicatorLeft ,有了 Indicator 踪影。也就是说这个 Indicator 是在 SlidingTabStrip 这个类中画出来的,那么最追 mIndicatorLeft 是根据什么设置的:
|
|
上面代码可以得知 Indicator 宽度总是和我们添加的 TabView 一样宽的,那么 TabView 的宽度我们一般也没有去设置,它又是根据什么设置的呢,为什么我们两个字的 TabView 和 4 个字的 TabView 是一样宽度,那么就去看 TabView 的 onMeasure 方法,这里具体方法代码就补贴出来了,反正结果就是里面没发现设置使得两个字的 TabView 和 4 个字的 TabView 是一样宽度的代码, 我们知道 TabView 是添加在 SlidingTabStrip 中,父布局会影响子视图宽度,那么继续看 SlidingTabStrip 的 onMeasure :
|
|
我们发现在 SlidingTabStrip onMeasure 放在中遍历子视图将所有子视图宽度设置为子视图中最大宽度然后均分剩余父布局空间,到这里我们彻底明白了 TabLayout 的布局规则。
google 大法好
通过上面 TabLayout 源码是梳理,我们发现不太可能实现 Indicator 指示线比 TabView 短了,那么我们先 goolge 一下,看看网友是怎么处理的,下面是 stackoverflow 上面的解决方案 Android Tab layout: Wrap tab indicator width with respect to tab title:
|
|
上面解决方案,只是将 TabView 的 左右 padding 值设置为0,使得指示线宽度和文字 Title 宽度一样长,无法达到指示线宽度比文字宽度短的效果,显然这种解决方案是不行的
setCustomView 解决方案
还记得 TabView 的源码中有个 mCustomView 么
|
|
而且还有个 setCustomView 方法
|
|
注释已经写的很清楚,就是这个 view 用于当前这个 tab,那么事情就好办了,我们自己定义一个 View ,然后包含一个 TextView 和一个指示线 View ,那么这个指示线 View 我们想设置成什么样都可以了。下面代码就很简单直接贴出来:
|
|
|
|
上面代码很简单,没什么好说的,但是大家会看到一个 builder 对象,而且把 TabLayout 对象传递传递进来进行设置, 没错,我们采用了 Builder + Helper 模式去实现的,Builder 把需要的设置的属性存起来,Helper 去设置 TabLayout 属性,而不是继承自 TabLayout 自定义 一个 View 方式,因为如果自定义 View 的话,代码侵入性太强,已经使用了 TabLayout 的地方需要改动原来代码,而才 TabLayoutHelper 我们不需要改动原来代码,只需要在原来基础上调用几行代码即可:
|
|
使用起来很简单,根据自己的需要设置即可。
总结:这种解决方案优点是指示线宽度样式自己可以随便设置,缺点是没有了原来的滑动动画效果。
项目地址:TabLayoutHelper