app连接网络那些事

RecyclerView的scrollToPosition和sm

2017-11-01  本文已影响0人  Frank_Kivi

在使用RecyclerView的过程中,不可避免的会遇到这样的需求,指定滚动到某个位置。通过查阅API我们知道有scrollToPosition和smoothScrollToPosition两个方法可以实现。
看方法名感觉差别不大,实际在使用过程中他们的效果完全不同。我们通过源码来分析一下。

scrollToPosition

 /**
     * Convenience method to scroll to a certain position.
     *
     * RecyclerView does not implement scrolling logic, rather forwards the call to
     * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
     * @param position Scroll to this adapter position
     * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)
     */
    public void scrollToPosition(int position) {
        if (mLayoutFrozen) {
            return;
        }
        stopScroll();
        if (mLayout == null) {
            Log.e(TAG, "Cannot scroll to position a LayoutManager set. "
                    + "Call setLayoutManager with a non-null argument.");
            return;
        }
        <!--调用了LayoutManager的方法-->
        mLayout.scrollToPosition(position);
        awakenScrollBars();
    }
    
     /**
     * <p>Scroll the RecyclerView to make the position visible.</p>
     *
     * <p>RecyclerView will scroll the minimum amount that is necessary to make the
     * target position visible. If you are looking for a similar behavior to
     * {@link android.widget.ListView#setSelection(int)} or
     * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
     * {@link #scrollToPositionWithOffset(int, int)}.</p>
     *
     * <p>Note that scroll position change will not be reflected until the next layout call.</p>
     *
     * @param position Scroll to this adapter position
     * @see #scrollToPositionWithOffset(int, int)
     */
     <!--注释说的很有意思,大概是两点,1是滚动的距离是使position可见的最小值。2是位置的变化需要在下次布局完成的时候才能显现出来。-->
    @Override
    public void scrollToPosition(int position) {
        mPendingScrollPosition = position;
        mPendingScrollPositionOffset = INVALID_OFFSET;
        if (mPendingSavedState != null) {
            mPendingSavedState.invalidateAnchor();
        }
        <!--这个方法是关键。-->
        requestLayout();
    }
        
         /**
      * Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView
      */
     public void requestLayout() {
         if (mRecyclerView != null) {
             mRecyclerView.requestLayout();
         }
     }
     @Override
    public void requestLayout() {
        if (mEatRequestLayout == 0 && !mLayoutFrozen) {
            super.requestLayout();
        } else {
            mLayoutRequestEaten = true;
        }
    }

最后就是调用view的requestLayout了。我们在前边的View源码分析上(View的生命周期,源码解析(上))里边提到过,requestLayout是一个非常霸道的方法,会把UI线程全部锁死直到自己完成才会再次解锁。所以这个方法经常调用会出现卡顿。

smoothScrollToPosition

 /**
     * Starts a smooth scroll to an adapter position.
     * <p>
     * To support smooth scrolling, you must override
     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
     * {@link SmoothScroller}.
     * <p>
     * {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
     * provide a custom smooth scroll logic, override
     * {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
     * LayoutManager.
     *
     * @param position The adapter position to scroll to
     * @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
     */
    public void smoothScrollToPosition(int position) {
        if (mLayoutFrozen) {
            return;
        }
        if (mLayout == null) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
                    + "Call setLayoutManager with a non-null argument.");
            return;
        }
        <!--也是调用LayoutManager的方法-->
        mLayout.smoothScrollToPosition(this, mState, position);
    }
    
      @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
            int position) {
        LinearSmoothScroller linearSmoothScroller =
                new LinearSmoothScroller(recyclerView.getContext());
        linearSmoothScroller.setTargetPosition(position);
        <!--新建了一个Scroller对象,然后调用startSmoothScroll-->
        startSmoothScroll(linearSmoothScroller);
    }
    
     /**
         * <p>Starts a smooth scroll using the provided SmoothScroller.</p>
         * <p>Calling this method will cancel any previous smooth scroll request.</p>
         * @param smoothScroller Instance which defines how smooth scroll should be animated
         */
        public void startSmoothScroll(SmoothScroller smoothScroller) {
            if (mSmoothScroller != null && smoothScroller != mSmoothScroller
                    && mSmoothScroller.isRunning()) {
                mSmoothScroller.stop();
            }
            mSmoothScroller = smoothScroller;
            <!--调用了Scroller的start方法-->
            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();
            <!--最后是关键,mViewFlinger其实也是一个Scroller,但是它是通过滚动播放动画的方式来实现的-->
            mRecyclerView.mViewFlinger.postOnAnimation();
        }

还可以补充一下,smoothScrollToPosition 其实可以理解成一个模拟的滑动操作,会回调那个滑动监听的回调方法。而 scrollToPosition 相当于直接把你想要的东西再重绘到界面上。
最后提醒两点:
1,smoothScrollToPosition和scrollToPosition都一样,并不是说让position处于第一个位置,而只是保证它可见。AbsListView 的对应方法也是一样的效果。
2,scrollToPosition的性能差了很多,不太建议使用,这也许就是为什么 AbsListView 只有一个 smoothScrollToPosition方法的原因。

上一篇下一篇

猜你喜欢

热点阅读