viewpage2或者recyleview滑动时间长的问题分析与解决

问题描述:
项目的tab切换从viewpage升级到viewpage2,发现在tab页切换的时候滑动时间长,尤其是从第一页切换到最后一页时,时间太长

问题分析:

查看viewpage2源码,viewpage2条目切换使用如下代码:

void setCurrentItemInternal(int item, boolean smoothScroll) {
         .........
        mScrollEventAdapter.notifyProgrammaticScroll(item, smoothScroll);
        if (!smoothScroll) {//非smoothscroll,使用非动画模块
            mRecyclerView.scrollToPosition(item);
            return;
        }

        // For smooth scroll, pre-jump to nearby item for long jumps.
        if (Math.abs(item - previousItem) > 3) {
            mRecyclerView.scrollToPosition(item > previousItem ? item - 3 : item + 3);
			//SmoothScrollToPosition是一个runnable,run方法执行的是mRecyclerView.smoothScrollToPosition(item)
            mRecyclerView.post(new SmoothScrollToPosition(item, mRecyclerView));
        } else {
            mRecyclerView.smoothScrollToPosition(item);
        }
    }

从以上代码可以看到,viewpage2的tab切换,使用的是recyclerView.smoothScrollToPosition,当新tab与当前tab的索引差值大于3时,先快速切换到item-3或者item+3的位置,再通过mRecyclerView.smoothScrollToPosition动画到指定的位置,smoothScrollToPosition调用的是layoutmanager的smoothScrollToPosition,在viewpager2中使用的事linerlayoutmanager
分析linerlayoutmanager.smoothScrollToPosition

 @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
            int position) {
        LinearSmoothScroller linearSmoothScroller =
                new LinearSmoothScroller(recyclerView.getContext());
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }
    //1、创建LinearSmoothScroller,用于后续启动scroll,注意此处的targetview = findViewByPosition,只会返回可见view,否则返回null
    
    
  public void startSmoothScroll(SmoothScroller smoothScroller) {
            if (mSmoothScroller != null && smoothScroller != mSmoothScroller
                    && mSmoothScroller.isRunning()) {
                mSmoothScroller.stop();
            }
            mSmoothScroller = smoothScroller;
            mSmoothScroller.start(mRecyclerView, this);
        }

 void start(RecyclerView recyclerView, LayoutManager layoutManager) {

          .........
            mRecyclerView = recyclerView;
            mLayoutManager = layoutManager;
            if (mTargetPosition == RecyclerView.NO_POSITION) {
                throw new IllegalArgumentException("Invalid target position");
            }
            mRecyclerView.mState.mTargetPosition = mTargetPosition;
            mRunning = true;
            mPendingInitialRun = true;
            mTargetView = findViewByPosition(getTargetPosition());
            onStart();
            mRecyclerView.mViewFlinger.postOnAnimation();

            mStarted = true;
        }
//2、通过mRecyclerView.mViewFlinger.postOnAnimation启动动画执行,postOnAnimation最终执行到 ViewCompat.postOnAnimation(RecyclerView.this, this);会执行mViewFlinger的run方法,在run方法中,scroller.computeScrollOffset(),由于scroller是第一次创建的或者还是上一次执行的scroller,此处会返回false,最终执行到smoothScroller.onAnimation(0, 0)

 @Override
        public void run() {
             //........
            final OverScroller scroller = mOverScroller;
            if (scroller.computeScrollOffset()) {
                //更新滑动距离
                final int x = scroller.getCurrX();
                final int y = scroller.getCurrY();
                int unconsumedX = x - mLastFlingX;
                int unconsumedY = y - mLastFlingY;
                mLastFlingX = x;
                mLastFlingY = y;
                int consumedX = 0;
                int consumedY = 0;

                 //.........
            }
 SmoothScroller smoothScroller = mLayout.mSmoothScroller;
            // call this after the onAnimation is complete not to have inconsistent callbacks etc.
            if (smoothScroller != null && smoothScroller.isPendingInitialRun()) {
                smoothScroller.onAnimation(0, 0);
            }

              //.........
        }


