本文为转载&地址:项目地址: https://github.com/itgoyo/AndroidSource-Analysis
简介: Android源码分析,让你更清楚的理解每一个组件的功能与用法。
TabLayout 源码解析
1. 功能介绍
1.1 TabLayout
Tabs跟随Actionbar在Android 3.0进入大家的视线,是一个很经典的设计。它也是Material Design 规范中提及的Component之一。Tabs
or Bottom
navigation?相信不少Android开发者与产品都撕过,就连微信在其中也有过抉择。Google在Google+以及Google
Photo中相继采用Bottom navigation的设计把剧情推到向高潮,一度轰动整个社区。Google继而在Material Design
规范加入了Bottom navigation,表明了态度,也给这起争论画上了圆满的句号。
在 support desgin lib 发布前,大家基本都采用PagerSlidingTabStrip来实现tab效果。其实TabLayout在实现上和PagerSlidingTabStrip十分相似,今天我们来分析TabLayout。
1.2 TabLayout使用
TabLayout使用比较简单。既可以单独使用,也可以与ViewPager配合使用。
1.2.1 TabLayout单独使用
在java代码中添加Tabs
也可以在xml中添加Tabs
1.2.2 与ViewPager搭配使用
|
|
2. 总体设计
TabLayout继承HorizontalScrollView天生就是一个可以横向滚动的ViewGroup. 我们知道,HorizontalScrollView与ScrollView一样, 最多只能包含一个子View.SlidingTabStrip继承于LinearLayout,是TabLayout的内部类。它是TabLayout唯一的子View. 所有的TabView都是它的子View.TabView继承于LinearLayout,以Tab为数据源,来展示Tab的样式。最终用for循环被add进SlidingTabStrip.Tab是一个简单的View Model实体类,控制TabView的title, icon, custom layout id等属性。TabItem继承于View. 用于在layout xml中来描述Tab. 需要注意的是,它不会add到SlidingTabStrip中去。它的作用是从xml中获取到text,icon,custom layout id等属性。TabLayout inflate到TabItem并获取属性到装配到Tab中,最终add到SlidingTabStrip中的还是TabView.OnTabSelectedListener是TabLayout中的内部接口,用于监听SlidingTabStrip中子TabView选中状态的改变。Mode是TabLayout滚动模式的描述,一共有两种状态。MODE_FIXED不可滚动模式,以及MODE_SCROLLABLE可以滚动模式。Gravity是TabView在SlidingTabStrip中layout方式的描述。分为:GRAVITY_FILL,GRAVITY_CENTER.
3. 详细设计
3.1 类关系图

