在寫listview的下拉刷新和上拉加載時,會遇到如下問題:
怎樣判斷listview滑動到了頂部或是底部?
下拉刷新時,一般只有當第一個item完全可見時,才處理下拉的邏輯,而上拉加載則處理方式比較多,可以在手指已經離開屏幕但listview由於慣性作用滑動到底部時自動加載,也可以在最底部顯示一個按鈕,點擊加載,加載完成后,又將按鈕置於新加載內容的最底部,還可以在最后一個item完全可見時處理手指滑動上拉的邏輯來進行加載,等等。
那么,再細化一點,第一個item完全可見和能見到一部分、最后一個item完全可見和僅僅能見到一部分分別需要怎么判斷?如果一個listview的item數目很少,都不足以充滿整個屏幕,顯然第一個和最后一個item都是完全可見的,這時候,還需要處理下拉和上拉的邏輯嗎?如果(不)需要,又該如何判斷?本文將對listview的item顯示狀態做個簡單總結,相信除了應用於下拉刷新和上拉加載之外,還會有其他價值。
demo非常簡單,就是一個listview界面,主要來看自定義的HwgtListView這個類:
HwgtListView.java:
public class HwgtListView extends ListView implements OnScrollListener {
public HwgtListView(Context context, AttributeSet attrs) {
super(context, attrs);
setOnScrollListener(this);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_IDLE:
Log.d("HWGT", "SCROLL_STATE_IDLE........");
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
Log.d("HWGT", "SCROLL_STATE_TOUCH_SCROLL........");
break;
case OnScrollListener.SCROLL_STATE_FLING:
Log.d("HWGT", "SCROLL_STATE_FLING........");
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Log.d("HWGT", "firstVisibleItem..=.."+firstVisibleItem +
" visibleItemCount..=.."+visibleItemCount +
" totalItemCount..=.."+totalItemCount);
}
}
由於我們主要是分析listview的滾動狀態,所以只需要實現OnScrollListener接口,復寫onScroll 和 onScrollStateChanged 兩個方法。
首先看onScrollStateChanged方法,它有一個scrollState參數,表示listview的三種滾動狀態:
1、手指接觸屏幕並開始滑動listview ,這個時候會打印 log:SCROLL_STATE_TOUCH_SCROLL。
2、手指離開屏幕,listview由於慣性還在滾動中,這個時候打印 log:SCROLL_STATE_FLING。
3、listview 停止滾動,這個時候打印 log:SCROLL_STATE_IDLE。
由於是滾動狀態的變化,StateChanged,所以,只是在三種狀態切換時打印一次log,手指剛接觸屏幕,還沒有滑動時,在onScrollStateChanged 方法中是不會打印log的。
以上是listview的item比較多,在屏幕上顯示不完的情況,如果item非常少,比如只有3個,連一屏幕都占不滿,則上述log的打印只會出現第一和第三種情況,不會出現SCROLL_STATE_FLING 的 log。在實際需求中,item比較少,連一屏幕都占不滿時,一般就不支持上拉加載了,因為沒意義,所以,這種情況只需要在case OnScrollListener.SCROLL_STATE_FLING:中做一個標識,用來判斷要不要響應手指上滑的邏輯。
再來看onScroll方法,該方法是listview正在滾動時觸發,不管手指是否在屏幕上。
onScroll 和 onScrollStateChanged 這兩個方法中相關的log打印順序是這樣的:
SCROLL_STATE_TOUCH_SCROLL // 首先是手指滑動屏幕引起onScrollStateChanged (從靜止到滾動的改變)
onScroll
onScroll
... ... // 然后,一堆onScroll ,說明正在滾動,到底一秒打印幾次,不是那么重要
SCROLL_STATE_TOUCH_SCROLL // 表示手指離開屏幕
onScroll
onScroll
... ... // 然后,又是一堆onScroll ,這次是手指已離開屏幕,只不過listview由於慣性作用而處於滾動狀態
SCROLL_STATE_IDLE // listview停止滾動了
有一種比較特殊的情況,手指輕點屏幕,並未觸發滾動時,會在打印 SCROLL_STATE_TOUCH_SCROLL log之前打印少量的 onScroll log信息,具體原因不太清楚,但是貌似不太影響平時的使用,有空再研究。
至此,onScroll 和 onScrollStateChanged 這兩個方法的作用和 listview 相關狀態的監聽響應順序已經清楚了,再來看onScroll 方法的參數,主要是3個int類型的參數,int firstVisibleItem、int visibleItemCount 和 int totalItemCount,顧名思義,
totalItemCount 指的是所有item的數量,
firstVisibleItem 代表的是屏幕上第一個可見的item的索引,不管它是完全可見還是只有很小的一部分可見。
visibleItemCount 則稍微有點特殊,它指的是屏幕上可見的item的數量,在屏幕被充滿時,這個值有時候會有差別,比如屏幕上最上邊和最下邊的item都只有非常小一部分可見時,稍微滑動listview讓屏幕上最上邊或是最下邊的item不可見,可能會導致 visibleItemCount 的數量減少1.所以,在使用這個參數時,需要留意這種特殊情況可能帶來的隱患。
接下來,結合上文總結的東西,看一下實際的應用:
1、判斷listview是否滑動到頂部
//在onScroll方法中判斷:
if (firstVisibleItem == 0) { // 只要第一個item有一小部分可見都會滿足條件
View firstView = view.getChildAt(firstVisibleItem);
if (firstView != null) {
if(firstView.getTop()==0){ // 判斷第一個item到頂部的距離
Log.d("HWGT", "滑動到頂部了,卧槽!");
}
}
}
上文已經分析過,僅僅只是判斷if (firstVisibleItem == 0)是不太准確的,因為只要是第一個item只有很小的一部分可見,都會導致這個條件成立,所以還需要判斷第一個item到屏幕頂部的距離,即(firstView.getTop()==0)。
2、判斷listview是由於慣性作用而到達底部的
//首先在onScroll方法中判斷listview到達底部:
if ((firstVisibleItem + visibleItemCount) == totalItemCount) {
View lastVisibleItemView = view.getChildAt(view.getChildCount() - 1);
if (lastVisibleItemView != null) {
if(getHeight() == lastVisibleItemView.getBottom()){
Log.d("HWGT", getHeight()+"...滾到底部了...=.."+lastVisibleItemView.getBottom());
}
}
}
同理,僅僅只是判斷 ((firstVisibleItem + visibleItemCount) == totalItemCount) 也是不准確的,
還需要判斷 (getHeight() == lastVisibleItemView.getBottom())。
那么如何判斷是由於慣性作用而不是手指拖動listview到達底部的呢?
上文已經分析過onScroll 和 onScrollStateChanged 這兩個方法的作用和 listview 相關狀態的監聽響應順序了,
在listview由於慣性作用滑動到底部的情況下,log的打印順序是這樣的:
SCROLL_STATE_TOUCH_SCROLL........
SCROLL_STATE_FLING........
1845...滾到底部了...=..1845
SCROLL_STATE_IDLE........
而listview在手指拖動下到達底部的情況下,log的打印順序是這樣的:
SCROLL_STATE_TOUCH_SCROLL........
1845...滾到底部了...=..1845
SCROLL_STATE_FLING........
SCROLL_STATE_IDLE........
所以在onScrollStateChanged 方法中增加相應的標識再結合onScroll方法中的判斷,就可以准確判斷listview是由於慣性作用而到達底部的了。
3、判斷listview是手指滑動過程中到達底部的
... ... //上文已經分析過了,參考 2、判斷listview是由於慣性作用而到達底部的
4、item的數量非常少,listview占不滿屏幕的情況
在這種情況下,下拉刷新還是有必要的,但是上拉加載就不一定了,那怎樣判斷item非常少時,listview要不要響應手指的滑動事件呢?
listview占不滿屏幕時,log的打印上有幾個比較特殊的地方:
A、剛一進入界面時,在onScroll方法中就會打印類似
滑動到頂部了
607...滾到底部了...=..607
這樣的log(測試時有3個item)
B、在手指滑動過程中,onScrollStateChanged 方法中則
只有
SCROLL_STATE_TOUCH_SCROLL........
SCROLL_STATE_IDLE........
沒有
SCROLL_STATE_FLING........
其實,在item很多且屏幕都顯示不完的情況下,剛進入界面時也會在onScroll方法中打印log
滑動到頂部了
把這些特點利用起來,完成普通的需求,還是比較容易的。