void onAnimation(int dx, int dy) {
             //.........

            mPendingInitialRun = false;
//如果有targetview,会执行mRecyclingAction.runIfNecessary和onTargetFound方法并且会stop掉当前的srcoller,mRecyclingAction.runIfNecessary内部会执行真正的scroll逻辑。如果targetview是空,通过onSeekTargetStep会预测移动的距离,并设置相应的滚动参数
            if (mTargetView != null) {
                // verify target position
                if (getChildPosition(mTargetView) == mTargetPosition) {
                    onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
                    mRecyclingAction.runIfNecessary(recyclerView);
                    stop();
                } else {
                    Log.e(TAG, "Passed over target position while smooth scrolling.");
                    mTargetView = null;
                }
            }
             if (mRunning) {
                onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction);
                boolean hadJumpTarget = mRecyclingAction.hasJumpTarget();
                mRecyclingAction.runIfNecessary(recyclerView);
                if (hadJumpTarget) {
                    // It is not stopped so needs to be restarted
                    if (mRunning) {
                        mPendingInitialRun = true;
                        recyclerView.mViewFlinger.postOnAnimation();
                    }
                }
            }
        }

//执行action.update,主要讲一开始启动滑动创建的LinearSmoothScroller的mDecelerateInterpolator设置到action中,这一点很重要,后面会用到,同时这里通过calculateTimeForDeceleration,通过距离计算滑动时间,距离越远滑动时间越长
    protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
        final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
        final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());
        final int distance = (int) Math.sqrt(dx * dx + dy * dy);
        final int time = calculateTimeForDeceleration(distance);
        if (time > 0) {
            action.update(-dx, -dy, time, mDecelerateInterpolator);
        }
    }

//runIfNecessary会执行到ViewFlinger.smoothScrollBy
void runIfNecessary(RecyclerView recyclerView) {
			 //直接跳转
                if (mJumpToPosition >= 0) {
                    final int position = mJumpToPosition;
                    mJumpToPosition = NO_POSITION;
                    recyclerView.jumpToPositionForSmoothScroller(position);
                    mChanged = false;
                    return;
                }
                if (mChanged) {
                    validate();
                    //执行到mViewFlinger.smoothScrollBy,执行滑动
                    recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator);
                    mConsecutiveUpdates++;
                    //.........
                    mChanged = false;
                } else {
                    mConsecutiveUpdates = 0;
                }
            }


//smoothScrollBy中,发现如果mInterpolator != interpolator,则会创建一个新的OverScroller进行最终的滑动处理,由于传入的interpolator是最初创建的LinearSmoothScroller的创建对象mDecelerateInterpolator,所以这里肯定会创建一个新的OverScroller进行滑动处理,同时会设置  setScrollState(SCROLL_STATE_SETTLING),更新状态
 public void smoothScrollBy(int dx, int dy, int duration,
                @Nullable Interpolator interpolator) {

            // Handle cases where parameter values aren't defined.
            if (duration == UNDEFINED_DURATION) {
                duration = computeScrollDuration(dx, dy);
            }
            if (interpolator == null) {
                interpolator = sQuinticInterpolator;
            }

            // If the Interpolator has changed, create a new OverScroller with the new
            // interpolator.
            if (mInterpolator != interpolator) {
                mInterpolator = interpolator;
                mOverScroller = new OverScroller(getContext(), interpolator);
            }

            // Reset the last fling information.
            mLastFlingX = mLastFlingY = 0;

            // Set to settling state and start scrolling.
            setScrollState(SCROLL_STATE_SETTLING);
            mOverScroller.startScroll(0, 0, dx, dy, duration);

            if (Build.VERSION.SDK_INT < 23) {
                // b/64931938 before API 23, startScroll() does not reset getCurX()/getCurY()
                // to start values, which causes fillRemainingScrollValues() put in obsolete values
                // for LayoutManager.onLayoutChildren().
                mOverScroller.computeScrollOffset();
            }

            postOnAnimation();
        }

