读源码:TabLayout
Posted Iaouei
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读源码:TabLayout相关的知识,希望对你有一定的参考价值。
一,内部类及分析其关系:
Tab类和TabView类和SlidingTabStrip类为TabLayout提供了三个基本的元素。
TabLayoutOnPageChangeListener和ViewPagerOnTabSelectedListener实现了ViewPager类的两个接口,作用是监听ViewPager页面改变和Tab选中状态。
PagerAdapterObserver为观察者监控PagerAdapter数据变化。
所以在自定义TabLayout前,先设计好Tab模式,并且官方扩展了一个TabView子控件件。
自定义TabLayout的工作,还要通过接口完成和ViewPager的交互,通过PagerAdapterObserver,确保TabLayout能够正常工作。
二,内部类:Tab模型
Tab内部类定义了Tab的成员变量,并set和get方法,然后又封装了Tab的属性设置方法,如:
@NonNull
public Tab setIcon(@Nullable Drawable icon)
mIcon = icon;
if (mPosition >= 0)
mParent.updateTab(mPosition);
return this;
/**
* Set the icon displayed on this tab.
*
* @param resId A resource ID referring to the icon that should be displayed
* @return The current instance for call chaining
*/
@NonNull
public Tab setIcon(@DrawableRes int resId)
return setIcon(AppCompatDrawableManager.get().getDrawable(mParent.getContext(), resId));
三,内部类:自定义的TabView
这个子view在应用中很方便,经常用到,设计它的作用是让Tab同时显示文字和图片,继承自LinearLayout。
其构造器设置了TabView在TabLayout中的状态,这些状态属性都是固定的不可变的,所以也决定TabView设置受到限制。
public TabView(Context context)
super(context);
if (mTabBackgroundResId != 0)
setBackgroundDrawable(AppCompatDrawableManager.get().getDrawable(context, mTabBackgroundResId));//设置背景
ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,mTabPaddingEnd, mTabPaddingBottom);//设置边距
setGravity(Gravity.CENTER);//设置位置
setOrientation(VERTICAL);//设置方向
四,SlidingTabStrip:Tab下面的滑线
这个自定义view SlidingTabStrip是私有内部类,它继承自LinearLayout,作用为获取tab的最宽宽度,设置SlidingTabStrip的宽度,并设置一个动画,随着tab的改变绘制SlidingTabStrip。它有一个构造器:
SlidingTabStrip(Context context)
super(context);
setWillNotDraw(false);//重写draw()不起作用时,需要添加这么一句
mSelectedIndicatorPaint = new Paint();
单看这个构造器就知道使用SlidingTabStrip超级简单,在这个构造器源码中,有一个地方需要注意,因为在我们自定义view中会用到,就是setWillNotDraw(false);但看这句的字面意思就不难猜到,双重否定等于肯定,让它进行绘制。当我们自定义的view在onDrow()不起作用时,一种方法是在构造器里设置背景颜色,一种就是添加这句语句。原因可以进一步参考ViewGroup为什么不会调用onDraw
代码结构极易一目了然。在onMeasure()获取最宽tab宽度,并设置为此控件的宽度;在onLayout()进行动画并更新Indicator位置;最后draw()。
五,ViewPager的监听器:TabLayoutOnPageChangeListener
这个类是实现ViewPager.OnPageChangeListener的一个内部类,作用是监听到ViewPager页面改变的时候,更新tab和Indicator。首先注意这个类是public,并且被static修饰,static一般用来修饰成员变量或或成员函数,有static修饰内部类有什么特别呢,那就是在外面使用这个类的时候,不需要先new内部类所在的类(如在这里为TabLayout)的实例,可以直接new这个内部类的实例,比如:
TabLayout.TabLayoutOnPageChangeListener listener=new TabLayout.TabLayoutOnPageChangeListener;
如果想进一步了解可以查看用static关键字修饰类
其它没啥可解释的,方便理解,贴出主要的代码块:
@Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels)
final TabLayout tabLayout = mTabLayoutRef.get();
if (tabLayout != null)
// 只更新选中的 text if we're not settling, or we are settling after
// being dragged
final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
mPreviousScrollState == SCROLL_STATE_DRAGGING;
// 更新indicator if we're not settling after being idle. This is caused
// 这是由setCurrentItem() 引起的,并且会由onPageSelected()传来的动画处理。
final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
&& mPreviousScrollState == SCROLL_STATE_IDLE);
tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
@Override
public void onPageSelected(int position)
final TabLayout tabLayout = mTabLayoutRef.get();
if (tabLayout != null && tabLayout.getSelectedTabPosition() != position)
//选中 tab, 如果没有 dragged/settled只更新indicator
// (since onPageScrolled will handle that).
final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
|| (mScrollState == SCROLL_STATE_SETTLING
&& mPreviousScrollState == SCROLL_STATE_IDLE);
tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
private void reset() //此内部类外部调用,重置
mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
六,TabLayout的tab选中监听:ViewPagerOnTabSelectedListener
方便理解,贴出这个内部类和上个内部类的类声明:
public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener
public static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener
除了这个内部类也是public static外,与ViewPager.OnPageChangeListener相对应的是这里实现的是TabLayout.OnTabSelectedListener;这个接口是TabLayout中定义的接口,在这里实现的作用是使TabLayout中的tab能够和ViewPager的pager保持同步。
然后在TabLayout中有一个方法:
public void setOnTabSelectedListener(OnTabSelectedListener onTabSelectedListener)
mOnTabSelectedListener = onTabSelectedListener;
mOnTabSelectedListener是这个内部类ViewPagerOnTabSelectedListener的一个实例,我们使用TabLayout时,就是通过调用这个方法设置tab的监听事件。
七,数据监听:PagerAdapterObserver
这是一个观察者,继承自DataSetObserver,DataSetObserver我不多说了,关于介绍推荐Android中的观察者DataSetObservable和DataSetObserver ,用法如果不理解可以先复习一下观察者模式,然后再百度一下。
这儿,PagerAdapterObserver实现的作用很简单,源码为下:
//监听适配器数据的变化
private class PagerAdapterObserver extends DataSetObserver
//数据集改变时
@Override
public void onChanged()
populateFromPagerAdapter();
//当数据集失效时
@Override
public void onInvalidated()
populateFromPagerAdapter();
数据集改变和数据集失效执行的是同一个动作。populateFromPagerAdapter()是放在TabLayout中的一个方法,作用为按照page的数量添加tab,并设置文字,然后又添了一道保障。方便理解,这儿贴出:
private void populateFromPagerAdapter()
removeAllTabs();
if (mPagerAdapter != null)
final int adapterCount = mPagerAdapter.getCount();//得到page的数量
for (int i = 0; i < adapterCount; i++)
addTab(newTab().setText(mPagerAdapter.getPageTitle(i)), false);//添加tab,并设置文字
// 确保映射到了当前设置的 ViewPager item
if (mViewPager != null && adapterCount > 0)
final int curItem = mViewPager.getCurrentItem();
if (curItem != getSelectedTabPosition() && curItem < getTabCount())
selectTab(getTabAt(curItem));//选中tab
else
removeAllTabs();
八,从头到尾缕TabLayout
这个类,先是设置了一些默认值,然后定义了两个tabs模式:MODE_SCROLLABLE和MODE_FIXED,两个tabs放进tablayout的位置:GRAVITY_FILL和GRAVITY_CENTER;然后设置了一个tab选中监听器:public interface OnTabSelectedListener,这个接口在上面介绍过的一个内部类已经实现;再接着是声明的TabLayout一些属性和一些用到的类的实例。
然后就到了构造器,看第三个构造器:
//第三个构造器
public TabLayout(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
ThemeUtils.checkAppCompatTheme(context);
// 使Scroll Bar不可用
setHorizontalScrollBarEnabled(false);
// 添加TabStrip
mTabStrip = new SlidingTabStrip(context);
addView(mTabStrip, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
//获取属性值
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
defStyleAttr, R.style.Widget_Design_TabLayout);
//加载属性。。。
// 加载tab mode and gravity
applyModeAndGravity();
这个构造器可以分为四个小步,获取属性值后的加载属性有兴趣可以看看源码,重点看applyModeAndGravity();理解如何加载tab mode 和 gravity的,set get有兴趣看源码,applyModeAndGravity(源码为:
//加载设置的模式和布局方式。。。
private void applyModeAndGravity()
int paddingStart = 0;
if (mMode == MODE_SCROLLABLE)
// If we're scrollable, or fixed at start, inset using padding
paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);
switch (mMode)
case MODE_FIXED:
mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
break;
case MODE_SCROLLABLE:
mTabStrip.setGravity(GravityCompat.START);
break;
updateTabViews(true);//更新TabViews
private void updateTabViews(final boolean requestLayout)
for (int i = 0; i < mTabStrip.getChildCount(); i++)
View child = mTabStrip.getChildAt(i);
child.setMinimumWidth(getTabMinWidth());
updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
if (requestLayout)
child.requestLayout();
构造器接下来就是一些属性的set get方法,和执行Tab事务的方法。我这里跳过直接看TabLayout最关键逻辑部分setupWithViewPager()。
public void setupWithViewPager(@Nullable final ViewPager viewPager)
if (mViewPager != null && mPageChangeListener != null)
// 如果已经和ViewPager建立关系, 从它移除
mViewPager.removeOnPageChangeListener(mPageChangeListener);
if (viewPager != null)
final PagerAdapter adapter = viewPager.getAdapter();
if (adapter == null)
throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
mViewPager = viewPager;
// 向ViewPager添加我们自定义的 OnPageChangeListener
if (mPageChangeListener == null)
mPageChangeListener = new TabLayoutOnPageChangeListener(this);
mPageChangeListener.reset();//重置
viewPager.addOnPageChangeListener(mPageChangeListener);//添加
// 再添加tab selected listener 去设置 ViewPager当前的item
setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(viewPager));
// 最后把 pager adapter转移过来
setPagerAdapter(adapter, true);//设置PagerAdapter
else
// 我们已经给出了个空的ViewPager ,所以需要去清除内部的状态 ,监听器,观察者,
mViewPager = null;
setOnTabSelectedListener(null);
setPagerAdapter(null, true);
setupWithViewPager()做的工作是和ViewPager建立关系,将PagerAdapter适配到tabLayout,所以其中关键又是setPagerAdapter():
//设置PagerAdapter
private void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver)
if (mPagerAdapter != null && mPagerAdapterObserver != null)
// 如果我们已经有了PagerAdapter, 取消注册observer
mPagerAdapter.unregisterDataSetObserver(mPagerAdapterObserver);
mPagerAdapter = adapter;
if (addObserver && adapter != null)
//在新的adapter注册观察者
if (mPagerAdapterObserver == null)
mPagerAdapterObserver = new PagerAdapterObserver();
adapter.registerDataSetObserver(mPagerAdapterObserver);
// 最后确保映射到了这个新的adapter
populateFromPagerAdapter();
setPagerAdapter()注册一个观察者,观察adapter的变化,然后根据变化映射到tab,是tab也发生相应变化,这个步骤放在了populateFromPagerAdapter(),这个方法在前面已经贴出,其做的事情是根据情况添加addTab(),选中selectTab(),移除removeAllTabs()。其中选中selectTab()逻辑有稍微复杂些:
//选中tab
void selectTab(Tab tab, boolean updateIndicator)
if (mSelectedTab == tab)
if (mSelectedTab != null)
if (mOnTabSelectedListener != null)
mOnTabSelectedListener.onTabReselected(mSelectedTab);//释放选中的tab
animateToTab(tab.getPosition());//进行动画
else
if (updateIndicator) //如果updateIndicator
final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
if (newPosition != Tab.INVALID_POSITION)
setSelectedTabView(newPosition);//设置选中的TabView
if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
&& newPosition != Tab.INVALID_POSITION)
// 如果没有正确的tab, 只 draw the indicator
setScrollPosition(newPosition, 0f, true);
else
animateToTab(newPosition);//进行动画
if (mSelectedTab != null && mOnTabSelectedListener != null)
mOnTabSelectedListener.onTabUnselected(mSelectedTab);
mSelectedTab = tab;
if (mSelectedTab != null && mOnTabSelectedListener != null)
mOnTabSelectedListener.onTabSelected(mSelectedTab);
//动画 改变Tab时的动画
private void animateToTab(int newPosition)
if (newPosition == Tab.INVALID_POSITION)
return;
if (getWindowToken() == null || !ViewCompat.isLaidOut(this)
|| mTabStrip.childrenNeedLayout())
// If we don't have a window token, or we haven't been laid out yet just draw the new
// position now
setScrollPosition(newPosition, 0f, true);
return;
final int startScrollX = getScrollX();
final int targetScrollX = calculateScrollXForTab(newPosition, 0);
if (startScrollX != targetScrollX)
if (mScrollAnimator == null)
mScrollAnimator = ViewUtils.createAnimator();
mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
mScrollAnimator.setDuration(ANIMATION_DURATION);
mScrollAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimatorCompat animator)
scrollTo(animator.getAnimatedIntValue(), 0);
);
mScrollAnimator.setIntValues(startScrollX, targetScrollX);
mScrollAnimator.start();
// Now animate the indicator
mTabStrip.animateIndicatorToPosition(newPosition, ANIMATION_DURATION);
//计算水平方向的滑动
private int calculateScrollXForTab(int position, float positionOffset)
if (mMode == MODE_SCROLLABLE)
final View selectedChild = mTabStrip.getChildAt(position);
final View nextChild = position + 1 < mTabStrip.getChildCount()
? mTabStrip.getChildAt(position + 1)
: null;
final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;
return selectedChild.getLeft()
+ ((int) ((selectedWidth + nextWidth) * positionOffset * 0.5f))
+ (selectedChild.getWidth() / 2)
- (getWidth() / 2);
return 0;
其他的方法都比较常见,很好理解,就不做解释了。最后推荐一篇非常不错的使用TabLayout及要点深入博文,希望对完全理解有所帮助:TabLayout:另一种Tab的实现方式
以上是关于读源码:TabLayout的主要内容,如果未能解决你的问题,请参考以下文章
在jdbc基础上进阶一小步的C3p0 连接池(DBCP 不能读xml配置文件,已淘汰) 和DBUtils 中两个主要类QueryRunner和ResultSetHandler的使用
错误记录TabLayout 升级支持库版本后报错 ( support:design 支持库升级到 28.0.0 后源码发生变更 )