一次失敗的RecycleView滑動定位,SnapHelper 真香!
週五給大家來一篇輕鬆一點的文章~
前段時間開的新專案,現在終於開始動工了,我和另一個小夥伴一起做,由於他還在處理另一個專案的尾巴,所以前期只有我一個人來做。之後我也會圍繞著這個專案來講一些我遇到的一些問題,和聯想發散的一些問題。
1 動機
image

image
這是UI給的圖
“精品話題”板塊,這部分我用recycleview做了個橫向滑動,然後讓女朋友試用。
問題就出在我女朋友試用後說體驗不好:
-
滑動速度太快了。
-
滑動結束沒有item在中間位置。

image
滑動速率太快,沒有定位
2 滑動速率
找了一圈可呼叫的方法,卻沒有看到可以直接設定速度的。
卒!只有從相關程式碼裡面找找看了。
RecycleView提供了兩個滑動監聽:
OnScrollListener和OnFlingListener
public abstract static class OnScrollListener {//SCROLL_STATE_IDLE、SCROLL_STATE_DRAGGING、SCROLL_STATE_SETTLING三個狀態public void onScrollStateChanged(RecyclerView recyclerView, int newState){}//滑動過程中一直會被呼叫public void onScrolled(RecyclerView recyclerView, int dx, int dy){}}
看了下被呼叫場景,並沒有什麼卵用。
public abstract static class OnFlingListener {//Override this to handle a fling given the velocities in both x and y directions.public abstract boolean onFling(int velocityX, int velocityY);}
這個介面就比較有意思了,重寫onFling可以處理拋投(手指快速滑動引起的螢幕慣性滑動),velocityX,velocityY就是x軸,Y軸上的速率啊。順藤摸瓜,看看在哪設定的具體的數值。
在唯一呼叫onFling()的地方,我找到了這樣一段程式碼:
public boolean fling(int velocityX, int velocityY) {//其他邏輯if (!dispatchNestedPreFling(velocityX, velocityY)) {//其他邏輯if (canScroll) {//其他邏輯velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));mViewFlinger.fling(velocityX, velocityY);return true;}}return false;}
可見,最後先判斷能否滑動,然後通過Math.max()確定具體數值的。
引數裡的velocityX, velocityY是函式傳過來的,mMaxFlingVelocity是啥啊??

image

image
去特喵的!是個private final屬性。
初始值是8000dp,最終數值根據螢幕解析度轉換成px後確定。
木有辦法,雖然不能通過set直接設定,好歹也找到了屬性名,那就通過反射來做。
//設定RecyclerView最大滑動速度private void setMaxFlingVelocity(RecyclerView recycleview, int velocity) {try{Field field = recycleview.getClass().getDeclaredField("mMaxFlingVelocity");field.setAccessible(true);field.set(recycleview, velocity);}catch (Exception e){e.printStackTrace();}}
設定個2000,走你!(設定4000使用起來比較舒服)

image
3 監聽
定位這一步肯定是在滑動快結束或者結束的時候經過判斷來決定停在哪的。
上面寫到的兩個滑動監聽中,OnFlingListener 恕本人無能,暫時沒有想到什麼辦法去使用velocityX, velocityY 兩個引數。所以只有考慮用OnScrollListener。
onScrollStateChanged() 和 onScrolled()
onScrolled()是在檢視滾動的過程中,一直會被呼叫的方法。
肯定不能在這裡面做判斷。
onScrollStateChanged()是在滑動狀態改變時候回撥的方法。並且引數傳回來一個newState。這引數代表著當前RV的狀態。這個狀態有三個。
public static final int SCROLL_STATE_IDLE = 0;public static final int SCROLL_STATE_DRAGGING = 1;public static final int SCROLL_STATE_SETTLING = 2;
0代表滑動停止;1代表正在被拖動;2代表當前在慣性滑動;
當我們拖拽view的時候有兩種情況:1→0 或者 1→2→0
所以我們就在view停止滑動的時候再去定位就好。
4 定位
recyclerView.scrollBy(int x, int y);recyclerView.scrollTo(int x, int y);recyclerView.scrollToPosition(int position);recyclerView.smoothScrollBy(int dx, int dy);recyclerView.smoothScrollToPosition(int position);
嘿嘿嘿!
看到position就開心。要算寬度dp什麼的最麻煩了,實在不行再去算寬度嘛。
現在就去找怎麼得到當前的position了。
scrollToPosition是直接顯示position的item。
smoothScrollToPosition是平滑到position的item。
當然選擇第smooth啦。
linearLayoutManager.findFirstVisibleItemPosition();linearLayoutManager.findLastVisibleItemPosition();linearLayoutManager.findFirstCompletelyVisibleItemPosition();linearLayoutManager.findLastCompletelyVisibleItemPosition();
前面兩個方法是找到螢幕顯示到的第一個/最後一個item(有可能只顯示了一半)的position。
後面兩個方法是找到螢幕顯示到的第一個/最後一個完整的item(有可能它兩邊還有沒顯示完整的item)的position。
我還是太年輕了!嚶嚶嚶!

image
這尼瑪什麼沙雕效果
當我們使用前兩個position時,永遠會遇到定位不到第一個或者最後一個的問題。
當時候後面兩個position的時候,90%你滑出來的position因為view顯示不全返回-1,眼睜睜看著她崩潰。而且這個smoothscroll效果也太不好了!
上面這四種定位的方式不適合當前情況,只適合螢幕能顯示整數個的情況,也就是recycleView在最邊緣的時候,螢幕不會有顯示不全的view。從一開始的方向就錯誤了。
點題,失敗的滑動定位!
5 解決辦法
後來前輩給我說了這麼一段程式碼:
new LinearSnapHelper().attachToRecyclerView(recycleview);

image
我當時的表情
用了一次之後,發現這玩意兒SnapHelper,真香!
* https://www.jianshu.com/p/e54db232df62 *SnapHelper 好文,如果你也不知道,趕緊學一波。

image
最後
之前都是寫設計模式,一直也沒有寫過啥有深度的話題(我認為的有深度應該就是會涉及到原始碼的分析,或者一個很難的課題),這一次想寫有深度文章的嘗試,正如題目所說,應該是失敗了。唉~ 還是太弱了。
對Android正遇瓶頸的同學們,想找到學習空間的同學可以瞭解一下我們的資料
附錄
Android高階技術大綱,以及系統進階視訊

技術大綱