編輯:關於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
已經有相關的方法可以查詢到了(嚴格來說應該是RecycleView
的LayoutManager
),網上已經有很多相關的博客說明.這裡也是參考了一下一些博客,這裡給出一個地址,可以了解一下,下面也會提及如何檢測,如果覺得鏈接內容太多可以跳過.
參考鏈接
特別說明,為了避免混亂
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的查找上.
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的弊端.
針對以上可能”誤殺”的觸摸事件,我們可以進一步地去確定和優化檢測的條件.
首先回顧一下檢測的條件:
RecycleView
的滑動狀態 檢測當前是否滑動到了頂部或者底部
以上兩個檢測條件還是不會改變的,但是第二個檢測條件可進一步優化.下面以如何更准確地檢測是否滑動到第一項itemView為例.
我們已經取到RecycleView
第一個itemView,現在已經確定了第一項itemView已經處於顯示的狀態,但是可能該itemView僅僅只是顯示了一小部分.我們希望該itemView是大部分可見或者完全可見.
解決關鍵
事實上,只要該itemView可見,說明該itemView至少有一部分是被繪制了,而只要進行了繪制,就可以得到該itemView相關的一些坐標屬性,從而與RecycleView
進行比較即可判斷該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超過一半滑出界面時即可以觸發回調事件.
將以上兩個檢測滿屏與檢測滑動到底部的方法組合起來即可.
最後附上更加具體的檢測方式:
https://github.com/CrazyTaro/RecycleViewAdatper
簡述:相信很多學安卓的都是從java入門之後開始進行安卓的學習,而當我們面臨安卓線程的書寫的時候,發現安卓線程並不是我們想象中使用java的線程寫法就可以。java線程的
Android通過PackageManagerService(後面簡稱Pms)進行包管理,其主要功能包括:用戶ID分配、包解析、包的安裝卸載等。本文不對Pms進行分析,
這樣的一個控件實現起來不難,需要對自定義view有一定的基礎,也要了解怎麼實現一個集合的排序。大體思路很簡單。首先完成view的基本繪制以及相關的內部邏輯。 其次,就是要
小米聯合中國聯通推出了新機——紅米3X,具備4100mAh超大電池,售價899元。現在,這款手機已經在中國聯通官網開啟預約,只需輸入