詳解RecyclerView下拉刷新與上拉更多
前言
在原來(lái)的文章中我提及了如何使用RecyclerView添加header與footer,今天我們來(lái)更深入的擴(kuò)展一下使用RecyclerView實(shí)現(xiàn)常用的下拉刷新與上拉加載更多的功能。當(dāng)然這些功能的實(shí)現(xiàn)也是基于前面的RecyclerView添加header與footer為基礎(chǔ)來(lái)實(shí)現(xiàn)的,不是很了解的可以先看看前面的文章可能能更好的幫助理解。
依賴
為了方法大家的使用我已經(jīng)把他上傳到Jcenter中了,所以大家可以調(diào)用下面的代碼了直接獲取使用:
- compile 'com.idisfkj.enchancerecyclerview:mylibrary:1.1.1'
EnhanceRecyclerView
我將這個(gè)擴(kuò)展的RecyclerView命名為EnhanceRecyclerView,繼承RecyclerView。我們知道既然要實(shí)現(xiàn)下拉刷新與上拉更多自然先要實(shí)現(xiàn)頭部與尾部的布局,所以我們先利用前面的知識(shí)來(lái)為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);
- }
其中的布局文件就不多說(shuō)了,至于addHeaderView與addFooterView方法可以查看我前面的那篇文章,有詳細(xì)的介紹
設(shè)置監(jiān)聽(tīng)器
既然要實(shí)現(xiàn)下拉刷新與上拉加載,自然少不了對(duì)監(jiān)聽(tīng)器的處理,所以下面來(lái)詳細(xì)介紹下對(duì)監(jiān)聽(tīng)器OnScrollListener與OnTouchListener的處理。
OnScrollListener
為EnhanceRecyclerView添加addOnScrollListener實(shí)現(xiàn)其中的onScrollStateChanged與onScrolled方法。
onScrolled
在onScrolled中我們主要做的是獲取EnhanceRcyclerView中item的總數(shù)量、視圖顯示中的***個(gè)item在EnhanceRecyclerView中所處的位置與視圖顯示中***一個(gè)item在EnhanceRecyclerView中所處的位置。
對(duì)于item的總數(shù)量很好獲取直接調(diào)用
- totalCount = getLayoutManager().getItemCount();
由于RecyclerView能實(shí)現(xiàn)LinearLayoutManager、GridLayoutManager與StaggeredGridLayoutManager不同的布局,所以另外兩個(gè)要根據(jù)不同的manager來(lái)獲取,還是看具體代碼吧
- 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
獲取到了那三個(gè)關(guān)鍵數(shù)據(jù)以后,就可以在onScrollStateChanged中實(shí)現(xiàn)具體的邏輯,在這個(gè)方法中主要實(shí)現(xiàn)的是對(duì)上拉加載更多的處理
- 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);
- }
簡(jiǎn)單說(shuō)明下,核心就是判斷l(xiāng)astItem是否處在***的位置,如果是的話就繼續(xù)加載更多的操作,這里提供了一個(gè)對(duì)數(shù)據(jù)處理的接口所以只要實(shí)現(xiàn)loadMoreListener.onLoadMore();即可。
上拉加載更多核心就是這么多,其它的可以查看源碼
OnTouchListener
這個(gè)監(jiān)聽(tīng)器主要是對(duì)下拉刷新進(jìn)行處理。我們要分別對(duì)其中我們所熟悉的MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE與MotionEvent.ACTION_UP進(jìn)行處理。ACTION_DOWN就是簡(jiǎn)單的獲取按下的坐標(biāo)位置,這里就不多說(shuō)了,下面主要的針對(duì)另外的兩個(gè)進(jìn)行簡(jiǎn)單說(shuō)明。
ACTION_MOVE
這做的邏輯就是對(duì)觸摸后的處理,根據(jù)滑動(dòng)的距離來(lái)動(dòng)態(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);
- }
- }
- }
至于下拉時(shí)與頂部的距離變化是通過(guò)設(shè)置margin來(lái)動(dòng)態(tài)改變的。
ACTION_UP
***的觸摸處理就是在離開(kāi)屏幕時(shí)根據(jù)滑動(dòng)的距離,是否調(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);
- }
- }
代碼中重要的地方都有指出相信都能看懂,這樣下拉與上拉的邏輯就基本實(shí)現(xiàn)了,下面來(lái)看接口的設(shè)計(jì)吧
下拉與上拉接口
- 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;
- }
在運(yùn)用是添加接口監(jiān)聽(tīng)時(shí)初始化前面為EnhanceRecyclerView所設(shè)置的監(jiān)聽(tīng)。
狀態(tài)重置設(shè)置
在調(diào)用下拉刷新或者上拉加載更多之后,我們?yōu)槠錁?gòu)造通用方法實(shí)現(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)完成下面來(lái)做個(gè)調(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>
設(shè)置監(jiān)聽(tīng)
- 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ù)的邏輯就不展示了,只是要記住在請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)完之后要在他們中調(diào)用相應(yīng)的mRecyclerView.setRefreshComplete()與 mRecyclerView.setLoadMoreComplete()來(lái)重置狀態(tài)。
至于其他的Adapter、LayoutManager等的設(shè)置就不多說(shuō)了,與原生的RecyclerView是一樣的。
總結(jié)
其實(shí)總的來(lái)說(shuō)難點(diǎn)有兩個(gè)
添加header與footer。這個(gè)前面已經(jīng)攻克了,而且原理也相對(duì)簡(jiǎn)單
實(shí)現(xiàn)觸摸與滑動(dòng)監(jiān)聽(tīng)邏輯。這個(gè)主要是對(duì)邏輯的理解,對(duì)整個(gè)刷新的過(guò)程做個(gè)整體分析,就能很好的理解上面的代碼。對(duì)其中視圖的動(dòng)態(tài)顯示做相應(yīng)的變化與接口的調(diào)用就能很好的處理這些工程。
當(dāng)然上面的實(shí)現(xiàn)可能還有瑕疵,希望指出,我會(huì)相應(yīng)的做修改或者你們修改后可以提交給我,我統(tǒng)一做修改,謝謝!