tablayout 滚动模式_TabLayout实现简书首页滑动效果

2、实现效果

jianshu.gif

jstablayout.gif

Screenshot_1542029939.png 3、实现过程

对比原生的tablayou和简书的,你会发现几处不同,一是简书的tabIndicator是圆角矩形,二是背景图的宽度是跟随文字宽度变化的。

3.1、源码追踪

首先呢,

我们熟知tablayout常规使用如下:

privatevoidinittb2(){

FragmentPagerAdapter fragmentPagerAdapter = newFragmentPagerAdapter(getSupportFragmentManager()) {

@Override

publicFragment getItem(intposition){

returnMyFragment.newInstance(position);

}

@Override

publicintgetCount(){

returntitles.size();

}

@Nullable

@Override

publicCharSequence getPageTitle(intposition){

returntitles.get(position);

}

};

vp2.setAdapter(fragmentPagerAdapter);

tb2.setupWithViewPager(vp2);

跟进去setupWithViewPager()

publicvoidsetupWithViewPager(@Nullable ViewPager viewPager){

setupWithViewPager(viewPager, true);

}

publicvoidsetupWithViewPager(@Nullable finalViewPager viewPager, booleanautoRefresh){

setupWithViewPager(viewPager, autoRefresh, false);

}

privatevoidsetupWithViewPager(@Nullable finalViewPager viewPager, booleanautoRefresh,

booleanimplicitSetup){

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 = newTabLayoutOnPageChangeListener( this);

}

mPageChangeListener.reset();

viewPager.addOnPageChangeListener(mPageChangeListener);

// Now we'll add a tab selected listener to set ViewPager's current item

mCurrentVpSelectedListener = newViewPagerOnTabSelectedListener(viewPager);

addOnTabSelectedListener(mCurrentVpSelectedListener);

finalPagerAdapter 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); ------------tips1

}

// Add a listener so that we're notified of any adapter changes

if(mAdapterChangeListener == null) {

mAdapterChangeListener = newAdapterChangeListener();

}

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;

}

上面主要是tablayout跟viewpager滑动之间的关系绑定

从tips1的地方一直跟进去你会发现

privatevoidaddTabView(Tab tab){

finalTabView tabView = tab.mView;

mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());

}

回到第一张类结构图,我知道了每一个tabItem是添加在private final SlidingTabStrip mTabStrip在这个对象中的,

然后我在SlidingTabStrip 的draw方法看到了

@Override

publicvoiddraw(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);

}

}

即是绘制滑动条的代码。

我将其改为:

@Override

protectedvoiddispatchDraw(Canvas canvas){

// Thick colored underline below the current selection

RectF r2 = newRectF(); //RectF对象

r2.left = mIndicatorLeft; //左边

r2.top = (getHeight() - mSelectedIndicatorHeight) / 2; //上边

r2.right = mIndicatorRight; //右边

r2.bottom = r2.top + mSelectedIndicatorHeight;

mSelectedIndicatorPaint.setAntiAlias( true);

if(mIndicatorLeft >= 0&& mIndicatorRight > mIndicatorLeft) {

canvas.drawRoundRect(r2, mSelectedIndicatorHeight / 2, mSelectedIndicatorHeight / 2, mSelectedIndicatorPaint);

}

super.dispatchDraw(canvas);

}

请注意我这里用了dispatchdraw,这个方法我找了很久,我尝试把tabindicator的高度设置为30,发现他会把文字挡住。我就百度搜索关键字

image.png

我百度了很多,没找到想要的,基本都是setLayer这个方法相关,很多博客也是讲的是draw的时候两个图形隐藏与显示还有透明度的关系

终于,

draw.png

我找到了,

image.png

然后成功将tabIndicator绘制到了文字下方。嘤嘤嘤,当时还是很开心的。

3.2、修改

然后呢

它的宽度是平局值分布的。我从draw方法里知道绘制tabIndicator的时候需要两个参数mIndicatorLeft 、mIndicatorRight

然后代码里搜索发现:

privatevoidupdateIndicatorPosition(){

finalView selectedTitle = getChildAt(mSelectedPosition);

intleft, 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);

}

通过一次次的断点调试,发现left,right的取值还是决定于selectedTitle.getLeft()和

selectedTitle.getRight()。

