RecyclerView.smoothScrollToPosition了解一下

小狗狗.jpg

前言

最近開發(fā)中遇到了一個需求环鲤,需要RecyclerView滾動到指定位置后置頂顯示闯冷,當(dāng)時遇到這個問題的時候砂心,心里第一反應(yīng)是直接使用RecyclerView的smoothScrollToPosition()方法,實(shí)現(xiàn)對應(yīng)位置的平滑滾動蛇耀。但是在實(shí)際使用中發(fā)現(xiàn)并沒有到底自己想要的效果辩诞。本想著偷懶直接從網(wǎng)上Copy下,但是發(fā)現(xiàn)效果并不是很好纺涤。于是就自己去研究源碼译暂。

該系列文章分為兩篇文章抠忘。

什么是可見范圍囚灼?

在了解RecyclerView的smoothScrollToPosition方法之前,有個知識點(diǎn)祭衩,我覺得有必要給大家說一下灶体,因?yàn)槭褂胹moothScrollToPosition中遇到的問題都與可見范圍有關(guān)。

可見范圍.png

這里所說的可見范圍是掐暮,RecyclerView第一個可見item的位置與最后一個可見item的位置之間的范圍蝎抽。

一、實(shí)際使用中遇見的問題

如果當(dāng)前滾動位置在可見范圍內(nèi)路克,是不會發(fā)生滾動的

不會滾動.gif

當(dāng)前RecyclerView的可見范圍為0到9樟结,當(dāng)我們想要滾動到1位置時,發(fā)現(xiàn)當(dāng)前RecyclerView并沒有發(fā)生滾動精算。

二瓢宦、如果當(dāng)前滾動位置在可見范圍之后,會滾動到底部

滾動到底部.gif

當(dāng)前RecyclerView的可見范圍為0到9灰羽,當(dāng)我們想要滾動到10位置時刁笙,發(fā)現(xiàn)RecyclerView滾動了,且當(dāng)前位置對應(yīng)的視圖在RecyclreView的底部谦趣。

三、如果當(dāng)前滾動位置在可見范圍之前座每,會滾動到頂部

滾動到頂部.gif

這里我們滾動RecyclerView,使其可見范圍為10到19前鹅,當(dāng)我們分別滾動到1、3位置時峭梳,RecyclerView滾動了舰绘。且當(dāng)前位置對應(yīng)的視圖在RecyclerView的頂部。

二葱椭、RecyclerView smoothScrollToPosition源碼解析

到了這里我們發(fā)現(xiàn)對于不同情況捂寿,RecyclerView內(nèi)部處理是不一樣的,所以為了解決實(shí)際問題孵运,看源碼是必不可少的秦陋,接下來我們就一起跟著源碼走一遍。來看看RecyclerView具體的滾動實(shí)現(xiàn)治笨。(這里需要提醒大家的是這里我采用的是LinearLayoutManager驳概,本文章都是基于LinearLayoutManager進(jìn)行分析的)

  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;
        }
        mLayout.smoothScrollToPosition(this, mState, position);
    }

mRecycler.smoothScrollToPosition()方法時赤嚼,內(nèi)部調(diào)用了LayoutManager的smoothScrollToPosition方法,LayoutManager中smoothScrollToPosition沒有實(shí)現(xiàn),具體實(shí)現(xiàn)在其子類中顺又,這里我們使用的是LinearLayoutManager更卒,所以我們來看看內(nèi)部是怎么實(shí)現(xiàn)的。

   @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
            int position) {
        LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext());
        scroller.setTargetPosition(position);//設(shè)定目標(biāo)位置
        startSmoothScroll(scroller);
    }

這里我們可以看到稚照,這里導(dǎo)致RecyclerView滑動的是LinearSmoothScroller蹂空,而LinearSmoothScroller的父類是RecyclerView.SmoothScroller,看到這里我相信大家都會感到一絲熟悉果录,因?yàn)槲覀冊趯丶?nèi)內(nèi)容進(jìn)行移動的時候上枕,我們都會使用到一個類,那就是Scroller雕憔。這里RecyclerView也自定了一個滑動Scroller姿骏。肯定是與滑動其內(nèi)部視圖相關(guān)的斤彼。

 public void startSmoothScroll(SmoothScroller smoothScroller) {
            if (mSmoothScroller != null && smoothScroller != mSmoothScroller
                    && mSmoothScroller.isRunning()) {
                mSmoothScroller.stop();
            }
            mSmoothScroller = smoothScroller;
            mSmoothScroller.start(mRecyclerView, this);
        }

