前言
最近開發(fā)中遇到了一個需求环鲤,需要RecyclerView滾動到指定位置后置頂顯示闯冷,當(dāng)時遇到這個問題的時候砂心,心里第一反應(yīng)是直接使用RecyclerView的smoothScrollToPosition()方法,實(shí)現(xiàn)對應(yīng)位置的平滑滾動蛇耀。但是在實(shí)際使用中發(fā)現(xiàn)并沒有到底自己想要的效果辩诞。本想著偷懶直接從網(wǎng)上Copy下,但是發(fā)現(xiàn)效果并不是很好纺涤。于是就自己去研究源碼译暂。
該系列文章分為兩篇文章抠忘。
- 如果你想了解其內(nèi)部實(shí)現(xiàn),請觀看本篇文章外永,
- 如果你想解決通過smoothScrollToPosition滾動到頂部崎脉,或者修改滾動加速,請觀看RecyclerView滾動位置伯顶,滾動速度設(shè)置
什么是可見范圍囚灼?
在了解RecyclerView的smoothScrollToPosition方法之前,有個知識點(diǎn)祭衩,我覺得有必要給大家說一下灶体,因?yàn)槭褂胹moothScrollToPosition中遇到的問題都與可見范圍有關(guān)。
這里所說的可見范圍是掐暮,RecyclerView第一個可見item的位置與最后一個可見item的位置之間的范圍蝎抽。
一、實(shí)際使用中遇見的問題
如果當(dāng)前滾動位置在可見范圍內(nèi)路克,是不會發(fā)生滾動的
當(dāng)前RecyclerView的可見范圍為0到9樟结,當(dāng)我們想要滾動到1位置時,發(fā)現(xiàn)當(dāng)前RecyclerView并沒有發(fā)生滾動精算。
二瓢宦、如果當(dāng)前滾動位置在可見范圍之后,會滾動到底部
當(dāng)前RecyclerView的可見范圍為0到9灰羽,當(dāng)我們想要滾動到10位置時刁笙,發(fā)現(xiàn)RecyclerView滾動了,且當(dāng)前位置對應(yīng)的視圖在RecyclreView的底部谦趣。
三、如果當(dāng)前滾動位置在可見范圍之前座每,會滾動到頂部
這里我們滾動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é)合流程圖再去看代碼,我相信大家能有更好的理解芦拿。