摘要:為表示之前進(jìn)行過滾動,為狀態(tài)表示滾動結(jié)束停下來的抽象方法抽象方法計算最終對齊要移動的距離計算二個參數(shù)對應(yīng)的當(dāng)前的坐標(biāo)與需要對齊的坐標(biāo)之間的距離。抽象方法找到要對齊的該方法會找到當(dāng)前上最接近對齊位置的那個,該稱為,對應(yīng)的稱為。
目錄介紹
01.SnapHelper簡單介紹
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計算偏移量
04.自定義SnapHelper類
4.1 業(yè)務(wù)需求
4.2 自定義helper類
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉(zhuǎn)載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
01.SnapHelper簡單介紹 1.1 SnapHelper作用在某些場景下,卡片列表滑動瀏覽[有的叫輪播圖],希望當(dāng)滑動停止時可以將當(dāng)前卡片停留在屏幕某個位置,比如停在左邊,以吸引用戶的焦點(diǎn)。那么可以使用RecyclerView + Snaphelper來實(shí)現(xiàn),SnapHelper旨在支持RecyclerView的對齊方式,也就是通過計算對齊RecyclerView中TargetView 的指定點(diǎn)或者容器中的任何像素點(diǎn)。
1.2 SnapHelper類分析
查閱可知,SnapHelper繼承自RecyclerView.OnFlingListener,并且重寫了onFling方法,這個類代碼并不多,下面會對重要方法一一解析。
支持SnapHelper的RecyclerView.LayoutManager必須實(shí)現(xiàn)的方式:
RecyclerView.SmoothScroller.ScrollVectorProvider接口
或者自己實(shí)現(xiàn)onFling(int,int)方法手動處理邏輯。
SnapHelper類重要的方法
attachToRecyclerView: 將SnapHelper attach 到指定的RecyclerView 上。
calculateDistanceToFinalSnap:復(fù)寫這個方法計算對齊到TargetView或容器指定點(diǎn)的距離,這是一個抽象方法,由子類自己實(shí)現(xiàn),返回的是一個長度為2的int 數(shù)組out,out[0]是x方向?qū)R要移動的距離,out[1]是y方向?qū)R要移動的距離。
calculateScrollDistance: 根據(jù)每個方向給定的速度估算滑動的距離,用于Fling 操作。
findSnapView:提供一個指定的目標(biāo)View 來對齊,抽象方法,需要子類實(shí)現(xiàn)
findTargetSnapPosition:提供一個用于對齊的Adapter 目標(biāo)position,抽象方法,需要子類自己實(shí)現(xiàn)。
onFling:根據(jù)給定的x和 y 軸上的速度處理Fling。
什么是Fling操作
手指在屏幕上滑動 RecyclerView然后松手,RecyclerView中的內(nèi)容會順著慣性繼續(xù)往手指滑動的方向繼續(xù)滾動直到停止,這個過程叫做 Fling 。 Fling 操作從手指離開屏幕瞬間被觸發(fā),在滾動停止時結(jié)束。
1.3 LinearSnapHelper類分析LinearSnapHelper 使當(dāng)前Item居中顯示,常用場景是橫向的RecyclerView,類似ViewPager效果,但是又可以快速滑動(滑動多頁)。
最簡單的使用就是,如下代碼
幾行代碼就可以用RecyclerView實(shí)現(xiàn)一個類似ViewPager的效果,并且效果還不錯??梢钥焖倩瑒佣囗?,當(dāng)前頁劇中顯示,并且顯示前一頁和后一頁的部分。
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一樣的效果,每次只能滑動一頁(LinearSnapHelper支持快速滑動), PagerSnapHelper也是Item居中對齊。
最簡單的使用就是,如下代碼
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入口方法
通過attachToRecyclerView方法將SnapHelper attach 到RecyclerView,看一下這個方法的源代碼
如果SnapHelper之前已經(jīng)附著到此RecyclerView上,則不用進(jìn)行任何操作
如果SnapHelper之前附著的RecyclerView和現(xiàn)在的不一致,就將原來設(shè)置的回調(diào)全部remove或者設(shè)置為null
然后更新RecyclerView對象引用,Attach的RecyclerView不為null,設(shè)置回調(diào)Callback,主要包括滑動的回調(diào)和Fling操作的回調(diào),初始化一個Scroller 用于后面做滑動處理,然后調(diào)用snapToTargetExistingView
大概流程就是:在attachToRecyclerView()方法中會清掉SnapHelper之前保存的RecyclerView對象的回調(diào)(如果有的話),對新設(shè)置進(jìn)來的RecyclerView對象設(shè)置回調(diào),然后初始化一個Scroller對象,最后調(diào)用snapToTargetExistingView()方法對SnapView進(jìn)行對齊調(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)說了,滑動的回調(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()方法
這個方法用于第一次Attach到RecyclerView時對齊TargetView,或者當(dāng)Scroll被觸發(fā)的時候和fling操作的時候?qū)RTargetView 。
判斷RecyclerView 和LayoutManager是否為null,接著調(diào)用findSnapView 方法來獲取需要對齊的目標(biāo)View,注意:這是個抽象方法,需要子類實(shí)現(xiàn)
通過calculateDistanceToFinalSnap 獲取x方向和y方向?qū)R需要移動的距離
最后如果需要滾動的距離不是為0,就調(diào)用smoothScrollBy方法使RecyclerView滾動相應(yīng)的距離
注意:RecyclerView.smoothScrollBy()這個方法的作用就是根據(jù)參數(shù)平滑滾動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]); } }
然后來看一下mScrollListener監(jiān)聽里面做了什么
該滾動監(jiān)聽器的實(shí)現(xiàn)很簡單,只是在正常滾動停止的時候調(diào)用了snapToTargetExistingView()方法對targetView進(jìn)行滾動調(diào)整,以確保停止的位置是在對應(yīng)的坐標(biāo)上,這就是RecyclerView添加該OnScrollListener的目的。
mScrolled為true表示之前進(jìn)行過滾動,newState為SCROLL_STATE_IDLE狀態(tài)表示滾動結(jié)束停下來
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抽象方法
計算最終對齊要移動的距離
計算二個參數(shù)對應(yīng)的 ItemView 當(dāng)前的坐標(biāo)與需要對齊的坐標(biāo)之間的距離。該方法返回一個大小為 2 的 int 數(shù)組,分別對應(yīng)out[0] 為 x 方向移動的距離,out[1] 為 y 方向移動的距離。
@SuppressWarnings("WeakerAccess") @Nullable public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager, @NonNull View targetView);
findSnapView抽象方法
找到要對齊的View
該方法會找到當(dāng)前 layoutManager 上最接近對齊位置的那個 view ,該 view 稱為 SanpView ,對應(yīng)的 position 稱為 SnapPosition 。如果返回 null ,就表示沒有需要對齊的 View ,也就不會做滾動對齊調(diào)整。
@SuppressWarnings("WeakerAccess") @Nullable public abstract View findSnapView(LayoutManager layoutManager);
findTargetSnapPosition抽象方法
找到需要對齊的目標(biāo)View的的Position。
更加詳細(xì)一點(diǎn)說就是該方法會根據(jù)觸發(fā) Fling 操作的速率(參數(shù) velocityX 和參數(shù) velocityY )來找到 RecyclerView 需要滾動到哪個位置,該位置對應(yīng)的 ItemView 就是那個需要進(jìn)行對齊的列表項(xiàng)。我們把這個位置稱為 targetSnapPosition ,對應(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)橹挥谐^該速率,ItemView才會有足夠的動力在手指離開屏幕時繼續(xù)滾動下去。
@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方法源代碼,就是通過該方法實(shí)現(xiàn)平滑滾動并使得在滾動停止時itemView對齊到目的坐標(biāo)位置
首先layoutManager必須實(shí)現(xiàn)ScrollVectorProvider接口才能繼續(xù)往下操作
然后通過createSnapScroller方法創(chuàng)建一個SmoothScroller,這個東西是一個平滑滾動器,用于對ItemView進(jìn)行平滑滾動操作
根據(jù)x和y方向的速度來獲取需要對齊的View的位置,需要子類實(shí)現(xiàn)
最終通過 SmoothScroller 來滑動到指定位置
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()方法會先判斷l(xiāng)ayoutManager是否實(shí)現(xiàn)了ScrollVectorProvider接口,如果沒有實(shí)現(xiàn)該接口就不允許通過該方法做滾動操作。接下來就去創(chuàng)建平滑滾動器SmoothScroller的一個實(shí)例,layoutManager可以通過該平滑滾動器來進(jìn)行滾動操作。SmoothScroller需要設(shè)置一個滾動的目標(biāo)位置,將通過findTargetSnapPosition()方法來計算得到的targetSnapPosition給它,告訴滾動器要滾到這個位置,然后就啟動SmoothScroller進(jìn)行滾動操作。
接著看下createSnapScroller這個方法源碼
先判斷l(xiāng)ayoutManager是否實(shí)現(xiàn)了ScrollVectorProvider這個接口,沒有實(shí)現(xiàn)該接口就不創(chuàng)建SmoothScroller
這里創(chuàng)建一個LinearSmoothScroller對象,然后返回給調(diào)用函數(shù),也就是說,最終創(chuàng)建出來的平滑滾動器就是這個LinearSmoothScroller
在創(chuàng)建該LinearSmoothScroller的時候主要考慮兩個方面:
第一個是滾動速率,由calculateSpeedPerPixel()方法決定;
第二個是在滾動過程中,targetView即將要進(jìn)入到視野時,將勻速滾動變換為減速滾動,然后一直滾動目的坐標(biāo)位置,使?jié)L動效果更真實(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的三個抽象方法,從而讓ItemView滾動居中對齊。那么具體怎么做到呢?
3.2 calculateDistanceToFinalSnap()方法源碼
calculateDistanceToFinalSnap源碼如下所示
如果是水平方向滾動的,則計算水平方向需要移動的距離,否則水平方向的移動距離為0
如果是豎直方向滾動的,則計算豎直方向需要移動的距離,否則豎直方向的移動距離為0
distanceToCenter方法主要作用是:計算水平或者豎直方向需要移動的距離
@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方法
計算對應(yīng)的view的中心坐標(biāo)到RecyclerView中心坐標(biāo)之間的距離
首先是找到targetView的中心坐標(biāo)
接著也就是找到容器【RecyclerView】的中心坐標(biāo)
兩個中心坐標(biāo)的差值就是targetView需要滾動的距離
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()方法源碼
也就是找到要對齊的View
根據(jù)layoutManager的布局方式(水平布局方式或者豎向布局方式)區(qū)分計算,但最終都是通過findCenterView()方法來找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)前是否支持垂直滾動還是橫向滾動
循環(huán)LayoutManager的所有子元素,計算每個 childView的中點(diǎn)距離Parent 的中點(diǎn),找到距離最近的一個,就是需要居中對齊的目標(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,來看一下在findTargetSnapPosition操作了什么
如果是水平方向滾動的列表,估算出水平方向SnapHelper響應(yīng)fling,對齊要滑動的position和當(dāng)前position的差,否則,水平方向滾動的差值為0
如果是豎直方向滾動的列表,估算出豎直方向SnapHelper響應(yīng)fling,對齊要滑動的position和當(dāng)前position的差,否則,豎直方向滾動的差值為0
這個方法在計算targetPosition的時候把布局方式和布局方向都考慮進(jìn)去了。布局方式可以通過layoutManager.canScrollHorizontally()/layoutManager.canScrollVertically()來判斷,布局方向就通過RecyclerView.SmoothScroller.ScrollVectorProvider這個接口中的computeScrollVectorForPosition()方法來判斷。
@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)行輔助滾動對齊。官方提供的LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager都實(shí)現(xiàn)了這個接口,所以都支持SnapHelper。
3.6 OrientationHelper類
如何創(chuàng)建OrientationHelper對象呢?如下所示
比如,上面三個抽象方法都使用到了這個類,這個類是干嘛的?
計算位置的時候用的是OrientationHelper這個工具類,它是LayoutManager用于測量child的一個輔助類,可以根據(jù)Layoutmanager的布局方式和布局方向來計算得到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計算偏移量
如下所示
首先,計算滾動的總距離,這個距離受到觸發(fā)fling時的速度的影響,得到一個distances數(shù)組
然后計算每個ItemView的長度
根據(jù)是橫向布局還是縱向布局,來取對應(yīng)布局方向上的滾動距離
總結(jié)大概流程就是:用滾動總距離除以itemview的長度,從而估算得到需要滾動的item數(shù)量,此數(shù)值就是位置偏移量。而滾動距離是通過SnapHelper的calculateScrollDistance()方法得到的,ItemView的長度是通過computeDistancePerChild()方法計算出來。
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)了居中對齊,那么我們只要更改一下對齊的規(guī)則就行,更改為開始對齊(計算目標(biāo) View到 Parent start 要滑動的距離),其他的邏輯和 LinearSnapHelper 是一樣的。因此我們選擇繼承 LinearSnapHelper
大概流程
重寫calculateDistanceToFinalSnap方法,計算SnapView當(dāng)前位置與目標(biāo)位置的距離
寫findSnapView方法,找到當(dāng)前時刻的SnapView
可以發(fā)現(xiàn)完成上面兩個方法就可以呢,但是感覺滑動效果不太好?;瑒颖容^快時,會滾動很遠(yuǎn)。在分析了上面的代碼可知,滾動速率,由createSnapScroller方法中的calculateSpeedPerPixel()方法決定。那么是不是可以修改一下速率就可以解決問題呢。最后測試真的可以,ok,完成了。
當(dāng)然還會發(fā)現(xiàn)滾動時候,會滑動多個item,如果相對item個數(shù)做限制,可以在findTargetSnapPosition()方法中處理。
代碼地址:https://github.com/yangchong2...
4.2 自定義helper類
重寫calculateDistanceToFinalSnap方法
這里需要知道,在LinearSnapHelper中,out[0]和out[1]是通過distanceToCenter獲取的。那么既然要設(shè)置開始對齊,那么這里需要創(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)前時刻的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(); //需要判斷是否是最后一個Item,如果是最后一個則不讓對齊,以免出現(xiàn)最后一個顯示不完全。 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); }
修改滾動速率
@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) { //這個地方可以自己設(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)于我的博客我的個人站點(diǎn):www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/72109.html
摘要:缺點(diǎn)自動裝箱的存在意味著每一次插入都會有額外的對象創(chuàng)建。對象本身是一層額外需要被創(chuàng)建以及被垃圾回收的對象。相較于我們舍棄了和類型的放棄了并依賴于二分法查找。 目錄介紹 25.0.0.0 請說一下RecyclerView?adapter的作用是什么,幾個方法是做什么用的?如何理解adapter訂閱者模式? 25.0.0.1 ViewHolder的作用是什么?如何理解ViewHolder...
摘要:支持復(fù)雜頁面,例如添加自定義頭部和底部布局,支持橫向滑動,還可以支持粘貼頭部類似微信好友分組,支持不規(guī)則瀑布流效果,支持側(cè)滑刪除功能。支持粘貼頭部的需求效果,這種效果類似微信好友分組的那種功能界面。 目錄介紹 1.復(fù)雜頁面庫介紹 2.本庫優(yōu)勢亮點(diǎn) 2.1 支持多種狀態(tài)切換管理 2.2 支持添加多個header和footer 2.3 支持側(cè)滑功能和拖拽移動 2.4 其他亮點(diǎn)介紹 ...
摘要:機(jī)制博客從到學(xué)習(xí)介紹從到學(xué)習(xí)上搭建環(huán)境并構(gòu)建運(yù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...
閱讀 1129·2021-09-13 10:29
閱讀 3447·2019-08-29 18:31
閱讀 2718·2019-08-29 11:15
閱讀 3071·2019-08-26 13:25
閱讀 1454·2019-08-26 12:00
閱讀 2471·2019-08-26 11:41
閱讀 3564·2019-08-26 10:31
閱讀 1558·2019-08-26 10:25