繼續(xù)走startSmoothScroll,方法內(nèi)部判斷了如果正在計(jì)算坐標(biāo)值就停止分瘦,然后調(diào)用start()方法重新開始計(jì)算坐標(biāo)值。接著開始看start()方法琉苇。

 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;//設(shè)置當(dāng)前scroller已經(jīng)開始執(zhí)行
            mPendingInitialRun = true;
            mTargetView = findViewByPosition(getTargetPosition());//根據(jù)目標(biāo)位置查找相應(yīng)View,
            onStart();
            mRecyclerView.mViewFlinger.postOnAnimation();
        }

在start方法中嘲玫,會標(biāo)識當(dāng)前scroller的執(zhí)行狀態(tài),同時會根據(jù)滾動的位置去尋找對應(yīng)的目標(biāo)視圖并扇。這里需要著重提示一下去团,findViewByPosition()這個方法,該方法會在Recycler的可見范圍內(nèi)去查詢是否有目標(biāo)位置對應(yīng)的視圖穷蛹,例如土陪,現(xiàn)在RecyclerView的可見范圍為1-9,目標(biāo)位置為10肴熏,那么mTargetView =null,如果可見范圍為9-20鬼雀,目標(biāo)位置為1,那么mTargetView =null蛙吏。

最終調(diào)用RecyclerView的內(nèi)部類 ViewFlinger的postOnAnimation()方法源哩。

   class ViewFlinger implements Runnable {
       ....省略部分代碼
     void postOnAnimation() {
            if (mEatRunOnAnimationRequest) {
                mReSchedulePostAnimationCallback = true;
            } else {
                removeCallbacks(this);
                ViewCompat.postOnAnimation(RecyclerView.this, this);
            }
        }
   }

這里我們發(fā)現(xiàn),ViewFlinger其實(shí)一個Runnable,在postOnAnimation()內(nèi)部又將該Runnable發(fā)送出去了鸦做。那下面我們只用關(guān)心ViewFlinger的run()方法就行了励烦。

   @Override
        public void run() {
                   ...省略部分代碼
            final OverScroller scroller = mScroller;
            //獲得layoutManger中的SmoothScroller
            final SmoothScroller smoothScroller = mLayout.mSmoothScroller;
            if (scroller.computeScrollOffset()) {//如果是第一次走,會返回false
                   ...省略部分代碼
             }
            if (smoothScroller != null) {
                if (smoothScroller.isPendingInitialRun()) {
                    smoothScroller.onAnimation(0, 0);
                }
                if (!mReSchedulePostAnimationCallback) {
                    smoothScroller.stop(); //stop if it does not trigger any scroll
                }
            }
              ...省略部分代碼
        }

ViewFlinger的run()方法內(nèi)部實(shí)現(xiàn)比較復(fù)雜泼诱, 在該方法第一次執(zhí)行的時候坛掠,會執(zhí)行,if (scroller.computeScrollOffset()) ,其中scroller是ViewFlinger中的屬性mScroller的引用却音,其中mScroller會在ViewFlinger創(chuàng)建對象的時候改抡,就默認(rèn)初始化了。那么第一次判斷時候系瓢,因?yàn)檫€沒有開始計(jì)算阿纤,所以不會進(jìn)這個if語句塊,那么接下來就會直接走下面的語句:

     if (smoothScroller != null) {
                if (smoothScroller.isPendingInitialRun()) {
                    smoothScroller.onAnimation(0, 0);
                }
                if (!mReSchedulePostAnimationCallback) {
                    smoothScroller.stop(); //stop if it does not trigger any scroll
                }
            }

