前言
在原來的文章中我提及了如何使用RecyclerView
添加header
與footer
游昼,今天我們來更深入的擴展一下使用RecyclerView
實現(xiàn)常用的下拉刷新與上拉加載更多的功能。當然這些功能的實現(xiàn)也是基于前面的RecyclerView添加header與footer為基礎來實現(xiàn)的尝蠕,不是很了解的可以先看看前面的文章可能能更好的幫助理解烘豌。
依賴
為了方法大家的使用我已經(jīng)把他上傳到Jcenter
中了,所以大家可以調(diào)用下面的代碼了直接獲取使用:
compile 'com.idisfkj.enchancerecyclerview:mylibrary:1.1.1'
EnhanceRecyclerView
我將這個擴展的RecyclerView
命名為EnhanceRecyclerView
看彼,繼承RecyclerView
廊佩。我們知道既然要實現(xiàn)下拉刷新與上拉更多自然先要實現(xiàn)頭部與尾部的布局,所以我們先利用前面的知識來為EnhanceRecycleView
添加header
與footer
public void initView() {
View headerView = LayoutInflater.from(getContext()).inflate(R.layout.head_layout, null);
View footerView = LayoutInflater.from(getContext()).inflate(R.layout.footer_layout, null);
addHeaderView(headerView);
addFooterView(footerView);
}
其中的布局文件就不多說了靖榕,至于addHeaderView與addFooterView方法可以查看我前面的那篇文章标锄,有詳細的介紹
設置監(jiān)聽器
既然要實現(xiàn)下拉刷新與上拉加載,自然少不了對監(jiān)聽器的處理序矩,所以下面來詳細介紹下對監(jiān)聽器OnScrollListener
與OnTouchListener
的處理鸯绿。
OnScrollListener
為EnhanceRecyclerView
添加addOnScrollListener
實現(xiàn)其中的onScrollStateChanged
與onScrolled
方法。
onScrolled
在onScrolled
中我們主要做的是獲取EnhanceRcyclerView
中item
的總數(shù)量簸淀、視圖顯示中的第一個item
在EnhanceRecyclerView
中所處的位置與視圖顯示中最后一個item
在EnhanceRecyclerView
中所處的位置瓶蝴。
對于item
的總數(shù)量很好獲取直接調(diào)用
totalCount = getLayoutManager().getItemCount();
由于RecyclerView
能實現(xiàn)LinearLayoutManager
、GridLayoutManager
與StaggeredGridLayoutManager
不同的布局租幕,所以另外兩個要根據(jù)不同的manager
來獲取,還是看具體代碼吧
if (getLayoutManager() instanceof LinearLayoutManager) {
lastItem = ((LinearLayoutManager) getLayoutManager()).findLastVisibleItemPosition();
firstVisible = ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();
} else {
into = ((StaggeredGridLayoutManager) getLayoutManager()).findLastVisibleItemPositions(into);
firstInto = ((StaggeredGridLayoutManager) getLayoutManager()).findFirstVisibleItemPositions(firstInto);
lastItem = into[0];
firstVisible = firstInto[0];
}
onScrollStateChanged
獲取到了那三個關鍵數(shù)據(jù)以后舷手,就可以在onScrollStateChanged
中實現(xiàn)具體的邏輯,在這個方法中主要實現(xiàn)的是對上拉加載更多的處理
if (lastItem == adapter.getItemCount() + 1 && newState == RecyclerView.SCROLL_STATE_IDLE && !isLoad) {
ViewGroup.LayoutParams params = getFooterView(0).getLayoutParams();
params.width = RecyclerView.LayoutParams.MATCH_PARENT;
params.height = RecyclerView.LayoutParams.WRAP_CONTENT;
getFooterView(0).setLayoutParams(params);
getFooterView(0).setVisibility(View.VISIBLE);
smoothScrollToPosition(totalCount);
isLoad = true;
loadMoreListener.onLoadMore();
}
if (firstVisible == 0) {
isTop = true;
} else {
isTop = false;
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) getHeaderView(0).getLayoutParams();
params.width = RecyclerView.LayoutParams.MATCH_PARENT;
params.height = RecyclerView.LayoutParams.WRAP_CONTENT;
params.setMargins(0, -getHeaderView(0).getHeight(), 0, 0);
getHeaderView(0).setLayoutParams(params);
}
簡單說明下劲绪,核心就是判斷lastItem
是否處在最后的位置男窟,如果是的話就繼續(xù)加載更多的操作盆赤,這里提供了一個對數(shù)據(jù)處理的接口所以只要實現(xiàn)loadMoreListener.onLoadMore();
即可。
上拉加載更多核心就是這么多歉眷,其它的可以查看源碼
OnTouchListener
這個監(jiān)聽器主要是對下拉刷新進行處理牺六。我們要分別對其中我們所熟悉的MotionEvent.ACTION_DOWN
、MotionEvent.ACTION_MOVE
與MotionEvent.ACTION_UP
進行處理汗捡。ACTION_DOWN
就是簡單的獲取按下的坐標位置淑际,這里就不多說了,下面主要的針對另外的兩個進行簡單說明扇住。
ACTION_MOVE
這做的邏輯就是對觸摸后的處理春缕,根據(jù)滑動的距離來動態(tài)的改變header
的文本與布局視圖的顯示。
public void touchMove(MotionEvent event) {
endY = event.getY();
moveY = endY - startY;
//防止item向上滑出
if (moveY > 0 && !isRefreshing) {
//防止回退文本顯示異常
scrollToPosition(0);
if (getHeaderView(0).getVisibility() == GONE)
getHeaderView(0).setVisibility(VISIBLE);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) getHeaderView(0).getLayoutParams();
params.width = RecyclerView.LayoutParams.MATCH_PARENT;
params.height = RecyclerView.LayoutParams.WRAP_CONTENT;
//使header隨moveY的值從頂部漸漸出現(xiàn)
if (moveY >= 400) {
moveY = 100 + moveY / 4;
} else {
moveY = moveY / 2;
}
viewHeight = getHeaderView(0).getHeight();
if (viewHeight <= 0)
viewHeight = 130;
moveY = moveY - viewHeight;
params.setMargins(0, (int) moveY, 0, 0);
getHeaderView(0).setLayoutParams(params);
if (moveY > 80) {
text.setText(getResources().getString(R.string.release_to_refresh));
} else {
text.setText(getResources().getString(R.string.pull_to_refresh));
}
} else {
if (getHeaderView(0).getVisibility() != GONE && !isRefreshing) {
getHeaderView(0).setVisibility(GONE);
}
}
}
至于下拉時與頂部的距離變化是通過設置margin
來動態(tài)改變的艘蹋。
ACTION_UP
最后的觸摸處理就是在離開屏幕時根據(jù)滑動的距離锄贼,是否調(diào)用加載數(shù)據(jù)的接口,或者隱藏下拉刷新頭部女阀,具體還是看代碼吧宅荤。
public void touchUp() {
if (!isRefreshing && (endY -startY) != 0 ) {
RecyclerView.LayoutParams params1 = (RecyclerView.LayoutParams) getHeaderView(0).getLayoutParams();
params1.width = RecyclerView.LayoutParams.MATCH_PARENT;
params1.height = RecyclerView.LayoutParams.WRAP_CONTENT;
if (moveY >= 80) {
text.setText(getResources().getString(R.string.refreshing));
params1.setMargins(0, 0, 0, 0);
isRefreshing = true;
//刷新數(shù)據(jù)
pullToRefresh.onRefreshing();
} else {
if (viewHeight <= 0)
viewHeight = 130;
params1.setMargins(0, -viewHeight, 0, 0);
getHeaderView(0).setVisibility(GONE);
}
getHeaderView(0).setLayoutParams(params1);
}
}
代碼中重要的地方都有指出相信都能看懂,這樣下拉與上拉的邏輯就基本實現(xiàn)了强品,下面來看接口的設計吧
下拉與上拉接口
public interface PullToRefreshListener {
void onRefreshing();
}
public void setPullToRefreshListener(PullToRefreshListener pullToRefresh) {
if (loadMoreListener == null) {
initListener();
}
this.pullToRefresh = pullToRefresh;
}
public interface LoadMoreListener {
void onLoadMore();
}
public void setLoadMoreListener(LoadMoreListener loadMoreListener) {
if (pullToRefresh == null) {
initListener();
}
this.loadMoreListener = loadMoreListener;
}
在運用是添加接口監(jiān)聽時初始化前面為EnhanceRecyclerView
所設置的監(jiān)聽膘侮。
狀態(tài)重置設置
在調(diào)用下拉刷新或者上拉加載更多之后屈糊,我們?yōu)槠錁嬙焱ㄓ梅椒▽崿F(xiàn)的榛,狀態(tài)的重置與數(shù)據(jù)的更新,方便統(tǒng)一調(diào)用逻锐。
public void setLoadMoreComplete() {
RecyclerView.LayoutParams params = (LayoutParams) getFooterView(0).getLayoutParams();
params.width = 0;
params.height = 0;
getFooterView(0).setLayoutParams(params);
getFooterView(0).setVisibility(View.GONE);
this.getAdapter().notifyDataSetChanged();
isLoad = false;
}
public void setRefreshComplete() {
RecyclerView.LayoutParams params1 = (RecyclerView.LayoutParams) getHeaderView(0).getLayoutParams();
params1.width = RecyclerView.LayoutParams.MATCH_PARENT;
params1.height = RecyclerView.LayoutParams.WRAP_CONTENT;
params1.setMargins(0, -getHeaderView(0).getHeight(), 0, 0);
getHeaderView(0).setLayoutParams(params1);
getHeaderView(0).setVisibility(GONE);
this.getAdapter().notifyDataSetChanged();
isRefreshing = false;
}
所用工作已經(jīng)完成下面來做個調(diào)用示范
使用
xml中引用
<com.idisfkj.mylibrary.EnhanceRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.idisfkj.mylibrary.EnhanceRecyclerView>
設置監(jiān)聽
mRecyclerView.setPullToRefreshListener(new com.idisfkj.mylibrary.EnhanceRecyclerView.PullToRefreshListener() {
@Override
public void onRefreshing() {
refreshData();
}
});
mRecyclerView.setLoadMoreListener(new EnhanceRecyclerView.LoadMoreListener() {
@Override
public void onLoadMore() {
loadMoreData();
}
});
refreshData()
與loadMoreData()
加載數(shù)據(jù)的邏輯就不展示了夫晌,只是要記住在請求網(wǎng)絡數(shù)據(jù)完之后要在他們中調(diào)用相應的mRecyclerView.setRefreshComplete()
與mRecyclerView.setLoadMoreComplete()
來重置狀態(tài)。
至于其他的Adapter
昧诱、LayoutManager
等的設置就不多說了晓淀,與原生的RecyclerView
是一樣的。
效果
總結
其實總的來說難點有兩個
- 添加
header
與footer
盏档。這個前面已經(jīng)攻克了凶掰,而且原理也相對簡單 - 實現(xiàn)觸摸與滑動監(jiān)聽邏輯。這個主要是對邏輯的理解蜈亩,對整個刷新的過程做個整體分析懦窘,就能很好的理解上面的代碼。對其中視圖的動態(tài)顯示做相應的變化與接口的調(diào)用就能很好的處理這些工程稚配。
當然上面的實現(xiàn)可能還有瑕疵畅涂,希望指出,我會相應的做修改或者你們修改后可以提交給我道川,我統(tǒng)一做修改午衰,謝謝立宜!
項目地址:https://github.com/idisfkj/EnhanceRecyclerView