然后我看了SlidingTabStrip的onMeasure方法和TabView的onMeasure方法并没有发现什么端倪。

我的tablayout是设置为可滚动的scrollable,然后七个字的tab背景也能包裹,两个字和三个字的背景宽度差不多长,所以我想是不是tab有一个最小宽度然后自适应的。

width.png

然后我发现

mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);

privateintgetTabMinWidth(){

if(mRequestedTabMinWidth != INVALID_WIDTH) {

// If we have been given a min width, use it

returnmRequestedTabMinWidth;

}

// Else, we'll use the default value

returnmMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0;

}

所以我在dimes里面design_tab_scrollable_min_width设置了一个较小的值,

果然就是宽度自动包裹了。

宽度的问题解决了,但是滑动的时候我发现相邻一个tab滑动的时候很匀称,但是间隔几个tab的时候滑动不自然。于是我找到了下面代码:

voidanimateIndicatorToPosition(finalintposition, intduration){

if(mIndicatorAnimator != null&& mIndicatorAnimator.isRunning()) {

mIndicatorAnimator.cancel();

}

finalbooleanisRtl = ViewCompat.getLayoutDirection( this)

== ViewCompat.LAYOUT_DIRECTION_RTL;

finalView targetView = getChildAt(position);

if(targetView == null) {

// If we don't have a view, just update the position now and return

updateIndicatorPosition();

return;

}

finalinttargetLeft = targetView.getLeft();

finalinttargetRight = targetView.getRight();

finalintstartLeft;

finalintstartRight;

if(Math.abs(position - mSelectedPosition) <= 1) {

// If the views are adjacent, we'll animate from edge-to-edge

startLeft = mIndicatorLeft;

startRight = mIndicatorRight;

} else{

// Else, we'll just grow from the nearest edge

finalintoffset = dpToPx(MOTION_NON_ADJACENT_OFFSET);

if(position < mSelectedPosition) {

// We're going end-to-start

if(isRtl) {

startLeft = startRight = targetLeft - offset;

} else{

startLeft = startRight = targetRight + offset;

}

} else{

// We're going start-to-end

if(isRtl) {

startLeft = startRight = targetRight + offset;

} else{

startLeft = startRight = targetLeft - offset;

}

}

}

if(startLeft != targetLeft || startRight != targetRight) {

ValueAnimator animator = mIndicatorAnimator = newValueAnimator();

animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);

animator.setDuration(duration);

animator.setFloatValues( 0, 1);

animator.addUpdateListener( newValueAnimator.AnimatorUpdateListener() {

@Override

publicvoidonAnimationUpdate(ValueAnimator animator){

finalfloatfraction = animator.getAnimatedFraction();

setIndicatorPosition(

AnimationUtils.lerp(startLeft, targetLeft, fraction),

AnimationUtils.lerp(startRight, targetRight, fraction));

}

});

animator.addListener( newAnimatorListenerAdapter() {

@Override

publicvoidonAnimationEnd(Animator animator){

mSelectedPosition = position;

mSelectionOffset = 0f;

}

});

animator.start();

}

}

我做了如下修改

if(Math.abs(position - mSelectedPosition) <= 1) {

// If the views are adjacent, we'll animate from edge-to-edge

startLeft = mIndicatorLeft;

startRight = mIndicatorRight;

} else{

startLeft = mIndicatorLeft;

startRight = mIndicatorRight;

/* // Else, we'll just grow from the nearest edge

final int offset = dpToPx(MOTION_NON_ADJACENT_OFFSET);

if (position < mSelectedPosition) {

// We're going end-to-start

if (isRtl) {

startLeft = startRight = targetLeft - offset;

} else {

startLeft = startRight = targetRight + offset;

}

} else {

// We're going start-to-end

if (isRtl) {

startLeft = startRight = targetRight + offset;

} else {

startLeft = startRight = targetLeft - offset;

}

}*/

}

意思就是1个或多个tab滑动的时候都直接按indicator的左右端点计算。它原本的代码是把左右端点设为一样的了,暂时不知道为何要这么做。

4、最后

我的简书TabLayout就基本完成了。

github地址,需要源码的的自取。

https://github.com/honglei92/JSTabLayout。

●编号395,输入编号直达本文

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值