最后發(fā)現(xiàn)夷陋,只是走了一個onAnimation(0欠拾,0),繼續(xù)走該方法骗绕。

 private void onAnimation(int dx, int dy) {
            final RecyclerView recyclerView = mRecyclerView;
            if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) {
                stop();
            }
            mPendingInitialRun = false;
            if (mTargetView != null) {//判斷目標(biāo)視圖是否存在藐窄,如果存在則計(jì)算移動到位置需要移動的距離
                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) {//如果不存在,繼續(xù)去找
                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();
                    } else {
                        stop(); // done
                    }
                }
            }
        }

在onAnimation方法中酬土,判斷了目標(biāo)視圖是否為空荆忍,大家應(yīng)該還記得上文中,我們對目標(biāo)視圖的查找撤缴。如果當(dāng)前位置不在可見范圍之內(nèi)刹枉,那么mTargetView =null,就不回走對應(yīng)的判斷語句。繼續(xù)查看onSeekTargetStep()屈呕。

    protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {
        if (getChildCount() == 0) {
            stop();
            return;
        }
        //noinspection PointlessBooleanExpression
        if (DEBUG && mTargetVector != null
                && ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
            throw new IllegalStateException("Scroll happened in the opposite direction"
                    + " of the target. Some calculations are wrong");
        }
        mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);
        mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);

        if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {
            updateActionForInterimTarget(action);
        } // everything is valid, keep going

    }

直接通過代碼微宝,發(fā)現(xiàn)并不理解改函數(shù)要做什么樣的工作,這里我們只知道第一次發(fā)生滾動時虎眨,mInterimTargetDx=0與mInterimTargetDy =0蟋软,那么會走updateActionForInterimTarget()方法。

    protected void updateActionForInterimTarget(Action action) {
        // find an interim target position
        PointF scrollVector = computeScrollVectorForPosition(getTargetPosition());
        ...省略部分代碼
        normalize(scrollVector);
        mTargetVector = scrollVector;

        mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);
        mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
        
        //計(jì)算需要滾動的時間嗽桩,  默認(rèn)滾動距離岳守,TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
        final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
          
        //為了避免在滾動的時候出現(xiàn)停頓,我們會跟蹤onSeekTargetStep中的回調(diào)距離碌冶,實(shí)際上不會滾動超出實(shí)際的距離
        action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO),
                (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO),
                //這里存入的時間要比實(shí)際花費(fèi)的時間大一點(diǎn)棺耍。
                (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);
    }

根據(jù)官方文檔進(jìn)行翻譯:當(dāng)目標(biāo)滾動位置對應(yīng)視圖不在RecyclerView的可見范圍內(nèi),該方法計(jì)算朝向該視圖的方向向量并觸發(fā)平滑滾動种樱。默認(rèn)滾動的距離為12000(單位:px),(也就是說了為了滾動到目標(biāo)位置俊卤,會讓Recycler至多滾動12000個像素)嫩挤。

既然該方法計(jì)算了時間,那么我們就看看calculateTimeForScrolling()方法消恍,通過方法名我們就應(yīng)該了解了該方法是計(jì)算給定距離在默認(rèn)速度下需要滾動的時間岂昭。

    protected int calculateTimeForScrolling(int dx) {
        //這里對時間進(jìn)行了四舍五入操作。 
        return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
    }

其中MILLISECONDS_PER_PX 會在LinearSmoothScroller初始化的時候創(chuàng)建狠怨。

  public LinearSmoothScroller(Context context) {
      MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
    }
  

查看calculateSpeedPerPixel()方法

    private static final float MILLISECONDS_PER_INCH = 25f;// 默認(rèn)為移動一英寸需要花費(fèi)25ms
    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
    }

也就是說约啊,當(dāng)前滾動的速度是與屏幕的像素密度相關(guān)邑遏, 通過獲取當(dāng)前手機(jī)屏幕每英寸的像素密度,與每英寸移動所需要花費(fèi)的時間恰矩,用每英寸移動所需要花費(fèi)的時間除以像素密度就能計(jì)算出移動一個像素密度需要花費(fèi)的時間记盒。OK,既然我們已經(jīng)算出了移動一個像素密度需要花費(fèi)的時間,那么直接乘以像素外傅,就能算出移動該像素所需要花費(fèi)的時間了纪吮。

