Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> RecycleViewScrollHelper--RecyclerView滑動事件檢測的輔助類

RecycleViewScrollHelper--RecyclerView滑動事件檢測的輔助類

編輯:關於Android編程

目錄

概述

這是一個關於RecycleView滑動事件的輔助類,該輔助類可以檢測RecycleView滑動到頂部或者底部的狀態.
可用於實現RecycleView加載更多或者刷新(雖然刷新可以直接用SwipeRefreshLayout).也可用於某些滑動相關的需求,如FloatingActionButton的隱藏與顯示之類的.


關於RecycleView的滑動監聽

RecycleView本身已經提供了滑動的監聽接口,OnScrollListener,這個接口包含了以下的方法.

//當recycleView的滑動狀態改變時回調
public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
//當RecycleView滑動之後被回調
public void onScrolled(RecyclerView recyclerView,int dx, int dy){}

RecycleView的滑動狀態

/**
 * The RecyclerView is not currently scrolling.
 * 當前的recycleView不滑動(滑動已經停止時)
 */
public static final int SCROLL_STATE_IDLE = 0;

/**
 * The RecyclerView is currently being dragged by outside input such as user touch input.
 * 當前的recycleView被拖動滑動
 */
public static final int SCROLL_STATE_DRAGGING = 1;

/**
 * The RecyclerView is currently animating to a final position while not under
 * outside control.
 * 當前的recycleView在滾動到某個位置的動畫過程,但沒有被觸摸滾動.調用 scrollToPosition(int) 應該會觸發這個狀態
 */
public static final int SCROLL_STATE_SETTLING = 2;

由以上狀態我們可以根據不同的狀態去判斷RecycleView當前的位置或者是滾動狀態.


關於滑動位置的監聽

我們需要確定的是RecycleView是否已經滑動到底部或者是頂部.
由以上提及的狀態我們可以確定,當前RecycleView滑動到頂部或者底部時,其滾動狀態都是靜止的,這時狀態應該是SCROLL_STATE_IDLE.
確定了狀態,下面需要確定的就是當前的item是否為頂部或者是底部的item?
關於這個問題,其實RecycleView已經有相關的方法可以查詢到了(嚴格來說應該是RecycleViewLayoutManager),網上已經有很多相關的博客說明.這裡也是參考了一下一些博客,這裡給出一個地址,可以了解一下,下面也會提及如何檢測,如果覺得鏈接內容太多可以跳過.
參考鏈接

特別說明,為了避免混亂
1.這裡使用itemView表示adapter裡每一個position對應的view;
2.position都是指adapter中的數據的位置
3.使用childView表示RecycleView緩存復用的子view


檢測邊界的itemView

關於itemView的位置確定,可以通過LinearLayoutManager獲取到當前顯示的view對應adapter中的position.

LinearLayoutManager linearManager = (LinearLayoutManager) layoutManager;
//查找最後一個可見的item的position
int lastItemPosition = linearManager.findLastVisibleItemPosition();
//查找第一個可見的item的position
int firstItemPosition =linearManager.findFirstVisibleItemPosition();

這裡查找到的position也就是adapter中的位置.因此就可以通過position來確定當前的itemView是頂部還底部了.


完整的檢測方式(onScrollStateChanged)

