記一次失敗的RecycleView滑動定位

做者:點先生, 時間:2018.11.26設計模式

前段時間開的新項目,如今終於開始動工了,我和另外一個小夥伴一塊兒作,因爲他還在處理另外一個項目的尾巴,因此前期只有我一我的來作。以後我也會圍繞着這個項目來說一些我遇到的一些問題,和聯想發散的一些問題。bash

動機

這是UI給的圖

「精品話題」板塊,這部分我用recycleview作了個橫向滑動,而後讓女友試用。 問題就出在我女友試用後說體驗很差:ide

  1. 滑動速度太快了。
  2. 滑動結束沒有item在中間位置。
    滑動速率太快,沒有定位。

滑動速率

找了一圈可調用的方法,卻沒有看到能夠直接設置速度的。
卒!
只有從相關代碼裏面找找看了。RecycleView提供了兩個滑動監聽:OnScrollListenerOnFlingListener函數

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){}
    }
複製代碼

看了下被調用場景,並無什麼卵用。ui

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()的地方,我找到了這樣一段代碼:this

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是啥啊??spa

去特喵的!是個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使用起來比較舒服) 3d

監聽

定位這一步確定是在滑動快結束或者結束的時候通過判斷來決定停在哪的。上面寫到的兩個滑動監聽中,OnFlingListener 恕本人無能,暫時沒有想到什麼辦法去使用velocityX, velocityY 兩個參數。因此只有考慮用OnScrollListener。code

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中止滑動的時候再去定位就好。

定位

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。

我仍是太年輕了!嚶嚶嚶!

這尼瑪什麼沙雕效果

當咱們使用前兩個position時,永遠會遇到定位不到第一個或者最後一個的問題。
當時候後面兩個position的時候,90%你滑出來的position由於view顯示不全返回-1,眼睜睜看着她崩潰。並且這個smoothscroll效果也太很差了!
上面這四種定位的方式不適合當前狀況,只適合屏幕能顯示整數個的狀況,也就是recycleView在最邊緣的時候,屏幕不會有顯示不全的view。從一開始的方向就錯誤了。
點題,失敗的滑動定位!

解決辦法

後來前輩給我說了這麼一段代碼:
new LinearSnapHelper().attachToRecyclerView(recycleview);

我當時的表情

用了一次以後,發現這玩意兒SnapHelper,真香!

最後

以前都是寫設計模式,一直也沒有寫過啥有深度的話題(我認爲的有深度應該就是會涉及到源碼的分析,或者一個很難的課題),這一次想寫有深度文章的嘗試,正如題目所說,應該是失敗了。唉~ 仍是太弱了。本篇文章惟一有價值的信息,大概是:我有女友吧。

相關文章
相關標籤/搜索