本文为转载&地址:项目地址: 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群获取更多信息。