摘要:為表示之前進(jìn)行過(guò)滾動(dòng),為狀態(tài)表示滾動(dòng)結(jié)束停下來(lái)的抽象方法抽象方法計(jì)算最終對(duì)齊要移動(dòng)的距離計(jì)算二個(gè)參數(shù)對(duì)應(yīng)的當(dāng)前的坐標(biāo)與需要對(duì)齊的坐標(biāo)之間的距離。抽象方法找到要對(duì)齊的該方法會(huì)找到當(dāng)前上最接近對(duì)齊位置的那個(gè),該稱為,對(duì)應(yīng)的稱為。
目錄介紹
01.SnapHelper簡(jiǎn)單介紹
1.1 SnapHelper作用
1.2 SnapHelper類分析
1.3 LinearSnapHelper類分析
1.4 PagerSnapHelper類分析
02.SnapHelper源碼分析
2.1 attachToRecyclerView入口方法
2.2 SnapHelper的抽象方法
2.3 onFling方法源碼分析
03.LinearSnapHelper源碼分析
3.1 LinearSnapHelper實(shí)現(xiàn)功能
3.2 calculateDistanceToFinalSnap()方法源碼
3.3 findSnapView()方法源碼
3.4 findTargetSnapPosition()方法源碼
3.5 支持哪些LayoutManager
3.6 OrientationHelper類
3.7 estimateNextPositionDiffForFling計(jì)算偏移量
04.自定義SnapHelper類
4.1 業(yè)務(wù)需求
4.2 自定義helper類
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時(shí)也開源了生活博客,從12年起,積累共計(jì)47篇[近20萬(wàn)字],轉(zhuǎn)載請(qǐng)注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬(wàn)事起于忽微,量變引起質(zhì)變!
01.SnapHelper簡(jiǎn)單介紹 1.1 SnapHelper作用在某些場(chǎng)景下,卡片列表滑動(dòng)瀏覽[有的叫輪播圖],希望當(dāng)滑動(dòng)停止時(shí)可以將當(dāng)前卡片停留在屏幕某個(gè)位置,比如停在左邊,以吸引用戶的焦點(diǎn)。那么可以使用RecyclerView + Snaphelper來(lái)實(shí)現(xiàn),SnapHelper旨在支持RecyclerView的對(duì)齊方式,也就是通過(guò)計(jì)算對(duì)齊RecyclerView中TargetView 的指定點(diǎn)或者容器中的任何像素點(diǎn)。
1.2 SnapHelper類分析
查閱可知,SnapHelper繼承自RecyclerView.OnFlingListener,并且重寫了onFling方法,這個(gè)類代碼并不多,下面會(huì)對(duì)重要方法一一解析。
支持SnapHelper的RecyclerView.LayoutManager必須實(shí)現(xiàn)的方式:
RecyclerView.SmoothScroller.ScrollVectorProvider接口
或者自己實(shí)現(xiàn)onFling(int,int)方法手動(dòng)處理邏輯。
SnapHelper類重要的方法
attachToRecyclerView: 將SnapHelper attach 到指定的RecyclerView 上。
calculateDistanceToFinalSnap:復(fù)寫這個(gè)方法計(jì)算對(duì)齊到TargetView或容器指定點(diǎn)的距離,這是一個(gè)抽象方法,由子類自己實(shí)現(xiàn),返回的是一個(gè)長(zhǎng)度為2的int 數(shù)組out,out[0]是x方向?qū)R要移動(dòng)的距離,out[1]是y方向?qū)R要移動(dòng)的距離。
calculateScrollDistance: 根據(jù)每個(gè)方向給定的速度估算滑動(dòng)的距離,用于Fling 操作。
findSnapView:提供一個(gè)指定的目標(biāo)View 來(lái)對(duì)齊,抽象方法,需要子類實(shí)現(xiàn)
findTargetSnapPosition:提供一個(gè)用于對(duì)齊的Adapter 目標(biāo)position,抽象方法,需要子類自己實(shí)現(xiàn)。
onFling:根據(jù)給定的x和 y 軸上的速度處理Fling。
什么是Fling操作
手指在屏幕上滑動(dòng) RecyclerView然后松手,RecyclerView中的內(nèi)容會(huì)順著慣性繼續(xù)往手指滑動(dòng)的方向繼續(xù)滾動(dòng)直到停止,這個(gè)過(guò)程叫做 Fling 。 Fling 操作從手指離開屏幕瞬間被觸發(fā),在滾動(dòng)停止時(shí)結(jié)束。
1.3 LinearSnapHelper類分析LinearSnapHelper 使當(dāng)前Item居中顯示,常用場(chǎng)景是橫向的RecyclerView,類似ViewPager效果,但是又可以快速滑動(dòng)(滑動(dòng)多頁(yè))。
最簡(jiǎn)單的使用就是,如下代碼
幾行代碼就可以用RecyclerView實(shí)現(xiàn)一個(gè)類似ViewPager的效果,并且效果還不錯(cuò)??梢钥焖倩瑒?dòng)多頁(yè),當(dāng)前頁(yè)劇中顯示,并且顯示前一頁(yè)和后一頁(yè)的部分。
private void initRecyclerView() { LinearLayoutManager manager = new LinearLayoutManager(this); manager.setOrientation(LinearLayoutManager.HORIZONTAL); mRecyclerView.setLayoutManager(manager); LinearSnapHelper snapHelper = new LinearSnapHelper(); snapHelper.attachToRecyclerView(mRecyclerView); SnapAdapter adapter = new SnapAdapter(this); mRecyclerView.setAdapter(adapter); adapter.addAll(getData()); }1.4 PagerSnapHelper類分析
PagerSnapHelper看名字可能就能猜到,使RecyclerView像ViewPager一樣的效果,每次只能滑動(dòng)一頁(yè)(LinearSnapHelper支持快速滑動(dòng)), PagerSnapHelper也是Item居中對(duì)齊。
最簡(jiǎn)單的使用就是,如下代碼
private void initRecyclerView() { LinearLayoutManager manager = new LinearLayoutManager(this); manager.setOrientation(LinearLayoutManager.HORIZONTAL); mRecyclerView.setLayoutManager(manager); PagerSnapHelper snapHelper = new PagerSnapHelper(); snapHelper.attachToRecyclerView(mRecyclerView); SnapAdapter adapter = new SnapAdapter(this); mRecyclerView.setAdapter(adapter); adapter.addAll(getData()); }02.SnapHelper源碼分析 2.1 attachToRecyclerView入口方法
通過(guò)attachToRecyclerView方法將SnapHelper attach 到RecyclerView,看一下這個(gè)方法的源代碼
如果SnapHelper之前已經(jīng)附著到此RecyclerView上,則不用進(jìn)行任何操作
如果SnapHelper之前附著的RecyclerView和現(xiàn)在的不一致,就將原來(lái)設(shè)置的回調(diào)全部remove或者設(shè)置為null
然后更新RecyclerView對(duì)象引用,Attach的RecyclerView不為null,設(shè)置回調(diào)Callback,主要包括滑動(dòng)的回調(diào)和Fling操作的回調(diào),初始化一個(gè)Scroller 用于后面做滑動(dòng)處理,然后調(diào)用snapToTargetExistingView
大概流程就是:在attachToRecyclerView()方法中會(huì)清掉SnapHelper之前保存的RecyclerView對(duì)象的回調(diào)(如果有的話),對(duì)新設(shè)置進(jìn)來(lái)的RecyclerView對(duì)象設(shè)置回調(diào),然后初始化一個(gè)Scroller對(duì)象,最后調(diào)用snapToTargetExistingView()方法對(duì)SnapView進(jìn)行對(duì)齊調(diào)整。
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException { if (mRecyclerView == recyclerView) { return; // nothing to do } if (mRecyclerView != null) { destroyCallbacks(); } mRecyclerView = recyclerView; if (mRecyclerView != null) { setupCallbacks(); mGravityScroller = new Scroller(mRecyclerView.getContext(), new DecelerateInterpolator()); snapToTargetExistingView(); } }
接著看看setupCallbacks()源碼
上面已經(jīng)說(shuō)了,滑動(dòng)的回調(diào)和Fling操作的回調(diào)
private void setupCallbacks() throws IllegalStateException { if (mRecyclerView.getOnFlingListener() != null) { throw new IllegalStateException("An instance of OnFlingListener already set."); } mRecyclerView.addOnScrollListener(mScrollListener); mRecyclerView.setOnFlingListener(this); }
接著看看snapToTargetExistingView()方法
這個(gè)方法用于第一次Attach到RecyclerView時(shí)對(duì)齊TargetView,或者當(dāng)Scroll被觸發(fā)的時(shí)候和fling操作的時(shí)候?qū)RTargetView 。
判斷RecyclerView 和LayoutManager是否為null,接著調(diào)用findSnapView 方法來(lái)獲取需要對(duì)齊的目標(biāo)View,注意:這是個(gè)抽象方法,需要子類實(shí)現(xiàn)
通過(guò)calculateDistanceToFinalSnap 獲取x方向和y方向?qū)R需要移動(dòng)的距離
最后如果需要滾動(dòng)的距離不是為0,就調(diào)用smoothScrollBy方法使RecyclerView滾動(dòng)相應(yīng)的距離
注意:RecyclerView.smoothScrollBy()這個(gè)方法的作用就是根據(jù)參數(shù)平滑滾動(dòng)RecyclerView的中的ItemView相應(yīng)的距離。
void snapToTargetExistingView() { if (mRecyclerView == null) { return; } LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager == null) { return; } View snapView = findSnapView(layoutManager); if (snapView == null) { return; } int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView); if (snapDistance[0] != 0 || snapDistance[1] != 0) { mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]); } }
然后來(lái)看一下mScrollListener監(jiān)聽里面做了什么
該滾動(dòng)監(jiān)聽器的實(shí)現(xiàn)很簡(jiǎn)單,只是在正常滾動(dòng)停止的時(shí)候調(diào)用了snapToTargetExistingView()方法對(duì)targetView進(jìn)行滾動(dòng)調(diào)整,以確保停止的位置是在對(duì)應(yīng)的坐標(biāo)上,這就是RecyclerView添加該OnScrollListener的目的。
mScrolled為true表示之前進(jìn)行過(guò)滾動(dòng),newState為SCROLL_STATE_IDLE狀態(tài)表示滾動(dòng)結(jié)束停下來(lái)
private final RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() { boolean mScrolled = false; @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) { mScrolled = false; snapToTargetExistingView(); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (dx != 0 || dy != 0) { mScrolled = true; } } };2.2 SnapHelper的抽象方法
calculateDistanceToFinalSnap抽象方法
計(jì)算最終對(duì)齊要移動(dòng)的距離
計(jì)算二個(gè)參數(shù)對(duì)應(yīng)的 ItemView 當(dāng)前的坐標(biāo)與需要對(duì)齊的坐標(biāo)之間的距離。該方法返回一個(gè)大小為 2 的 int 數(shù)組,分別對(duì)應(yīng)out[0] 為 x 方向移動(dòng)的距離,out[1] 為 y 方向移動(dòng)的距離。
@SuppressWarnings("WeakerAccess") @Nullable public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager, @NonNull View targetView);
findSnapView抽象方法
找到要對(duì)齊的View
該方法會(huì)找到當(dāng)前 layoutManager 上最接近對(duì)齊位置的那個(gè) view ,該 view 稱為 SanpView ,對(duì)應(yīng)的 position 稱為 SnapPosition 。如果返回 null ,就表示沒有需要對(duì)齊的 View ,也就不會(huì)做滾動(dòng)對(duì)齊調(diào)整。
@SuppressWarnings("WeakerAccess") @Nullable public abstract View findSnapView(LayoutManager layoutManager);
findTargetSnapPosition抽象方法
找到需要對(duì)齊的目標(biāo)View的的Position。
更加詳細(xì)一點(diǎn)說(shuō)就是該方法會(huì)根據(jù)觸發(fā) Fling 操作的速率(參數(shù) velocityX 和參數(shù) velocityY )來(lái)找到 RecyclerView 需要滾動(dòng)到哪個(gè)位置,該位置對(duì)應(yīng)的 ItemView 就是那個(gè)需要進(jìn)行對(duì)齊的列表項(xiàng)。我們把這個(gè)位置稱為 targetSnapPosition ,對(duì)應(yīng)的 View 稱為 targetSnapView 。如果找不到 targetSnapPosition ,就返回RecyclerView.NO_POSITION 。
public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, int velocityY);2.3 onFling方法源碼分析
SnapHelper繼承了 RecyclerView.OnFlingListener,實(shí)現(xiàn)了onFling方法。
獲取RecyclerView要進(jìn)行fling操作需要的最小速率,為啥呢?因?yàn)橹挥谐^(guò)該速率,ItemView才會(huì)有足夠的動(dòng)力在手指離開屏幕時(shí)繼續(xù)滾動(dòng)下去。
@Override public boolean onFling(int velocityX, int velocityY) { LayoutManager layoutManager = mRecyclerView.getLayoutManager(); if (layoutManager == null) { return false; } RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); if (adapter == null) { return false; } int minFlingVelocity = mRecyclerView.getMinFlingVelocity(); return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity) && snapFromFling(layoutManager, velocityX, velocityY); }
接著看看snapFromFling方法源代碼,就是通過(guò)該方法實(shí)現(xiàn)平滑滾動(dòng)并使得在滾動(dòng)停止時(shí)itemView對(duì)齊到目的坐標(biāo)位置
首先layoutManager必須實(shí)現(xiàn)ScrollVectorProvider接口才能繼續(xù)往下操作
然后通過(guò)createSnapScroller方法創(chuàng)建一個(gè)SmoothScroller,這個(gè)東西是一個(gè)平滑滾動(dòng)器,用于對(duì)ItemView進(jìn)行平滑滾動(dòng)操作
根據(jù)x和y方向的速度來(lái)獲取需要對(duì)齊的View的位置,需要子類實(shí)現(xiàn)
最終通過(guò) SmoothScroller 來(lái)滑動(dòng)到指定位置
private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX, int velocityY) { if (!(layoutManager instanceof ScrollVectorProvider)) { return false; } RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager); if (smoothScroller == null) { return false; } int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY); if (targetPosition == RecyclerView.NO_POSITION) { return false; } smoothScroller.setTargetPosition(targetPosition); layoutManager.startSmoothScroll(smoothScroller); return true; }
總結(jié)一下可知:snapFromFling()方法會(huì)先判斷l(xiāng)ayoutManager是否實(shí)現(xiàn)了ScrollVectorProvider接口,如果沒有實(shí)現(xiàn)該接口就不允許通過(guò)該方法做滾動(dòng)操作。接下來(lái)就去創(chuàng)建平滑滾動(dòng)器SmoothScroller的一個(gè)實(shí)例,layoutManager可以通過(guò)該平滑滾動(dòng)器來(lái)進(jìn)行滾動(dòng)操作。SmoothScroller需要設(shè)置一個(gè)滾動(dòng)的目標(biāo)位置,將通過(guò)findTargetSnapPosition()方法來(lái)計(jì)算得到的targetSnapPosition給它,告訴滾動(dòng)器要滾到這個(gè)位置,然后就啟動(dòng)SmoothScroller進(jìn)行滾動(dòng)操作。
接著看下createSnapScroller這個(gè)方法源碼
先判斷l(xiāng)ayoutManager是否實(shí)現(xiàn)了ScrollVectorProvider這個(gè)接口,沒有實(shí)現(xiàn)該接口就不創(chuàng)建SmoothScroller
這里創(chuàng)建一個(gè)LinearSmoothScroller對(duì)象,然后返回給調(diào)用函數(shù),也就是說(shuō),最終創(chuàng)建出來(lái)的平滑滾動(dòng)器就是這個(gè)LinearSmoothScroller
在創(chuàng)建該LinearSmoothScroller的時(shí)候主要考慮兩個(gè)方面:
第一個(gè)是滾動(dòng)速率,由calculateSpeedPerPixel()方法決定;
第二個(gè)是在滾動(dòng)過(guò)程中,targetView即將要進(jìn)入到視野時(shí),將勻速滾動(dòng)變換為減速滾動(dòng),然后一直滾動(dòng)目的坐標(biāo)位置,使?jié)L動(dòng)效果更真實(shí),這是由onTargetFound()方法決定。
@Nullable protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) { if (!(layoutManager instanceof ScrollVectorProvider)) { return null; } return new LinearSmoothScroller(mRecyclerView.getContext()) { @Override protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView); final int dx = snapDistances[0]; final int dy = snapDistances[1]; final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); if (time > 0) { action.update(dx, dy, time, mDecelerateInterpolator); } } @Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; } }; }03.LinearSnapHelper源碼分析 3.1 LinearSnapHelper實(shí)現(xiàn)功能
LinearSnapHelper實(shí)現(xiàn)了SnapHelper,并且實(shí)現(xiàn)SnapHelper的三個(gè)抽象方法,從而讓ItemView滾動(dòng)居中對(duì)齊。那么具體怎么做到呢?
3.2 calculateDistanceToFinalSnap()方法源碼
calculateDistanceToFinalSnap源碼如下所示
如果是水平方向滾動(dòng)的,則計(jì)算水平方向需要移動(dòng)的距離,否則水平方向的移動(dòng)距離為0
如果是豎直方向滾動(dòng)的,則計(jì)算豎直方向需要移動(dòng)的距離,否則豎直方向的移動(dòng)距離為0
distanceToCenter方法主要作用是:計(jì)算水平或者豎直方向需要移動(dòng)的距離
@Override public int[] calculateDistanceToFinalSnap( @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToCenter(layoutManager, targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToCenter(layoutManager, targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; }
接著看看distanceToCenter方法
計(jì)算對(duì)應(yīng)的view的中心坐標(biāo)到RecyclerView中心坐標(biāo)之間的距離
首先是找到targetView的中心坐標(biāo)
接著也就是找到容器【RecyclerView】的中心坐標(biāo)
兩個(gè)中心坐標(biāo)的差值就是targetView需要滾動(dòng)的距離
private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView, OrientationHelper helper) { final int childCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedMeasurement(targetView) / 2); final int containerCenter; if (layoutManager.getClipToPadding()) { containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; } else { containerCenter = helper.getEnd() / 2; } return childCenter - containerCenter; }3.3 findSnapView()方法源碼
也就是找到要對(duì)齊的View
根據(jù)layoutManager的布局方式(水平布局方式或者豎向布局方式)區(qū)分計(jì)算,但最終都是通過(guò)findCenterView()方法來(lái)找snapView的。
@Override public View findSnapView(RecyclerView.LayoutManager layoutManager) { if (layoutManager.canScrollVertically()) { return findCenterView(layoutManager, getVerticalHelper(layoutManager)); } else if (layoutManager.canScrollHorizontally()) { return findCenterView(layoutManager, getHorizontalHelper(layoutManager)); } return null; }
接著看看findCenterView方法源代碼
查詢當(dāng)前是否支持垂直滾動(dòng)還是橫向滾動(dòng)
循環(huán)LayoutManager的所有子元素,計(jì)算每個(gè) childView的中點(diǎn)距離Parent 的中點(diǎn),找到距離最近的一個(gè),就是需要居中對(duì)齊的目標(biāo)View
@Nullable private View findCenterView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { int childCount = layoutManager.getChildCount(); if (childCount == 0) { return null; } View closestChild = null; final int center; if (layoutManager.getClipToPadding()) { center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; } else { center = helper.getEnd() / 2; } int absClosest = Integer.MAX_VALUE; for (int i = 0; i < childCount; i++) { final View child = layoutManager.getChildAt(i); int childCenter = helper.getDecoratedStart(child) + (helper.getDecoratedMeasurement(child) / 2); int absDistance = Math.abs(childCenter - center); /** if child center is closer than previous closest, set it as closest **/ if (absDistance < absClosest) { absClosest = absDistance; closestChild = child; } } return closestChild; }3.4 findTargetSnapPosition()方法源碼
LinearSnapHelper實(shí)現(xiàn)了SnapHelper,來(lái)看一下在findTargetSnapPosition操作了什么
如果是水平方向滾動(dòng)的列表,估算出水平方向SnapHelper響應(yīng)fling,對(duì)齊要滑動(dòng)的position和當(dāng)前position的差,否則,水平方向滾動(dòng)的差值為0
如果是豎直方向滾動(dòng)的列表,估算出豎直方向SnapHelper響應(yīng)fling,對(duì)齊要滑動(dòng)的position和當(dāng)前position的差,否則,豎直方向滾動(dòng)的差值為0
這個(gè)方法在計(jì)算targetPosition的時(shí)候把布局方式和布局方向都考慮進(jìn)去了。布局方式可以通過(guò)layoutManager.canScrollHorizontally()/layoutManager.canScrollVertically()來(lái)判斷,布局方向就通過(guò)RecyclerView.SmoothScroller.ScrollVectorProvider這個(gè)接口中的computeScrollVectorForPosition()方法來(lái)判斷。
@Override public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, int velocityY) { if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { return RecyclerView.NO_POSITION; } final int itemCount = layoutManager.getItemCount(); if (itemCount == 0) { return RecyclerView.NO_POSITION; } final View currentView = findSnapView(layoutManager); if (currentView == null) { return RecyclerView.NO_POSITION; } final int currentPosition = layoutManager.getPosition(currentView); if (currentPosition == RecyclerView.NO_POSITION) { return RecyclerView.NO_POSITION; } RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; // deltaJumps sign comes from the velocity which may not match the order of children in // the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to // get the direction. PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1); if (vectorForEnd == null) { // cannot get a vector for the given position. return RecyclerView.NO_POSITION; } int vDeltaJump, hDeltaJump; if (layoutManager.canScrollHorizontally()) { hDeltaJump = estimateNextPositionDiffForFling(layoutManager, getHorizontalHelper(layoutManager), velocityX, 0); if (vectorForEnd.x < 0) { hDeltaJump = -hDeltaJump; } } else { hDeltaJump = 0; } if (layoutManager.canScrollVertically()) { vDeltaJump = estimateNextPositionDiffForFling(layoutManager, getVerticalHelper(layoutManager), 0, velocityY); if (vectorForEnd.y < 0) { vDeltaJump = -vDeltaJump; } } else { vDeltaJump = 0; } int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump; if (deltaJump == 0) { return RecyclerView.NO_POSITION; } int targetPos = currentPosition + deltaJump; if (targetPos < 0) { targetPos = 0; } if (targetPos >= itemCount) { targetPos = itemCount - 1; } return targetPos; }3.5 支持哪些LayoutManager
SnapHelper為了適配layoutManager的各種情況,特意要求只有實(shí)現(xiàn)了RecyclerView.SmoothScroller.ScrollVectorProvider接口的layoutManager才能使用SnapHelper進(jìn)行輔助滾動(dòng)對(duì)齊。官方提供的LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager都實(shí)現(xiàn)了這個(gè)接口,所以都支持SnapHelper。
3.6 OrientationHelper類
如何創(chuàng)建OrientationHelper對(duì)象呢?如下所示
比如,上面三個(gè)抽象方法都使用到了這個(gè)類,這個(gè)類是干嘛的?
計(jì)算位置的時(shí)候用的是OrientationHelper這個(gè)工具類,它是LayoutManager用于測(cè)量child的一個(gè)輔助類,可以根據(jù)Layoutmanager的布局方式和布局方向來(lái)計(jì)算得到ItemView的大小位置等信息。
@NonNull private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { if (mVerticalHelper == null || mVerticalHelper.mLayoutManager != layoutManager) { mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); } return mVerticalHelper; } @NonNull private OrientationHelper getHorizontalHelper( @NonNull RecyclerView.LayoutManager layoutManager) { if (mHorizontalHelper == null || mHorizontalHelper.mLayoutManager != layoutManager) { mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); } return mHorizontalHelper; }3.7 estimateNextPositionDiffForFling計(jì)算偏移量
如下所示
首先,計(jì)算滾動(dòng)的總距離,這個(gè)距離受到觸發(fā)fling時(shí)的速度的影響,得到一個(gè)distances數(shù)組
然后計(jì)算每個(gè)ItemView的長(zhǎng)度
根據(jù)是橫向布局還是縱向布局,來(lái)取對(duì)應(yīng)布局方向上的滾動(dòng)距離
總結(jié)大概流程就是:用滾動(dòng)總距離除以itemview的長(zhǎng)度,從而估算得到需要滾動(dòng)的item數(shù)量,此數(shù)值就是位置偏移量。而滾動(dòng)距離是通過(guò)SnapHelper的calculateScrollDistance()方法得到的,ItemView的長(zhǎng)度是通過(guò)computeDistancePerChild()方法計(jì)算出來(lái)。
private int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager, OrientationHelper helper, int velocityX, int velocityY) { int[] distances = calculateScrollDistance(velocityX, velocityY); float distancePerChild = computeDistancePerChild(layoutManager, helper); if (distancePerChild <= 0) { return 0; } int distance = Math.abs(distances[0]) > Math.abs(distances[1]) ? distances[0] : distances[1]; return (int) Math.round(distance / distancePerChild); }04.自定義SnapHelper類 4.1 業(yè)務(wù)需求
LinearSnapHelper 實(shí)現(xiàn)了居中對(duì)齊,那么我們只要更改一下對(duì)齊的規(guī)則就行,更改為開始對(duì)齊(計(jì)算目標(biāo) View到 Parent start 要滑動(dòng)的距離),其他的邏輯和 LinearSnapHelper 是一樣的。因此我們選擇繼承 LinearSnapHelper
大概流程
重寫calculateDistanceToFinalSnap方法,計(jì)算SnapView當(dāng)前位置與目標(biāo)位置的距離
寫findSnapView方法,找到當(dāng)前時(shí)刻的SnapView
可以發(fā)現(xiàn)完成上面兩個(gè)方法就可以呢,但是感覺滑動(dòng)效果不太好?;瑒?dòng)比較快時(shí),會(huì)滾動(dòng)很遠(yuǎn)。在分析了上面的代碼可知,滾動(dòng)速率,由createSnapScroller方法中的calculateSpeedPerPixel()方法決定。那么是不是可以修改一下速率就可以解決問題呢。最后測(cè)試真的可以,ok,完成了。
當(dāng)然還會(huì)發(fā)現(xiàn)滾動(dòng)時(shí)候,會(huì)滑動(dòng)多個(gè)item,如果相對(duì)item個(gè)數(shù)做限制,可以在findTargetSnapPosition()方法中處理。
代碼地址:https://github.com/yangchong2...
4.2 自定義helper類
重寫calculateDistanceToFinalSnap方法
這里需要知道,在LinearSnapHelper中,out[0]和out[1]是通過(guò)distanceToCenter獲取的。那么既然要設(shè)置開始對(duì)齊,那么這里需要?jiǎng)?chuàng)建distanceToStart方法
@Nullable @Override public int[] calculateDistanceToFinalSnap(RecyclerView.LayoutManager layoutManager, View targetView) { int[] out = new int[2]; if (layoutManager.canScrollHorizontally()) { out[0] = distanceToStart(targetView, getHorizontalHelper(layoutManager)); } else { out[0] = 0; } if (layoutManager.canScrollVertically()) { out[1] = distanceToStart(targetView, getVerticalHelper(layoutManager)); } else { out[1] = 0; } return out; } private int distanceToStart(View targetView, OrientationHelper helper) { return helper.getDecoratedStart(targetView) - helper.getStartAfterPadding(); }
寫findSnapView方法,找到當(dāng)前時(shí)刻的SnapView
@Nullable @Override public View findSnapView(RecyclerView.LayoutManager layoutManager) { if (layoutManager instanceof LinearLayoutManager) { if (layoutManager.canScrollHorizontally()) { return findStartView(layoutManager, getHorizontalHelper(layoutManager)); } else { return findStartView(layoutManager, getVerticalHelper(layoutManager)); } } return super.findSnapView(layoutManager); } private View findStartView(RecyclerView.LayoutManager layoutManager, OrientationHelper helper) { if (layoutManager instanceof LinearLayoutManager) { int firstChild = ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); //需要判斷是否是最后一個(gè)Item,如果是最后一個(gè)則不讓對(duì)齊,以免出現(xiàn)最后一個(gè)顯示不完全。 boolean isLastItem = ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1; if (firstChild == RecyclerView.NO_POSITION || isLastItem) { return null; } View child = layoutManager.findViewByPosition(firstChild); if (helper.getDecoratedEnd(child) >= helper.getDecoratedMeasurement(child) / 2 && helper.getDecoratedEnd(child) > 0) { return child; } else { if (((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition() == layoutManager.getItemCount() - 1) { return null; } else { return layoutManager.findViewByPosition(firstChild + 1); } } } return super.findSnapView(layoutManager); }
修改滾動(dòng)速率
@Nullable protected LinearSmoothScroller createSnapScroller(final RecyclerView.LayoutManager layoutManager) { if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { return null; } return new LinearSmoothScroller(mRecyclerView.getContext()) { @Override protected void onTargetFound(View targetView, RecyclerView.State state, RecyclerView.SmoothScroller.Action action) { int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), targetView); final int dx; final int dy; if (snapDistances != null) { dx = snapDistances[0]; dy = snapDistances[1]; final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); if (time > 0) { action.update(dx, dy, time, mDecelerateInterpolator); } } } @Override protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { //這個(gè)地方可以自己設(shè)置 return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; } }; }關(guān)于其他內(nèi)容介紹 關(guān)于其他內(nèi)容介紹 01.關(guān)于博客匯總鏈接
1.技術(shù)博客匯總
2.開源項(xiàng)目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關(guān)于我的博客我的個(gè)人站點(diǎn):www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡(jiǎn)書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國(guó):https://my.oschina.net/zbj161...
泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/72109.html
摘要:缺點(diǎn)自動(dòng)裝箱的存在意味著每一次插入都會(huì)有額外的對(duì)象創(chuàng)建。對(duì)象本身是一層額外需要被創(chuàng)建以及被垃圾回收的對(duì)象。相較于我們舍棄了和類型的放棄了并依賴于二分法查找。 目錄介紹 25.0.0.0 請(qǐng)說(shuō)一下RecyclerView?adapter的作用是什么,幾個(gè)方法是做什么用的?如何理解adapter訂閱者模式? 25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder...
摘要:支持復(fù)雜頁(yè)面,例如添加自定義頭部和底部布局,支持橫向滑動(dòng),還可以支持粘貼頭部類似微信好友分組,支持不規(guī)則瀑布流效果,支持側(cè)滑刪除功能。支持粘貼頭部的需求效果,這種效果類似微信好友分組的那種功能界面。 目錄介紹 1.復(fù)雜頁(yè)面庫(kù)介紹 2.本庫(kù)優(yōu)勢(shì)亮點(diǎn) 2.1 支持多種狀態(tài)切換管理 2.2 支持添加多個(gè)header和footer 2.3 支持側(cè)滑功能和拖拽移動(dòng) 2.4 其他亮點(diǎn)介紹 ...
摘要:機(jī)制博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yùn)行簡(jiǎn)單程序入門從到學(xué)習(xí)配置文件詳解從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)介紹從到學(xué)習(xí)如何自定義從到學(xué)習(xí)轉(zhuǎn)換從到學(xué)習(xí)介紹中的從到學(xué)習(xí)中的幾種詳解從到學(xué)習(xí)讀取數(shù)據(jù)寫入到從到學(xué)習(xí)項(xiàng)目如何運(yùn)行從 Flink Checkpoint 機(jī)制 https://t.zsxq.com/ynQNbeM 博客 1、Flink 從0到1學(xué)習(xí) —— Apache Fl...
閱讀 1051·2021-09-13 10:29
閱讀 3398·2019-08-29 18:31
閱讀 2648·2019-08-29 11:15
閱讀 3022·2019-08-26 13:25
閱讀 1380·2019-08-26 12:00
閱讀 2324·2019-08-26 11:41
閱讀 3423·2019-08-26 10:31
閱讀 1498·2019-08-26 10:25