3.2 分析
3.2.1 TabLayout子View唯一性保证
前面介绍TabLayout继承于HorizontalScrollView最多只能有1个子View. 但TabLayout可以在layout中添加多个子View节点.
这是怎么回事呢?
看过LayoutInflater源码的同学可能会知道这个过程:先inflate到生成View对象,再调用ViewGroup#addView(...)系列方法把view添加到ViewGroup中。我们发现TabLayout的addView(...)系列方法,都删去super调用,且调用了共同的一个方法,addViewInternal(View
view)。
|
|
可见,若child非TabItem对象会抛出异常。所以xml中给TabLayout添加tab时,只能添加TabItem对象。若想添加其它View类型怎么办?TabItem有android:customView这个属性。我们继续来看。
这里调newTab()方法创建了一个tab对象,并且用对象池把创建的tab对象缓存起来。然后将TabItem对象的属性都赋值给tab对象。在createTabView(Tab
tab)这个方法中,首先从TabView池中获取TabView对象,如果不存在,则实例化一个对象,并调用tabView.setTab(tab)方法来进行了数据绑定。
addTab(...)有三个重载方法,最终都会调用如下方法:
在addView(Tab, int,
boolean)方法中,把TabView对象add进了SlidingTabStrip这个ViewGroup中。实际上SlidingTabStrip的对象mTabStrip才是TabLayout的唯一子View.在TabLayout的构造方法中:
至此,我们就明白了TabLayout中子View的一致性是如何保证的。也明白了TabView其实才是亲生的,TabItem其实是后娘养的!
这些代码都很简单,不过我们可以从中学习到很多有用的思想。
至此,一个清晰的View层级图应该就出现在了各位同学的眼前。
3.2.2 与ViewPager搭配使用
有了上面的的基础,我们再来看看TabLayout是如何和它的好基友ViewPager搭配使用的。
这里的TabLayoutOnPageChangeListener实现了ViewPager.OnPageChangeListener.
首先调用ViewPager对象addOnPageChangeListener(OnPageChangeListener)来监听ViewPager的滑动以及当前也的选中。然后设置ViewPagerOnTabSelectedListener对象,保证ViewPager的页面和TabLayout的item的选中状态保持一致,以及滚动的协同性。这里的监听在3.2.3中详细讲解。
我们一般调用viewPager.getAdapter().notifyDataSetChanged()来进行ViewPager的刷新.
现在我们在ViewPager的adapter中注册一个监听器,监听ViewPager的刷新行为。目的是为了刷新ViewPager的同时也可以刷新TabLayout.
我们来看看PagerAdapterObserver这个监听器是如何刷新TabLayout的。
刷新方式很简单粗暴,从SlidingTabStrip对象中移除所有的TabView,继而从View
ModelmTabs中移除所有Tab对象。然后从adapter中获取tab信息,循环调用addTab(Tab,
boolean)方法重新添加TabView。最后调用ViewPager对象的getCurrentItem()方法,获取当前位置,然后调用selectTab(int
position)恢复TabView的选中状态(针对TabView的选中,3.2.4中有详细介绍)。
3.2.3 ViewPager与TabLayout的Tab及indicaotr协同滚动
|
|
用过ViewPager的同学对OnPageChangeListener不会陌生,不多赘述。TabLayoutOnPageChangeListener实现了OnPageChangeListener,
在onPageScrolled(...)方法中做协同滚动处理。滚动的条件是:
调用TabLayout的setScrollPosition(...)方法来控制TabLayout中TabView和indocator的协同滚动。
3.2.3.1 TabLayout的Indicator协同滚动
indicator的滚动由SlidingTabStrip来处理:
|
|
这里的position是当前选中的位置。positionOffset是: 距当前Tab滑动的距离/从当前tab滑动到下一个tab的总距离
这样一个范围在[0,1]间的小数。
SlidingTabStrip#setIndicatorPositionFromTabPosition(int, float)
SlidingTabStrip#updateIndicatorPosition()
通过getChildAt(mSelectedPosition),
获取到到mSelectedPosition处的TabView。若滑动的mSelectionOffset>0f且当前选中的位置mSelectedPosition不是最后一个TabView.
获取到下一个TabView,并计算出indicator的left和right。
SlidingTabStrip#setIndicatorPosition(int, int)
非常简单的代码,在调用ViewCompat.postInvalidateOnAnimation(this)重绘View之前,去掉一些重复绘制的帧。
|
|
绘制逻辑很简单。调用canvas.drawRect(float left, float top, float right, float
bottom, Paint paint)来绘制indicator.这里:
3.2.3.2 TabLayout的TabView协同滚动
我们回头来看 3.2.3中setScrollPosition(...)方法
在3.2.3.1中我们知道indicator的滚动是通过mTabStrip.setIndicatorPositionFromTabPosition(position,
positionOffset)实现的。那TabView的滚动呢?我们知道TabLayout是继承HorizonScrollView天生就是一个可以横行滚动的View,所以,我们只需要调用scrollTo(int
x, int y)方法就可以实现横向滚动。
这里x方向的偏移量调用calculateScrollXForTab(position, positionOffset)实时计算得出,y方向的偏移量为0。
至此,我们就明白了TabLayout是如何随ViewPager的滚动而滚动的。
3.2.4 Tab选中状态
|
|
调用View的setSelected(boolean)方法。
4. 开源项目中的使用
开源项目中使用TabLayout的例子特别多, 这里给出我写的一个项目:
- SwipeToLoadLayout的demo
发现文章,代码有误、对内容有疑问,请在Issue上面提单,技术讨论可以给我Email,欢迎关注左边微信公众号或加QQ群获取更多信息。
