前言
在原来的文章中我提及了如何使用RecyclerView
添加header
与footer
,今天我们来更深入的扩展一下使用RecyclerView
实现常用的下拉刷新与上拉加载更多的功能。当然这些功能的实现也是基于前面的RecyclerView添加header与footer为基础来实现的,不是很了解的可以先看看前面的文章可能能更好的帮助理解。
依赖
为了方法大家的使用我已经把他上传到Jcenter
中了,所以大家可以调用下面的代码了直接获取使用:
compile 'com.idisfkj.enchancerecyclerview:mylibrary:1.1.1'
EnhanceRecyclerView
我将这个扩展的RecyclerView
命名为EnhanceRecyclerView
,继承RecyclerView
。我们知道既然要实现下拉刷新与上拉更多自然先要实现头部与尾部的布局,所以我们先利用前面的知识来为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方法可以查看我前面的那篇文章,有详细的介绍
设置监听器
既然要实现下拉刷新与上拉加载,自然少不了对监听器的处理,所以下面来详细介绍下对监听器OnScrollListener
与OnTouchListener
的处理。
OnScrollListener
为EnhanceRecyclerView
添加addOnScrollListener
实现其中的onScrollStateChanged
与onScrolled
方法。
onScrolled
在onScrolled
中我们主要做的是获取EnhanceRcyclerView
中item
的总数量、视图显示中的第一个item
在EnhanceRecyclerView
中所处的位置与视图显示中最后一个item
在EnhanceRecyclerView
中所处的位置。
对于item
的总数量很好获取直接调用
totalCount = getLayoutManager().getItemCount();
由于RecyclerView
能实现LinearLayoutManager
、GridLayoutManager
与StaggeredGridLayoutManager
不同的布局,所以另外两个要根据不同的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
获取到了那三个关键数据以后,就可以在onScrollStateChanged
中实现具体的逻辑,在这个方法中主要实现的是对上拉加载更多的处理
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
是否处在最后的位置,如果是的话就继续加载更多的操作,这里提供了一个对数据处理的接口所以只要实现loadMoreListener.onLoadMore();
即可。
上拉加载更多核心就是这么多,其它的可以查看源码
OnTouchListener
这个监听器主要是对下拉刷新进行处理。我们要分别对其中我们所熟悉的MotionEvent.ACTION_DOWN
、MotionEvent.ACTION_MOVE
与MotionEvent.ACTION_UP
进行处理。ACTION_DOWN
就是简单的获取按下的坐标位置,这里就不多说了,下面主要的针对另外的两个进行简单说明。
ACTION_MOVE
这做的逻辑就是对触摸后的处理,根据滑动的距离来动态的改变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的值从顶部渐渐出现
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
来动态改变的。
ACTION_UP
最后的触摸处理就是在离开屏幕时根据滑动的距离,是否调用加载数据的接口,或者隐藏下拉刷新头部,具体还是看代码吧。
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;
//刷新数据
pullToRefresh.onRefreshing();
} else {
if (viewHeight <= 0)
viewHeight = 130;
params1.setMargins(0, -viewHeight, 0, 0);
getHeaderView(0).setVisibility(GONE);
}
getHeaderView(0).setLayoutParams(params1);
}
}
代码中重要的地方都有指出相信都能看懂,这样下拉与上拉的逻辑就基本实现了,下面来看接口的设计吧
下拉与上拉接口
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;
}
在运用是添加接口监听时初始化前面为EnhanceRecyclerView
所设置的监听。
状态重置设置
在调用下拉刷新或者上拉加载更多之后,我们为其构造通用方法实现,状态的重置与数据的更新,方便统一调用。
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;
}
所用工作已经完成下面来做个调用示范
使用
xml中引用
设置监听
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()
加载数据的逻辑就不展示了,只是要记住在请求网络数据完之后要在他们中调用相应的mRecyclerView.setRefreshComplete()
与mRecyclerView.setLoadMoreComplete()
来重置状态。
至于其他的Adapter
、LayoutManager
等的设置就不多说了,与原生的RecyclerView
是一样的。
效果
总结
其实总的来说难点有两个
- 添加
header
与footer
。这个前面已经攻克了,而且原理也相对简单 - 实现触摸与滑动监听逻辑。这个主要是对逻辑的理解,对整个刷新的过程做个整体分析,就能很好的理解上面的代码。对其中视图的动态显示做相应的变化与接口的调用就能很好的处理这些工程。
当然上面的实现可能还有瑕疵,希望指出,我会相应的做修改或者你们修改后可以提交给我,我统一做修改,谢谢!
项目地址:https://github.com/idisfkj/EnhanceRecyclerView