既然現(xiàn)在我們算出了時間,我們現(xiàn)在只用關(guān)心Action的update()方法到底是干什么的就好了萎胰,

        //保存關(guān)于SmoothScroller滑動距離信息
        public static class Action {
               ...省略代碼
             public void update(int dx, int dy, int duration, Interpolator interpolator) {
                mDx = dx;
                mDy = dy;
                mDuration = duration;
                mInterpolator = interpolator;
                mChanged = true;
            }
         }

這里我們發(fā)現(xiàn)Action,只是存儲關(guān)于SmoothScroller滑動信息的一個類碾盟,那么初始時保存了橫向與豎直滑動的距離(12000px)、滑動時間技竟,插值器冰肴。同時記錄當(dāng)前數(shù)據(jù)改變的狀態(tài)。

現(xiàn)在我們已經(jīng)把Action的onSeekTargetStep方法走完了榔组,那接下來熙尉,我們繼續(xù)看Action的runIfNecessary()方法。

   void runIfNecessary(RecyclerView recyclerView) {
                 ....省略代碼
                if (mChanged) {
                    validate();
                    if (mInterpolator == null) {
                        if (mDuration == UNDEFINED_DURATION) {
                            recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy);
                        } else {
                            //這里傳入的mDx,mDy,mDuration.是Action之前update()方法瓷患。保存的信息
                            recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration);
                        }
                    } else {
                        recyclerView.mViewFlinger.smoothScrollBy(
                                mDx, mDy, mDuration, mInterpolator);
                    }
              mChanged = false;
                ....省略代碼
            }

TNND,調(diào)來調(diào)去最后又把Action存儲的信息傳給了ViewFlinger的smoothScrollBy()方法骡尽。這里需要注意:一旦調(diào)用該方法會將mChanged置為false,下次再次進(jìn)入該方法時,那么就不會調(diào)用ViewFlinger的滑動方法了擅编。

 public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
             //判斷是否是同一插值器攀细,如果不是,重新創(chuàng)建mScroller
            if (mInterpolator != interpolator) {
                mInterpolator = interpolator;
                mScroller = new OverScroller(getContext(), interpolator);
            }
            setScrollState(SCROLL_STATE_SETTLING);
            mLastFlingX = mLastFlingY = 0;
            mScroller.startScroll(0, 0, dx, dy, duration);
            if (Build.VERSION.SDK_INT < 23) {
                mScroller.computeScrollOffset();
            }
            postOnAnimation();
        }

這里mScroller接受到Acttion傳入的滑動信息開始滑動后爱态。最后會調(diào)用postOnAnimation()谭贪,又將ViewFiinger的run()法發(fā)送出去。那么最終我們又回到了ViewFiinger的run()方法锦担。

    public void run() {
         ...省略部分代碼
   if (scroller.computeScrollOffset()) {
                final int[] scrollConsumed = mScrollConsumed;
                final int x = scroller.getCurrX();
                final int y = scroller.getCurrY();
                int dx = x - mLastFlingX;
                int dy = y - mLastFlingY;
                int hresult = 0;
                int vresult = 0;
                mLastFlingX = x;
                mLastFlingY = y;
                int overscrollX = 0, overscrollY = 0;
                ...省略部分代碼
                if (mAdapter != null) {
                    startInterceptRequestLayout();
                    onEnterLayoutOrScroll();
                    TraceCompat.beginSection(TRACE_SCROLL_TAG);
                    fillRemainingScrollValues(mState);
                    if (dx != 0) {//如果橫向方向大于0俭识,開始讓RecyclerView滾動
                        hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
                        overscrollX = dx - hresult;
                    }
                    if (dy != 0) {//如果豎直方向大于0,開始讓RecyclerView滾動洞渔,獲得當(dāng)前滾動的距離
                        vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
                        overscrollY = dy - vresult;
                    }
                    TraceCompat.endSection();
                    repositionShadowingViews();

                    onExitLayoutOrScroll();
                    stopInterceptRequestLayout(false);
                    if (smoothScroller != null && !smoothScroller.isPendingInitialRun()
                            && smoothScroller.isRunning()) {
                        final int adapterSize = mState.getItemCount();
                        if (adapterSize == 0) {
                            smoothScroller.stop();
                        } else if (smoothScroller.getTargetPosition() >= adapterSize) {
                            smoothScroller.setTargetPosition(adapterSize - 1);
                            //傳入當(dāng)前RecylerView滾動的距離 dx dy
                            smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
                        } else {
                            //傳入當(dāng)前RecylerView滾動的距離 dx dy
                            smoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);
                        }
                    }
                }
                enableRunOnAnimationRequests();
             }