//onScrollStateChanged 方法
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
//判斷是當前layoutManager是否為LinearLayoutManager
//只有LinearLayoutManager才有查找第一個和最後一個可見view位置的方法
if (layoutManager instanceof LinearLayoutManager) {
    LinearLayoutManager linearManager = (LinearLayoutManager) layoutManager;
    //獲取最後一個可見view的位置
    int lastItemPosition = linearManager.findLastVisibleItemPosition();
    //獲取第一個可見view的位置
    int firstItemPosition =linearManager.findFirstVisibleItemPosition();
    if (newState == RecyclerView.SCROLL_STATE_IDLE && lastItemPosition + 1 == adapter.getItemCount()) {
    //最後一個itemView的position為adapter中最後一個數據時,說明該itemView就是底部的view了
    //需要注意position從0開始索引,adapter.getItemCount()是數據量總數
    } 
    //同理檢測是否為頂部itemView時,只需要判斷其位置是否為0即可
    if (newState == RecyclerView.SCROLL_STATE_IDLE && firstItemPosition == 0) {}

以上是簡單的頂部/底部判斷方式.


簡單判斷方式的缺點

以上已經介紹了如何判斷RecycleView滑動到頂部和底部的方式.但這個判斷方式是有缺陷的.問題在於RecycleView的可見itemView的查找上.

itemView的可見問題

RecycleView中的itemView是可大可小的,這個取決於我們的實際使用場景及業務.當itemView的內容比較多時,將會占據相當一部分RecycleView的界面.所以我們往往存在這種情況:
某些itemView會在滑動過程中只顯示一部分或者一半
但是這種情況下,該itemView還是屬於一個可見(visible).

任何時候一個itemView只要有任何一部分顯示在RecycleView上時,該itemView都是可見的

回到我們之前查找邊界itemView的方法中,查找邊界用的方法是:

linearLayoutManager.findFirstVisibleItemPosition();

所以說到這裡應該明白了僅管第一個itemView只有一點點顯示在RecycleView中時,該itemView也是會被保留並查找到的,此時返回的位置確實是0(第一個itemView的position).

在大部分情況下,這種情況並沒有很大的問題.但是在某些情況下,這樣的情況會使我們的邏輯或者業務出現不正常的表現現象.


某些需求情況

有以下一種情況:

需求
當滑動到底部或者中途時,由於item可能很多,重新滑動回去是很麻煩的情況,所以會設置一個回到頂部的按鈕.當滑動到第一項itemView時,取消回到頂部按鈕的顯示.

導致的問題
通過以上的說明,有可能出現以下的情況,當滑動到第一項itemView顯示了一點點的時候回到頂部的按鈕就消失了.

解決方案
如果我們希望這個按鈕不是這麼快消失而是第一項itemView顯示完全顯示或者至少大部分顯示時才消失,那麼就需要檢測完全顯示的子view

示例圖片
這裡寫圖片描述
第一次不管怎麼滑動,除非完全滑動到頂部,否則按鈕不會消失.
切換不完全檢測顯示的childView之後,只要第一項(child-0)在界面中出現任何一部分,按鈕都會消失,這就是沒有檢測完全的childView的弊端.


檢測完全顯示的子view

針對以上可能”誤殺”的觸摸事件,我們可以進一步地去確定和優化檢測的條件.
首先回顧一下檢測的條件:

檢測當前RecycleView的滑動狀態 檢測當前是否滑動到了頂部或者底部

以上兩個檢測條件還是不會改變的,但是第二個檢測條件可進一步優化.下面以如何更准確地檢測是否滑動到第一項itemView為例.
我們已經取到RecycleView第一個itemView,現在已經確定了第一項itemView已經處於顯示的狀態,但是可能該itemView僅僅只是顯示了一小部分.我們希望該itemView是大部分可見或者完全可見.

解決關鍵
事實上,只要該itemView可見,說明該itemView至少有一部分是被繪制了,而只要進行了繪制,就可以得到該itemView相關的一些坐標屬性,從而與RecycleView進行比較即可判斷該itemView是否完全顯示或者部分顯示.

實現步驟

首先,我們需要獲取該itemView.由於該itemView可見並且是第一項itemView,所以必定在RecycleView的第一個子view(也就是緩存的第一個childView). 其次,我們需要獲取第一項itemView的頂部坐標,當該itemView完全顯示在RecycleView中時,該頂部坐標必然也在RecycleView的頂部坐標內;否則該頂部坐標將小於RecycleView的頂部坐標.

注意點

這裡有需要注意的地方,子view是依賴於RecycleView繪制的,所以獲取到的子view的頂部坐標是相對於RecycleView而言的坐標,RecycleView的頂部坐標也是相對於自身而言,所以RecycleView的頂部坐標應該是0

//獲取 RecycleView第一個子view
View childView=recycleView.getChildAt(0);
//獲取第一個子view的頂部坐標
int top=childView.getTop();
//獲取 RecycleView的頂部坐標
//正常來說RecycleView的頂部坐標應該是0,但是嚴格來考慮,當RecycleView設置了paddingTop時,所有子view的繪制將以paddingTop的位置為起始位置,所以實際的頂部應該是paddingTop的高度的數值.
int topEdge=recycleView.getPaddingTop();
if(top>=topEdge){
    //子view完全顯示
}

以上是關於第一項itemView是否完全檢測的方式,對於最後一項itemView檢測方式也是類似的,只是將檢測的itemView的top換成bottom,因為此時需要檢測的最後一項itemView的最底邊小於RecycleView的底部坐標時(實際上就是RecycleView的高),說明最後一項完全顯示了.


檢測滿屏數據時的RecycleView

通過以上說明,我們已經可以檢測頂部itemView及底部itemView完全顯示在界面上時的情況了,可以避免一些不正常的現象.
但這並不會完全解決所有的情況,在某些情況下可能還是會出現一些意想不到的問題.
假設有以下的情況:

需求
RecycleView只有一個item,並且item的高度不足以填充滿RecycleView的高度(也就是還有空白的空間可以顯示其它的item),這種情況下只要對RecycleView進行任意的滑動,結果會是什麼?

原因
其實從以上的分析我們可以確定在這種情況下不管怎麼滑動都會觸發滑動到頂部或者是底部的事件的.原因在於:
此時頂部及底部的itemView是同一個view並且已經完全顯示在RecycleView中,RecycleView還有空余的空白空間
在這種情況下只要進行了滑動,不管是什麼類型的滑動最終都會成功檢測到頂部itemView或者是底部itemView

解決方案
如果為了避免這種情況,我們就需要對在檢測時多進行一步,若當前的RecycleView顯示的itemView不滿屏的情況下,其實並不存在滑動的說法(沒有加載更多,因為根本數據還沒有滿屏顯示),至於下拉刷新的,還是可以的,但一般都會使用SwipeRefreshLayout實現下拉刷新了.因此這裡主要考慮的是關於滑動到底部加載更多的問題.

示例圖片
這裡寫圖片描述
圖中RecycleView中的item並沒有完全填充界面,有toast出現時都是進行了滑動的.
可見在沒有檢測是否滿屏顯示的情況下,只要有滑動都會觸發滑動到頂部或者底部的事件(在這裡有切換了優先檢測的過程,請注意toast).
當選擇兩個事件可以同時檢測觸發時,可見toast提示的文本是提示了兩次的


實現步驟

以下針對是否滑動到底部,暫不考慮滑動到頂部的情況.

參考完全顯示子view的檢測,對於底部,我們只要確保RecycleView最後一個子view底部坐標小於RecycleView底部坐標即可確定其完全顯示. 之後我們需要確定的是:是否有足夠的子view填充了整個RecycleView.同理只要確定第一個子view的頂部坐標小於RecycleView頂部坐標,此時第一個子view已經部分不可見,說明RecycleView是完全被填充的.

注意以下所有的坐標都是基於RecycleView本身,是相對於RecycleView的坐標,因為所有的子view都是在RecycleView內部繪制的

//獲取最後一個childView
View lastChildView = recyclerView.getChildAt(childCount - 1);
//獲取第一個childView
View firstChildView = recyclerView.getChildAt(0);
int top = firstChildView.getTop();
int bottom = lastChildView.getBottom();
 //recycleView顯示itemView的有效區域的bottom坐標Y
int bottomEdge = recyclerView.getHeight() - recyclerView.getPaddingBottom();
 //recycleView顯示itemView的有效區域的top坐標Y
int topEdge = recyclerView.getPaddingTop();
 //第一個view的頂部小於top邊界值,說明第一個view已經部分或者完全移出了界面
 //最後一個view的底部小於bottom邊界值,說明最後一個view已經完全顯示在界面
 //若滿足這兩個條件,說明所有子view已經填充滿了recycleView,recycleView可以"真正地"滑動
if (bottom <= bottomEdge && top < topEdge) {
    //滿屏的recyceView
}

其它

設置檢測滑動邊界時的容差值.在檢測完全顯示的childView時,檢測的方式是當childView邊界完全顯示在界面中時才會檢測成功.這就導致了一個可能的情況是,當只差一點點滑動到邊界時也不會檢測成功.這裡的靈敏度要求很高.
容差值的出現就是為了解決這種情況,當需要檢測時的靈敏度不需要這麼高時,允許在邊界一定范圍內即可檢測成功時,可以設置容差值.
容差值會使得更容易判斷成立.容差值必須為0或者正數,默認為0.

//設置頂部檢測的容差值
public void setTopOffsetFaultTolerance(int offset);
//設置底部檢測的容差值
public void setBottomOffsetFaultTolerance(int offset);

示例:當設置容差值為item的一半高度時,則在頂部或者底部item超過一半滑出界面時即可以觸發回調事件.


完整地檢測滿屏並滑動到底部(或者頂部)

將以上兩個檢測滿屏與檢測滑動到底部的方法組合起來即可.
最後附上更加具體的檢測方式:

可以設置先檢測滑動到底部還是頂部 可以設置在先檢測到某一種情況時是否還繼續檢測另一種情況(栽些情況下可能需要同時檢測是否滑動到頂部及底部) 可以設置是否檢測滿屏的情況(非滿屏情況下不觸發滑動事件) 可以設置檢測滑動到頂部/底部的容差值(即擴大檢測范圍)

GitHub地址

https://github.com/CrazyTaro/RecycleViewAdatper

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved