目錄介紹
- 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實現功能
- 3.2 calculateDistanceToFinalSnap()方法原始碼
- 3.3 findSnapView()方法原始碼
- 3.4 findTargetSnapPosition()方法原始碼
- 3.5 支援哪些LayoutManager
- 3.6 OrientationHelper類
- 3.7 estimateNextPositionDiffForFling計算偏移量
- 04.自定義SnapHelper類
- 4.1 業務需求
- 4.2 自定義helper類
好訊息
- 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計47篇[近20萬字],轉載請註明出處,謝謝!
- 連結地址:github.com/yangchong21…
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!
01.SnapHelper簡單介紹
1.1 SnapHelper作用
- 在某些場景下,卡片列表滑動瀏覽[有的叫輪播圖],希望當滑動停止時可以將當前卡片停留在螢幕某個位置,比如停在左邊,以吸引使用者的焦點。那麼可以使用RecyclerView + Snaphelper來實現,SnapHelper旨在支援RecyclerView的對齊方式,也就是通過計算對齊RecyclerView中TargetView 的指定點或者容器中的任何畫素點。
1.2 SnapHelper類分析
- 查閱可知,SnapHelper繼承自RecyclerView.OnFlingListener,並且重寫了onFling方法,這個類程式碼並不多,下面會對重要方法一一解析。
- 支援SnapHelper的RecyclerView.LayoutManager必須實現的方式:
- RecyclerView.SmoothScroller.ScrollVectorProvider介面
- 或者自己實現onFling(int,int)方法手動處理邏輯。
- 支援SnapHelper的RecyclerView.LayoutManager必須實現的方式:
- SnapHelper類重要的方法
- attachToRecyclerView: 將SnapHelper attach 到指定的RecyclerView 上。
- calculateDistanceToFinalSnap:複寫這個方法計算對齊到TargetView或容器指定點的距離,這是一個抽象方法,由子類自己實現,返回的是一個長度為2的int 陣列out,out[0]是x方向對齊要移動的距離,out[1]是y方向對齊要移動的距離。
- calculateScrollDistance: 根據每個方向給定的速度估算滑動的距離,用於Fling 操作。
- findSnapView:提供一個指定的目標View 來對齊,抽象方法,需要子類實現
- findTargetSnapPosition:提供一個用於對齊的Adapter 目標position,抽象方法,需要子類自己實現。
- onFling:根據給定的x和 y 軸上的速度處理Fling。
- 什麼是Fling操作
- 手指在螢幕上滑動 RecyclerView然後鬆手,RecyclerView中的內容會順著慣性繼續往手指滑動的方向繼續滾動直到停止,這個過程叫做 Fling 。 Fling 操作從手指離開螢幕瞬間被觸發,在滾動停止時結束。
1.3 LinearSnapHelper類分析
- LinearSnapHelper 使當前Item居中顯示,常用場景是橫向的RecyclerView,類似ViewPager效果,但是又可以快速滑動(滑動多頁)。
- 最簡單的使用就是,如下程式碼
- 幾行程式碼就可以用RecyclerView實現一個類似ViewPager的效果,並且效果還不錯。可以快速滑動多頁,當前頁劇中顯示,並且顯示前一頁和後一頁的部分。
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之前已經附著到此RecyclerView上,則不用進行任何操作
- 如果SnapHelper之前附著的RecyclerView和現在的不一致,就將原來設定的回撥全部remove或者設定為null
- 然後更新RecyclerView物件引用,Attach的RecyclerView不為null,設定回撥Callback,主要包括滑動的回撥和Fling操作的回撥,初始化一個Scroller 用於後面做滑動處理,然後呼叫snapToTargetExistingView
- 大概流程就是:在attachToRecyclerView()方法中會清掉SnapHelper之前儲存的RecyclerView物件的回撥(如果有的話),對新設定進來的RecyclerView物件設定回撥,然後初始化一個Scroller物件,最後呼叫snapToTargetExistingView()方法對SnapView進行對齊調整。
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()原始碼
- 上面已經說了,滑動的回撥和Fling操作的回撥
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,或者當Scroll被觸發的時候和fling操作的時候對齊TargetView 。
- 判斷RecyclerView 和LayoutManager是否為null,接著呼叫findSnapView 方法來獲取需要對齊的目標View,注意:這是個抽象方法,需要子類實現
- 通過calculateDistanceToFinalSnap 獲取x方向和y方向對齊需要移動的距離
- 最後如果需要滾動的距離不是為0,就呼叫smoothScrollBy方法使RecyclerView滾動相應的距離
- 注意:RecyclerView.smoothScrollBy()這個方法的作用就是根據引數平滑滾動RecyclerView的中的ItemView相應的距離。
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監聽裡面做了什麼
- 該滾動監聽器的實現很簡單,只是在正常滾動停止的時候呼叫了snapToTargetExistingView()方法對targetView進行滾動調整,以確保停止的位置是在對應的座標上,這就是RecyclerView新增該OnScrollListener的目的。
- mScrolled為true表示之前進行過滾動,newState為SCROLL_STATE_IDLE狀態表示滾動結束停下來
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抽象方法
- 計算最終對齊要移動的距離
- 計算二個引數對應的 ItemView 當前的座標與需要對齊的座標之間的距離。該方法返回一個大小為 2 的 int 陣列,分別對應out[0] 為 x 方向移動的距離,out[1] 為 y 方向移動的距離。
@SuppressWarnings("WeakerAccess") @Nullable public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager, @NonNull View targetView); 複製程式碼
- 計算最終對齊要移動的距離
- findSnapView抽象方法
- 找到要對齊的View
- 該方法會找到當前 layoutManager 上最接近對齊位置的那個 view ,該 view 稱為 SanpView ,對應的 position 稱為 SnapPosition 。如果返回 null ,就表示沒有需要對齊的 View ,也就不會做滾動對齊調整。
@SuppressWarnings("WeakerAccess") @Nullable public abstract View findSnapView(LayoutManager layoutManager); 複製程式碼
- 找到要對齊的View
- findTargetSnapPosition抽象方法
- 找到需要對齊的目標View的的Position。
- 更加詳細一點說就是該方法會根據觸發 Fling 操作的速率(引數 velocityX 和引數 velocityY )來找到 RecyclerView 需要滾動到哪個位置,該位置對應的 ItemView 就是那個需要進行對齊的列表項。我們把這個位置稱為 targetSnapPosition ,對應的 View 稱為 targetSnapView 。如果找不到 targetSnapPosition ,就返回RecyclerView.NO_POSITION 。
public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, int velocityY); 複製程式碼
- 找到需要對齊的目標View的的Position。
2.3 onFling方法原始碼分析
-
SnapHelper繼承了 RecyclerView.OnFlingListener,實現了onFling方法。
- 獲取RecyclerView要進行fling操作需要的最小速率,為啥呢?因為只有超過該速率,ItemView才會有足夠的動力在手指離開螢幕時繼續滾動下去。
@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方法原始碼,就是通過該方法實現平滑滾動並使得在滾動停止時itemView對齊到目的座標位置
- 首先layoutManager必須實現ScrollVectorProvider介面才能繼續往下操作
- 然後通過createSnapScroller方法建立一個SmoothScroller,這個東西是一個平滑滾動器,用於對ItemView進行平滑滾動操作
- 根據x和y方向的速度來獲取需要對齊的View的位置,需要子類實現
- 最終通過 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; } 複製程式碼
- 總結一下可知:snapFromFling()方法會先判斷layoutManager是否實現了ScrollVectorProvider介面,如果沒有實現該介面就不允許通過該方法做滾動操作。接下來就去建立平滑滾動器SmoothScroller的一個例項,layoutManager可以通過該平滑滾動器來進行滾動操作。SmoothScroller需要設定一個滾動的目標位置,將通過findTargetSnapPosition()方法來計算得到的targetSnapPosition給它,告訴滾動器要滾到這個位置,然後就啟動SmoothScroller進行滾動操作。
-
接著看下createSnapScroller這個方法原始碼
- 先判斷layoutManager是否實現了ScrollVectorProvider這個介面,沒有實現該介面就不建立SmoothScroller
- 這裡建立一個LinearSmoothScroller物件,然後返回給呼叫函式,也就是說,最終建立出來的平滑滾動器就是這個LinearSmoothScroller
- 在建立該LinearSmoothScroller的時候主要考慮兩個方面:
- 第一個是滾動速率,由calculateSpeedPerPixel()方法決定;
- 第二個是在滾動過程中,targetView即將要進入到視野時,將勻速滾動變換為減速滾動,然後一直滾動目的座標位置,使滾動效果更真實,這是由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實現功能
- LinearSnapHelper實現了SnapHelper,並且實現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方法
- 計算對應的view的中心座標到RecyclerView中心座標之間的距離
- 首先是找到targetView的中心座標
- 接著也就是找到容器【RecyclerView】的中心座標
- 兩個中心座標的差值就是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
- 根據layoutManager的佈局方式(水平佈局方式或者豎向佈局方式)區分計算,但最終都是通過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方法原始碼
- 查詢當前是否支援垂直滾動還是橫向滾動
- 迴圈LayoutManager的所有子元素,計算每個 childView的中點距離Parent 的中點,找到距離最近的一個,就是需要居中對齊的目標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實現了SnapHelper,來看一下在findTargetSnapPosition操作了什麼
- 如果是水平方向滾動的列表,估算出水平方向SnapHelper響應fling,對齊要滑動的position和當前position的差,否則,水平方向滾動的差值為0
- 如果是豎直方向滾動的列表,估算出豎直方向SnapHelper響應fling,對齊要滑動的position和當前position的差,否則,豎直方向滾動的差值為0
- 這個方法在計算targetPosition的時候把佈局方式和佈局方向都考慮進去了。佈局方式可以通過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的各種情況,特意要求只有實現了RecyclerView.SmoothScroller.ScrollVectorProvider介面的layoutManager才能使用SnapHelper進行輔助滾動對齊。官方提供的LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager都實現了這個介面,所以都支援SnapHelper。
3.6 OrientationHelper類
- 如何建立OrientationHelper物件呢?如下所示
- 比如,上面三個抽象方法都使用到了這個類,這個類是幹嘛的?
- 計算位置的時候用的是OrientationHelper這個工具類,它是LayoutManager用於測量child的一個輔助類,可以根據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計算偏移量
- 如下所示
- 首先,計算滾動的總距離,這個距離受到觸發fling時的速度的影響,得到一個distances陣列
- 然後計算每個ItemView的長度
- 根據是橫向佈局還是縱向佈局,來取對應佈局方向上的滾動距離
- 總結大概流程就是:用滾動總距離除以itemview的長度,從而估算得到需要滾動的item數量,此數值就是位置偏移量。而滾動距離是通過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 業務需求
- LinearSnapHelper 實現了居中對齊,那麼我們只要更改一下對齊的規則就行,更改為開始對齊(計算目標 View到 Parent start 要滑動的距離),其他的邏輯和 LinearSnapHelper 是一樣的。因此我們選擇繼承 LinearSnapHelper
- 大概流程
- 重寫calculateDistanceToFinalSnap方法,計算SnapView當前位置與目標位置的距離
- 寫findSnapView方法,找到當前時刻的SnapView
- 可以發現完成上面兩個方法就可以呢,但是感覺滑動效果不太好。滑動比較快時,會滾動很遠。在分析了上面的程式碼可知,滾動速率,由createSnapScroller方法中的calculateSpeedPerPixel()方法決定。那麼是不是可以修改一下速率就可以解決問題呢。最後測試真的可以,ok,完成了。
- 當然還會發現滾動時候,會滑動多個item,如果相對item個數做限制,可以在findTargetSnapPosition()方法中處理。
- 程式碼地址:github.com/yangchong21…
4.2 自定義helper類
- 重寫calculateDistanceToFinalSnap方法
- 這裡需要知道,在LinearSnapHelper中,out[0]和out[1]是通過distanceToCenter獲取的。那麼既然要設定開始對齊,那麼這裡需要建立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方法,找到當前時刻的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,如果是最後一個則不讓對齊,以免出現最後一個顯示不完全。 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) { //這個地方可以自己設定 return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; } }; } 複製程式碼
關於其他內容介紹
關於其他內容介紹
01.關於部落格彙總連結
02.關於我的部落格
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:github.com/yangchong21…
- 知乎:www.zhihu.com/people/yang…
- 簡書:www.jianshu.com/u/b7b2c6ed9…
- csdn:my.csdn.net/m0_37700275
- 喜馬拉雅聽書:www.ximalaya.com/zhubo/71989…
- 開源中國:my.oschina.net/zbj1618/blo…
- 泡在網上的日子:www.jcodecraeer.com/member/cont…
- 郵箱:yangchong211@163.com
- 阿里雲部落格:yq.aliyun.com/users/artic… 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:segmentfault.com/u/xiangjian…