這里scroller(拿到之前Action傳入的滑動距離信息)已經(jīng)開始滑動了套媚,故 if (scroller.computeScrollOffset()) 條件為true, 那么scroller拿到當(dāng)前豎直方向的值就開始讓RecyclerView滾動了,也就是代碼 mLayout.scrollVerticallyBy(dy, mRecycler, mState);接著又讓smoothScroller執(zhí)行onAnimation()方法。其中傳入的參數(shù)是RecyclerView已經(jīng)滾動的距離磁椒。那我們現(xiàn)在繼續(xù)看onAnimation方法堤瘤。

    private void onAnimation(int dx, int dy) {
            final RecyclerView recyclerView = mRecyclerView;
            if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) {
                stop();
            }
            mPendingInitialRun = false;
            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) {//獲得當(dāng)前Recycler需要滾動的距離
                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();
                    } else {
                        stop(); // done
                    }
                }
            }
        }

那么現(xiàn)在代碼就明了了,RecylerView會判斷在滾動的時候浆熔,目標(biāo)視圖是否已經(jīng)出現(xiàn)本辐,如果沒有出現(xiàn),會調(diào)用onSeekTargetStep保存當(dāng)前RecylerView滾動距離,然后判斷RecyclerView是否需要滑動慎皱,然后又通過postOnAnimation()將ViewFlinger 發(fā)送出去了老虫。那么直到找到目標(biāo)視圖才會停止。

那什么情況下茫多,目標(biāo)視圖不為空呢祈匙,其實(shí)在RecylerView內(nèi)部滾動的時候。會判斷目標(biāo)視圖是否存在地梨,如果存在會對mTargetView進(jìn)行賦值操作菊卷。由于篇幅限制,這里就不對目標(biāo)視圖的查找進(jìn)行介紹了宝剖,有興趣的小伙伴可以自己看一下源碼洁闰。

那接下來,我們就假如當(dāng)前已經(jīng)找到了目標(biāo)視圖万细,那么接下來程序會走onTargetFound()方法扑眉。

  protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
        //計(jì)算讓目標(biāo)視圖可見的,需要滾動的橫向距離
        final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());
       //計(jì)算讓目標(biāo)視圖可見的赖钞,需要滾動的橫向距離
        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);
        }
    }

當(dāng)目標(biāo)視圖被找到以后,會計(jì)算讓目標(biāo)視圖出現(xiàn)在可見范圍內(nèi)雪营,需要移動的橫向與縱向距離弓千。并計(jì)算所需要花費(fèi)的時間。然后重新讓RecyclerView滾動一段距離献起。

這里我們著重看calculateDyToMakeVisible洋访。

    public int calculateDyToMakeVisible(View view, int snapPreference) {
        final RecyclerView.LayoutManager layoutManager = getLayoutManager();
        if (layoutManager == null || !layoutManager.canScrollVertically()) {
            return 0;
        }
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
                view.getLayoutParams();
        //獲取當(dāng)前view在其父布局的開始位置
        final int top = layoutManager.getDecoratedTop(view) - params.topMargin;
        //獲取當(dāng)前View在其父布局結(jié)束位置
        final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;
        //獲取當(dāng)前布局的開始位置 
        final int start = layoutManager.getPaddingTop();
        //獲取當(dāng)前布局的結(jié)束位置
        final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();
        return calculateDtToFit(top, bottom, start, end, snapPreference);
    }