总结viewpager2和RecyclerView滑动过程:
1、viewpager2执行setCurrentItemInternal,触发mRecyclerView.smoothScrollToPosition
2、RecyclerView执行到layoutmanager的smoothScrollToPosition,创建LinearSmoothScroller,用于后续启动scroll
3、mRecyclerView.mViewFlinger.postOnAnimation,执行到LinearSmoothScroller的onAnimation,进而触发mRecyclingAction.runIfNecessary方法
4、runIfNecessary中recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator)方法,这里会创建一个新的OverScroller执行真正的滑动

解决方案:
我们发现最终的滑动时通过OverScroller执行的,如果我们能够使用自定义的OverScroller,则可以控制传入的时间,但是由于每次都是创建新的OverScroller,所以替换的时机比较重要,我们发现在smoothScrollBy中会执行setScrollState(SCROLL_STATE_SETTLING)更新当前状态,代码如下,此时会触发dispatchOnScrollStateChanged方法,因此如果我们在recyleview的onScrollStateChanged中监听到SCROLL_STATE_SETTLING时,反射的修改OverScroller,能够达到目标。

 void setScrollState(int state) {
        if (state == mScrollState) {
            return;
        }
        if (DEBUG) {
            Log.d(TAG, "setting scroll state to " + state + " from " + mScrollState,
                    new Exception());
        }
        mScrollState = state;
        if (state != SCROLL_STATE_SETTLING) {
            stopScrollersInternal();
        }
        dispatchOnScrollStateChanged(state);
    }

最终解决代码如下:

public static void setViewPage2ScrollDuration(ViewPager2 viewPager2, int duration) {
        try {
        //反射获取viewpage2的mRecyclerView,并设置滑动监听
            Field recyclerView = ViewPager2.class.getDeclaredField("mRecyclerView");
            recyclerView.setAccessible(true);
            RecyclerView view = (RecyclerView) recyclerView.get(viewPager2);
            view.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                    super.onScrollStateChanged(recyclerView, newState);
                    //滑动状态为RecyclerView.SCROLL_STATE_SETTLING时,反射修改mOverScroller
                    if (newState == RecyclerView.SCROLL_STATE_SETTLING) {
                        setRecyclerviewScrollDuration(view, duration);
                    }
                }
            });

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }


    }

    public static void setRecyclerviewScrollDuration(RecyclerView recyclerView, int duration) {
        if (recyclerView == null) return;
        try {
        //反射获取RecyclerView的mViewFlinger
            Field mViewFlinger = RecyclerView.class.getDeclaredField("mViewFlinger");
            mViewFlinger.setAccessible(true);
            Class viewFlingerClass = Class.forName("androidx.recyclerview.widget.RecyclerView$ViewFlinger");
  
 //反射获取mViewFlinger的mOverScroller
            Field mOverScroller = viewFlingerClass.getDeclaredField("mOverScroller");
            mOverScroller.setAccessible(true);

         //自定义CustomOverScroll,反射修改
            CustomOverScroll scroll = new CustomOverScroll(recyclerView.getContext(), new DecelerateInterpolator());
            scroll.setDuration(duration);
             mOverScroller.set(mViewFlinger.get(recyclerView), scroll);
        } catch (NoSuchFieldException | ClassNotFoundException | IllegalAccessException e) {
            e.printStackTrace();
        }

public class CustomOverScroll extends OverScroller {
    private int mDuration = -1;

    public CustomOverScroll(Context context) {
        super(context);
    }

    public CustomOverScroll(Context context, Interpolator interpolator) {
        super(context, interpolator);
    }

    public CustomOverScroll(Context context, Interpolator interpolator, float bounceCoefficientX, float bounceCoefficientY) {
        super(context, interpolator, bounceCoefficientX, bounceCoefficientY);
    }

    public CustomOverScroll(Context context, Interpolator interpolator, float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
        super(context, interpolator, bounceCoefficientX, bounceCoefficientY, flywheel);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy) {
        if (mDuration > 0) {
            super.startScroll(startX, startY, dx, dy, mDuration);
        } else {
            super.startScroll(startX, startY, dx, dy);
        }
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        if (mDuration > 0) {
            super.startScroll(startX, startY, dx, dy, mDuration);
        } else {
            super.startScroll(startX, startY, dx, dy, duration);
        }
    }

    public void setDuration(int mDuration) {
        this.mDuration = mDuration;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值