今天说说TabLayout和ViewPager是怎么实现相互绑定的,Google确实牛,通过这个setupWithViewPager 方法设置一下,他们就可以实现TabLayout与ViewPager互相变换,我们通过使用肯定可以得出tabLayout.setupWithViewPager(viewPager)有三个作用
第一个是从VIewPager中获取TabLayout的Title
第二个是ViewPager滑动时设置TabLayout的Title和indicator
第三个是点击TabLayout时ViewPager相应变化
来看看源码怎么实现的:
/**
* The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
*
* <p>This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with
* auto-refresh enabled.</p>
*
* @param viewPager the ViewPager to link to, or {@code null} to clear any previous link
*/
public void setupWithViewPager(@Nullable ViewPager viewPager) {
setupWithViewPager(viewPager, true);
}
/**
* The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
*
* <p>This method will link the given ViewPager and this TabLayout together so that
* changes in one are automatically reflected in the other. This includes scroll state changes
* and clicks. The tabs displayed in this layout will be populated
* from the ViewPager adapter's page titles.</p>
*
* <p>If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will
* trigger this layout to re-populate itself from the adapter's titles.</p>
*
* <p>If the given ViewPager is non-null, it needs to already have a
* {@link PagerAdapter} set.</p>
*
* @param viewPager the ViewPager to link to, or {@code null} to clear any previous link
* @param autoRefresh whether this layout should refresh its contents if the given ViewPager's
* content changes
*/
public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) {
setupWithViewPager(viewPager, autoRefresh, false);
}
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
boolean implicitSetup) {
if (mViewPager != null) {
// If we've already been setup with a ViewPager, remove us from it
if (mPageChangeListener != null) {
mViewPager.removeOnPageChangeListener(mPageChangeListener);
}
if (mAdapterChangeListener != null) {
mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener);
}
}
if (mCurrentVpSelectedListener != null) {
// If we already have a tab selected listener for the ViewPager, remove it
removeOnTabSelectedListener(mCurrentVpSelectedListener);
mCurrentVpSelectedListener = null;
}
if (viewPager != null) {
mViewPager = viewPager;
// Add our custom OnPageChangeListener to the ViewPager
if (mPageChangeListener == null) {
mPageChangeListener = new TabLayoutOnPageChangeListener(this);
}
mPageChangeListener.reset();
viewPager.addOnPageChangeListener(mPageChangeListener);
// Now we'll add a tab selected listener to set ViewPager's current item
mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
addOnTabSelectedListener(mCurrentVpSelectedListener);
final PagerAdapter adapter = viewPager.getAdapter();
if (adapter != null) {
// Now we'll populate ourselves from the pager adapter, adding an observer if
// autoRefresh is enabled
setPagerAdapter(adapter, autoRefresh);
}
// Add a listener so that we're notified of any adapter changes
if (mAdapterChangeListener == null) {
mAdapterChangeListener = new AdapterChangeListener();
}
mAdapterChangeListener.setAutoRefresh(autoRefresh);
viewPager.addOnAdapterChangeListener(mAdapterChangeListener);
// Now update the scroll position to match the ViewPager's current item
setScrollPosition(viewPager.getCurrentItem(), 0f, true);
} else {
// We've been given a null ViewPager so we need to clear out the internal state,
// listeners and observers
mViewPager = null;
setPagerAdapter(null, false);
}
mSetupViewPagerImplicitly = implicitSetup;
}
1、 三个重载的方法,不做多余的解释,直接看三个参数:viewPager就不用多说了,autoRefresh表示当ViewPager的内容发生时,是否刷新其内容,默认是刷新的,请看第一个方法的:setupWithViewPager(viewPager, true);说明默认是刷新,来看看第三个参数implicitSetup,其实这个参数我也不太懂,大概是一个是否设置ViewPager的判断
2、 以下几个方法分别是移除ViewPager的监听和选项卡的监听,所以我们在代码里设置的这三个监听都会失效的,此坑勿踩
if (mViewPager != null) {
// If we've already been setup with a ViewPager, remove us from it
if (mPageChangeListener != null) {
mViewPager.removeOnPageChangeListener(mPageChangeListener);
}
if (mAdapterChangeListener != null) {
mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener);
}
}
if (mCurrentVpSelectedListener != null) {
// If we already have a tab selected listener for the ViewPager, remove it
removeOnTabSelectedListener(mCurrentVpSelectedListener);
mCurrentVpSelectedListener = null;
}
3、 接着往下看
if (mPageChangeListener == null) {
mPageChangeListener = new TabLayoutOnPageChangeListener(this);
}
这句话就是他们自己给ViewPager添加的OnPageChangeListener,顺着这个信息,我们来看看他们是怎么重写的。以下是他们自定义的源码:
/**
* A {@link ViewPager.OnPageChangeListener} class which contains the
* necessary calls back to the provided {@link TabLayout} so that the tab position is
* kept in sync.
*
* <p>This class stores the provided TabLayout weakly, meaning that you can use
* {@link ViewPager#addOnPageChangeListener(ViewPager.OnPageChangeListener)
* addOnPageChangeListener(OnPageChangeListener)} without removing the listener and
* not cause a leak.
*/
public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
private final WeakReference<TabLayout> mTabLayoutRef;
private int mPreviousScrollState;
private int mScrollState;
public TabLayoutOnPageChangeListener(TabLayout tabLayout) {
mTabLayoutRef = new WeakReference<>(tabLayout);
}
@Override
public void onPageScrollStateChanged(final int state) {
mPreviousScrollState = mScrollState;
mScrollState = state;
}
@Override
public void onPageScrolled(final int position, final float positionOffset,
final int positionOffsetPixels) {
final TabLayout tabLayout = mTabLayoutRef.get();
if (tabLayout != null) {
// Only update the text selection if we're not settling, or we are settling after
// being dragged
final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
mPreviousScrollState == SCROLL_STATE_DRAGGING;
// Update the indicator if we're not settling after being idle. This is caused
// from a setCurrentItem() call and will be handled by an animation from
// onPageSelected() instead.
final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
&& mPreviousScrollState == SCROLL_STATE_IDLE);
tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
}
}
@Override
public void onPageSelected(final int position) {
final TabLayout tabLayout = mTabLayoutRef.get();
if (tabLayout != null && tabLayout.getSelectedTabPosition() != position
&& position < tabLayout.getTabCount()) {
// Select the tab, only updating the indicator if we're not being dragged/settled
// (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);
}
}
void reset() {
mPreviousScrollState = mScrollState = SCROLL_STATE_IDLE;
}
}
首先,我们看到的是一个构造器,构造器里面用了弱引用(WeakReference),为什么弱引用,不明白的同学可以去看看另一篇文章:http://blog.csdn.net/chenyuan_jhon/article/details/62898421
然后是重写OnPageChangeListener里面的三个方法,第一个方法是记录ViewPager滚动的状态的,让我们来看看ViewPager的三种状态
源码如下:
/**
* Indicates that the pager is in an idle, settled state. The current page
* is fully in view and no animation is in progress.
*/
public static final int SCROLL_STATE_IDLE = 0;
/**
* Indicates that the pager is currently being dragged by the user.
*/
public static final int SCROLL_STATE_DRAGGING = 1;
/**
* Indicates that the pager is in the process of settling to a final position.
*/
public static final int SCROLL_STATE_SETTLING = 2;
- SCROLL_STATE_IDLE:表示ViewPager处于空闲状态,何为空闲,就是ViewPager当前页面完全可见,并且没有做任何动作,包括动画、滚动等
- SCROLL_STATE_DRAGGING:表示ViewPager正在处于滚动状态
- SCROLL_STATE_SETTLING:表示滚动的最终位置
那么,理解了这三种状态,再理解下面的逻辑就容易很多了,
来看看第二个方法onPageScrolled里面写的都是什么意思
首先从弱引用中获取TabLayout实例,然后是两个boolean的变量:updateText代表的是是否更新TabLayout的title,updateIndicator代表的是是否更新Indicator,然后将处理交给了tabLayout.setScrollPosition(position,positionOffset,updateText, updateIndicator);这个方法,我们进去看看这个方法,源码如下:
/**
* Set the scroll position of the tabs. This is useful for when the tabs are being displayed as
* part of a scrolling container such as {@link android.support.v4.view.ViewPager}.
* <p>
* Calling this method does not update the selected tab, it is only used for drawing purposes.
*
* @param position current scroll position
* @param positionOffset Value from [0, 1) indicating the offset from {@code position}.
* @param updateSelectedText Whether to update the text's selected state.
*/
public void setScrollPosition(int position, float positionOffset, boolean updateSelectedText) {
setScrollPosition(position, positionOffset, updateSelectedText, true);
}
void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
boolean updateIndicatorPosition) {
final int roundedPosition = Math.round(position + positionOffset);
if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) {
return;
}
// Set the indicator position, if enabled
if (updateIndicatorPosition) {
mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
}
// Now update the scroll position, canceling any running animation
if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
mScrollAnimator.cancel();
}
scrollTo(calculateScrollXForTab(position, positionOffset), 0);
// Update the 'selected state' view as we scroll, if enabled
if (updateSelectedText) {
setSelectedTabView(roundedPosition);
}
}
- 最开始方法的重载不在赘述
- final int roundedPosition = Math.round(position + positionOffset);这个方法表示四舍五入,即如果滑动超过屏幕一半就在position的基础上+1,如果不到一半roundedPositon = position
然后这句
if (updateIndicatorPosition) {
mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
}
是设置指示器的位置,我们来看看是怎么设置的
void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
mIndicatorAnimator.cancel();
}
mSelectedPosition = position;
mSelectionOffset = positionOffset;
updateIndicatorPosition();
}
我们发现关键还是这个方法: updateIndicatorPosition();
于是我们可以只看这个方法的源码如下:
private void updateIndicatorPosition() {
final View selectedTitle = getChildAt(mSelectedPosition);
int left, right;
if (selectedTitle != null && selectedTitle.getWidth() > 0) {
left = selectedTitle.getLeft();
right = selectedTitle.getRight();
if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
// Draw the selection partway between the tabs
View nextTitle = getChildAt(mSelectedPosition + 1);
left = (int) (mSelectionOffset * nextTitle.getLeft() +
(1.0f - mSelectionOffset) * left);
right = (int) (mSelectionOffset * nextTitle.getRight() +
(1.0f - mSelectionOffset) * right);
}
} else {
left = right = -1;
}
setIndicatorPosition(left, right);
}
这里确定要绘制的指示器的左右位置,然后
void setIndicatorPosition(int left, int right) {
if (left != mIndicatorLeft || right != mIndicatorRight) {
// If the indicator's left/right has changed, invalidate
mIndicatorLeft = left;
mIndicatorRight = right;
ViewCompat.postInvalidateOnAnimation(this);
}
}
通过这个方法将left、right赋值给mIndicatorLeft、mIndicatorRight,最后调用draw方法绘制指示器,源码如下:
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// Thick colored underline below the current selection
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
}
}
至此,当ViewPager滚动的时候,指示器的绘制就已经完成了
然后,我们回到TabLayoutOnPageChangeListener的监听,继续分析源码,OnPageChangeListener的第三个方法onPageSelected方法源码如下:
@Override
public void onPageSelected(final int position) {
final TabLayout tabLayout = mTabLayoutRef.get();
if (tabLayout != null && tabLayout.getSelectedTabPosition() != position
&& position < tabLayout.getTabCount()) {
// Select the tab, only updating the indicator if we're not being dragged/settled
// (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);
}
}
首先我们需要知道重写这里是干什么的,我们知道,当ViewPager滚动之后,我们是不是要把TabLayout和ViewPager相互绑定?这里就是干这个的,我们看看是如何绑定的,这里就不贴源码了,直接拿出最关键的代码:setSelectedTabView(newPosition);设置绑定就是通过这个方法,其源码如下:
private void setSelectedTabView(int position) {
final int tabCount = mTabStrip.getChildCount();
if (position < tabCount) {
for (int i = 0; i < tabCount; i++) {
final View child = mTabStrip.getChildAt(i);
child.setSelected(i == position);
}
}
}
就是循环绑定其子控件,child.setSelected(i == position);这个方法不在解释,在View类中,可以自己看,如此,TabLayout与ViewPager的相互绑定就一目了然了