這里我們會根據(jù)當(dāng)前view的top、bottom及當(dāng)前布局的start谴餐、end等坐標(biāo)信息姻政,然后調(diào)用了calculateDtToFit()方法。現(xiàn)在最重要的出現(xiàn)了岂嗓,也是我們那三個問題出現(xiàn)的原因V埂!

    public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
            snapPreference) {
        switch (snapPreference) {
            case SNAP_TO_START:
                return boxStart - viewStart;
            case SNAP_TO_END:
                return boxEnd - viewEnd;
            case SNAP_TO_ANY:
                final int dtStart = boxStart - viewStart;
                if (dtStart > 0) {//滾動位置在可見范圍之前
                    return dtStart;
                }
                final int dtEnd = boxEnd - viewEnd;
                if (dtEnd < 0) {//滾動位置在可見范圍之后
                    return dtEnd;
                }
                break;
            default:
                throw new IllegalArgumentException("snap preference should be one of the"
                        + " constants defined in SmoothScroller, starting with SNAP_");
        }
        return 0;//在可見范圍之內(nèi)厌殉,直接返回
    }

我們會根據(jù)snapPreference對應(yīng)的值來計(jì)算相應(yīng)的距離食绿,同時snapPreference的具體值與getVerticalSnapPreference(這里我們是豎直方向)所以我們看該方法。

 protected int getVerticalSnapPreference() {
        return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :
                mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;
    }

其中mTargetVector與layoutManager.computeScrollVectorForPosition有關(guān)公罕。

  @Override
    public PointF computeScrollVectorForPosition(int targetPosition) {
        if (getChildCount() == 0) {
            return null;
        }
        final int firstChildPos = getPosition(getChildAt(0));
        final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;
        if (mOrientation == HORIZONTAL) {
            return new PointF(direction, 0);
        } else {
            return new PointF(0, direction);
        }
    }

也就是說在LinerlayoutManager為豎直的情況下器紧,snapPreference默認(rèn)為SNAP_ANY,那么我們就可以得到,下面三種情況熏兄。

  • 當(dāng)滾動位置在可見范圍之內(nèi)時
    boxStart - viewStart<=0
    boxEnd - viewEnd>0
    滾動距離為0,故不會滾動
  • 當(dāng)滾動位置在可見范圍之前時
    boxStart - viewStart> 0
    那么實(shí)際滾動距離為正值,內(nèi)容向上滾動摩桶,故只能滾動到頂部
  • 當(dāng)滾動位置在可見范圍距離之外時
    boxEnd - viewEnd<0
    那么實(shí)際滾動距離為其差值桥状,內(nèi)容向下滾動,故只能滾動到底部

有可能大家現(xiàn)在看代碼已經(jīng)看暈了硝清,下面我就用一張圖來總結(jié)整個流程辅斟,結(jié)合流程圖再去看代碼,我相信大家能有更好的理解芦拿。

基本流程圖.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末士飒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔗崎,更是在濱河造成了極大的恐慌酵幕,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缓苛,死亡現(xiàn)場離奇詭異芳撒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)未桥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門笔刹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冬耿,你說我怎么就攤上這事舌菜。” “怎么了亦镶?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵日月,是天一觀的道長。 經(jīng)常有香客問我染乌,道長山孔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任荷憋,我火速辦了婚禮台颠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘勒庄。我一直安慰自己串前,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布实蔽。 她就那樣靜靜地躺著荡碾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪局装。 梳的紋絲不亂的頭發(fā)上坛吁,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天劳殖,我揣著相機(jī)與錄音,去河邊找鬼拨脉。 笑死哆姻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的玫膀。 我是一名探鬼主播矛缨,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼帖旨!你這毒婦竟也來了箕昭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤解阅,失蹤者是張志新(化名)和其女友劉穎落竹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓮钥,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筋量,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碉熄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桨武。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锈津,靈堂內(nèi)的尸體忽然破棺而出其屏,到底是詐尸還是另有隱情虫啥,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站车摄,受9級特大地震影響剂公,放射性物質(zhì)發(fā)生泄漏惫恼。R本人自食惡果不足惜理澎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望煌往。 院中可真熱鬧倾哺,春花似錦、人聲如沸刽脖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽曲管。三九已至却邓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間院水,已是汗流浹背腊徙。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工简十, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撬腾。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓勺远,卻偏偏與公主長得像,于是被迫代替她去往敵國和親时鸵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容