列表滾動過程
開始之前,我們想一下一個列表的滾動過程是怎樣的?
列表的滾動一般分為兩種:
- 手指按下 -> 手指拖拽列表移動 -> 手指停止拖拽 -> 抬起手指
- 手指按下 -> 手指快速拖拽後抬起手指 -> 列表繼續滾動 -> 停止滾動
從上面可以看出,滾動狀態分為:
|--靜止
|--滾動
|--被迫拖拽移動
|--自己滾動
複製程式碼
上面的過程的狀態變化如下:
- 靜止 -> 被迫拖拽移動 -> 靜止
- 靜止 -> 被迫拖拽移動 -> 自己滾動 -> 靜止
監聽RecyclerView的滾動
好了,我們分析完滾動的過程,再看看如何監聽RecyclerView的滾動.檢視原始碼是最好的方法.
看原始碼
檢視RecyclerView的原始碼,我們可以看到以下程式碼:
/**
* Set a listener that will be notified of any changes in scroll state or position.
* @param listener Listener to set or null to clear
* @deprecated Use {@link #addOnScrollListener(OnScrollListener)} and
* {@link #removeOnScrollListener(OnScrollListener)}
*/
@Deprecated
public void setOnScrollListener(OnScrollListener listener) {
mScrollListener = listener;
}
/**
* Add a listener that will be notified of any changes in scroll state or position.
* <p>Components that add a listener should take care to remove it when finished.
* Other components that take ownership of a view may call {@link #clearOnScrollListeners()}
* to remove all attached listeners.</p>
* @param listener listener to set or null to clear
*/
public void addOnScrollListener(OnScrollListener listener) {
if (mScrollListeners == null) {
mScrollListeners = new ArrayList<>();
}
mScrollListeners.add(listener);
}
複製程式碼
也就是說有兩種方式可以監聽滾動事件:
setOnScrollListener()
addOnScrollListener()
其中 setOnScrollListener 已經過時(@deprecated
),建議使用 addOnScrollListener.如果在原始碼中沒有 addOnScrollListener 方法,可能你的版本過舊,請升級recyclerview包.
設定的監聽器原始碼如下:
public abstract static class OnScrollListener {
/**
* Callback method to be invoked when RecyclerView's scroll state changes.
* @param recyclerView The RecyclerView whose scroll state has changed.
* @param newState The updated scroll state. One of {@link #SCROLL_STATE_IDLE},
* {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
*/
public void onScrollStateChanged(RecyclerView recyclerView, int newState){}
/**
* Callback method to be invoked when the RecyclerView has been scrolled. This will be
* called after the scroll has completed.
* <p>
* This callback will also be called if visible item range changes after a layout
* calculation. In that case, dx and dy will be 0.
*
* @param recyclerView The RecyclerView which scrolled.
* @param dx The amount of horizontal scroll.
* @param dy The amount of vertical scroll.
*/
public void onScrolled(RecyclerView recyclerView, int dx, int dy){}
}
複製程式碼
在滾動過程中,此監聽器會回撥兩個方法.
onScrollStateChanged : 滾動狀態變化時回撥 onScrolled : 滾動時回撥
這兩者的區別在於: 狀態與過程
舉例子
注 : 以下原始碼可在最後的地址中找到.
demoRv = (RecyclerView) findViewById(R.id.demo_rv);
layoutManager = new LinearLayoutManager(this);
demoRv.setLayoutManager(layoutManager);
demoRv.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
bookAdapter = new BookAdapter();
bookAdapter.fillList(MockService.getBookList());
demoRv.setAdapter(bookAdapter);
demoRv.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
Log.i(TAG, "-----------onScrollStateChanged-----------");
Log.i(TAG, "newState: " + newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
Log.i(TAG, "-----------onScrolled-----------");
Log.i(TAG, "dx: " + dx);
Log.i(TAG, "dy: " + dy);
Log.i(TAG, "CHECK_SCROLL_UP: " + recyclerView.canScrollVertically(TAG_CHECK_SCROLL_UP));
Log.i(TAG, "CHECK_SCROLL_DOWN: " + recyclerView.canScrollVertically(TAG_CHECK_SCROLL_DOWN));
}
});
複製程式碼
以上程式碼中輸出了主要個幾個資訊:
- newState : 目前的狀態
- dx : 水平滾動距離
- dy : 垂直滾動距離
onScrollStateChanged 方法
在void onScrollStateChanged(RecyclerView recyclerView, int newState)
中回撥兩個變數:
- recyclerView : 當前在滾動的RecyclerView
- newState : 當前滾動狀態.
其中newState有三種值:
//靜止,沒有滾動
public static final int SCROLL_STATE_IDLE = 0;
//正在被外部拖拽,一般為使用者正在用手指滾動
public static final int SCROLL_STATE_DRAGGING = 1;
//自動滾動開始
public static final int SCROLL_STATE_SETTLING = 2;
複製程式碼
onScrolled 方法
在void onScrolled(RecyclerView recyclerView, int dx, int dy)
方法中回撥了三個變數:
- recyclerView : 當前滾動的view
- dx : 水平滾動距離
- dy : 垂直滾動距離
真機實踐
執行程式碼
執行以上程式碼,然後按照上面的滾動過程分別進行兩種滾動.
第一種方式緩慢滾動結果如下:
I/MainActivity: -----------onScrollStateChanged-----------
I/MainActivity: newState: 1
I/MainActivity: -----------onScrolled-----------
I/MainActivity: dx: 0
I/MainActivity: dy: -6
I/MainActivity: CHECK_SCROLL_UP: true
I/MainActivity: CHECK_SCROLL_DOWN: true
------------------------n個onScrolled--------------------
I/MainActivity: -----------onScrolled-----------
I/MainActivity: dx: 0
I/MainActivity: dy: -2
I/MainActivity: CHECK_SCROLL_UP: true
I/MainActivity: CHECK_SCROLL_DOWN: false
I/MainActivity: -----------onScrollStateChanged-----------
I/MainActivity: newState: 0
複製程式碼
第二種快速滾動結果如下:
I/MainActivity: -----------onScrollStateChanged-----------
I/MainActivity: newState: 1
I/MainActivity: -----------onScrolled-----------
I/MainActivity: dx: 0
I/MainActivity: dy: 59
I/MainActivity: CHECK_SCROLL_UP: true
I/MainActivity: CHECK_SCROLL_DOWN: true
--------------------------n個onScrolled-------------------
I/MainActivity: -----------onScrolled-----------
I/MainActivity: dx: 0
I/MainActivity: dy: 54
I/MainActivity: CHECK_SCROLL_UP: true
I/MainActivity: CHECK_SCROLL_DOWN: true
I/MainActivity: -----------onScrollStateChanged-----------
I/MainActivity: newState: 2
I/MainActivity: -----------onScrolled-----------
I/MainActivity: dx: 0
I/MainActivity: dy: 56
I/MainActivity: CHECK_SCROLL_UP: true
I/MainActivity: CHECK_SCROLL_DOWN: true
--------------------------n個onScrolled-------------------
I/MainActivity: -----------onScrolled-----------
I/MainActivity: dx: 0
I/MainActivity: dy: 14
I/MainActivity: CHECK_SCROLL_UP: true
I/MainActivity: CHECK_SCROLL_DOWN: true
I/MainActivity: -----------onScrolled-----------
I/MainActivity: dx: 0
I/MainActivity: dy: 1
I/MainActivity: CHECK_SCROLL_UP: true
I/MainActivity: CHECK_SCROLL_DOWN: true
I/MainActivity: -----------onScrollStateChanged-----------
I/MainActivity: newState: 0
複製程式碼
分析結果
且在滾動過程中發現:
1.滾動方向
dy > 0 時為手指向上滾動,列表滾動顯示下面的內容 dy < 0 時為手指向下滾動,列表滾動顯示上面的內容
2.回撥過程
緩慢拖拽回撥過程:
1. newState = RecyclerView.SCROLL_STATE_DRAGGING;
2. dy 多次改變
3. newState = RecyclerView.SCROLL_STATE_IDLE
複製程式碼
快速滾動回撥過程:
1. newState = RecyclerView.SCROLL_STATE_DRAGGING;
2. dy 多次改變
3. newState = RecyclerView.SCROLL_STATE_SETTLING;
4. dy 多次改變
5. newState = RecyclerView.SCROLL_STATE_IDLE;
複製程式碼
3.頂端與底部
以上資訊中還列印了canScrollVertically
的資訊,其中:
RecyclerView.canScrollVertically(1)
的值表示是否能向下滾動,false表示已經滾動到底部RecyclerView.canScrollVertically(-1)
的值表示是否能向上滾動,false表示已經滾動到頂部
封裝
基於以上,我們可以封裝一個可以回撥滾動狀態和方向的RecyclerView.
先建立事件監聽的介面OnScrollCallback
,程式碼如下:
public interface OnScrollCallback {
void onStateChanged(ScrollRecycler recycler, int state);
void onScrollUp(ScrollRecycler recycler, int dy);
void onScrollToBottom();
void onScrollDown(ScrollRecycler recycler, int dy);
void onScrollToTop();
}
複製程式碼
再寫一個類ScrollRecycler
繼承RecyclerView
,在類中新增以下方法:
addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
callback.onStateChanged(ScrollRecycler.this, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
if (!recyclerView.canScrollVertically(1)) {
callback.onScrollToBottom();
}
if (!recyclerView.canScrollVertically(-1)) {
callback.onScrollToTop();
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0) {
callback.onScrollDown(ScrollRecycler.this, dy);
} else {
callback.onScrollUp(ScrollRecycler.this, dy);
}
}
});
複製程式碼
測試以下是否可行,下面直接給出測試結果:
I/MainActivity: onStateChanged: state:1
I/MainActivity: onScrollUp: -11
I/MainActivity: onScrollUp: -10
I/MainActivity: onScrollUp: -14
I/MainActivity: onScrollUp: -22
I/MainActivity: onStateChanged: state:2
I/MainActivity: onStateChanged: state:0
I/MainActivity: onScrollToTop:
複製程式碼
完畢,以上就是對RecyclerView的滾動的簡單研究,專案程式碼地址如下:Dev-Wiki/RecyclerScroll: RecyclerView滾動的Demo
更多文章請移步我的部落格:DevWiki Blog
重要說明
想隨時獲取最新部落格文章更新,請關注公共賬號DevWiki,或掃描下面的二維碼: