安卓RecyclerView那些事-(二)实现一个下拉刷新、划到底部加载更多的RecyclerView

这次,我们在RecyclerView的基础之上来实现一个下拉刷新,滑到底部加载更多的RecyclerView。如果你还不熟悉RecyclerView的基本使用方法,请看我的上一篇博客安卓RecyclerView那些事 - (一)了解RecycleView。
需求 我们要实现的功能:(1)下拉刷新RecyclerView的数据,更新数据,(2)划到底部加载更多数据,更新数据(3)加载更多数据时显示“正在加载”的footerView
需求分析 1.下拉刷新数据 这个功能比较容易实现,通常的做法在RecyclerView外部嵌套一个SwipeRefreshLayout,然后给SwipeRefreshLayout添加刷新监听函数,在刷新监听函数中我们重新获取数据,并更新RecyclerView的数据。还不清楚SwipeRefreshLayout怎么用的同学可以看这个博客android之官方下拉刷新组件SwipeRefreshLayout;
2.划到底部加载更多数据
要实现这个功能我们首先要判断什么样的情况是划到了RecyclerView的底部。
通过LinearLayoutManager判断 通过LinearLayoutManager获得一下几个参数:item的总个数,当前可见的最后一个item在所有item中的位置,当前可见的最后一个item的bottom,RecyclerView的bottom。
我们为RecyclerView添加滑动监听函数RecyclerView.addOnScrollListener(mOnScrollListener),在滑动监听函数中我们判断当前可见的最后一个item是不是RecyclerView中所有item的最后一个,并且当前可见的最后一个item的bottom是不是等于RecyclerView的bottom。
代码如下:

public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); addOnScrollListener(mOnScrollListener); //添加底部加载接口 }/** * 滑动监听 * 滑动到最后一个item的底部时加载更多信息 */ private OnScrollListener mOnScrollListener = new OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); }@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) { LinearLayoutManager linearLayout = (LinearLayoutManager) layoutManager; int mLastChildPosition = linearLayout.findLastVisibleItemPosition(); //当前页面最后一个可见的item的位置 int itemTotalCount = linearLayout.getItemCount(); //获取总的item的数量 View lastChildView = linearLayout.getChildAt(linearLayout.getChildCount() - 1); //最后一个子view Log.e(TAG,"mLastChildPosition:"+mLastChildPosition+",itemTotalCount:"+itemTotalCount); int lastChildBottom = lastChildView.getBottom(); //最后一个子view的bottom int recyclerBottom = getBottom(); if (mLastChildPosition == itemTotalCount - 1 && lastChildBottom == recyclerBottom) {//当前页面的最后一个item是item全部的最后一个并且当前页面的最后一个item的底部是recycleView的底部的时候,获取新数据 if (listener != null) { //业务代码 listener.loadMore(); } } } } };

通过RecyclerView的canScrollVertically()函数判断 canScrollVertically()是在View中定义的,用来判断是否能进行滑动。我们看一下这个方法怎么用

安卓RecyclerView那些事-(二)实现一个下拉刷新、划到底部加载更多的RecyclerView
文章图片
boolean canScrollVertically(int) 这个方法需要传入一个int型变量,当这个变量为正时函数返回否能向上滑动,为负时返回能否向下滑动。
同样,我们要利用canScrollVertically实现判断是否滑倒底部,需要在RecyclerView的滑动监听函数中判断canScrollVertically(1)的值。代码如下
public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); addOnScrollListener(mOnScrollListener); //添加底部加载接口 }/** * 滑动监听 * 滑动到最后一个item的底部时加载更多信息 */ private OnScrollListener mOnScrollListener = new OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); }@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if(!canScrollVertically(1)){ if (listener != null) { listener.loadMore(); } } } };

3.RecyclerView自定义footerView 我们知道Adapter通过先调用onCreateViewHolder(ViewGroup parent, int viewType)创建视图(当创建足够的ViewHolder后便不再创建,我的上一篇博客 安卓RecyclerView那些事 - (一)了解RecycleView),然后调用onBindViewHolder(MyViewHolder holder, int position)为视图绑定数据。
大家有没有注意onCreateViewHolder(ViewGroup parent, int viewType)的参数中有一个viewType,那这个参数是哪里来的呢?其实RecyclerView还有一个方法getItemViewType(int position)
其实,Adapter创建每一个item时,先调用getItemViewType(int position)获得item的类型,然后调用onCreateViewHolder(ViewGroup parent, int viewType)创建视图,最后调用onBindViewHolder(MyViewHolder holder, int position)为视图绑定数据。顺序如下图所示。

安卓RecyclerView那些事-(二)实现一个下拉刷新、划到底部加载更多的RecyclerView
文章图片
image.png
我们可以在getItemViewType(int position)中根据position返回不同的ViewType,在onCreateViewHolder(ViewGroup parent, int viewType)中根据viewType创建不同的ViewHolder,最后在onBindViewHolder(MyViewHolder holder, int position)中绑定数据。这样,就可以在RecyclerView中显示不同类型的item,甚至可以在RecyclerView中嵌套RecyclerView等各种View。
代码实现 1.自定义一个ViewHolder
public class MyViewHolder extends RecyclerView.ViewHolder{ private SparseArray mHolderView; //缓存子控件View private View ParentView; //最外层viewpublic MyViewHolder(View itemView) { super(itemView); this.ParentView=itemView; if(mHolderView==null){ mHolderView=new SparseArray<>(); } }/** * 根据layoutID创建ViewHolder * @param parent * @param layoutId * @return */ public static MyViewHolder createViewHolder(ViewGroup parent,int layoutId){ View view= LayoutInflater.from(parent.getContext()).inflate(layoutId,parent,false); return new MyViewHolder(view); }/** * 根据View创建viewHolder * @param view * @return */ public static MyViewHolder createViewHolder(View view){ return new MyViewHolder(view); }/** * 获取View * @param id * @param * @return */ public T getView(int id){ View view=mHolderView.get(id); if(view==null){ view=ParentView.findViewById(id); mHolderView.put(id,view); } return (T) view; } }

2.定义一个Model,用于模拟数据加载
public class Model { public static List getData(){ List list=new ArrayList<>(); for(int i=0; i<20; i++){ list.add(i); } return list; } }

3.自定义Adapter 【安卓RecyclerView那些事-(二)实现一个下拉刷新、划到底部加载更多的RecyclerView】重写Adapter中的getItemViewType,onCreateViewHolder,onBindViewHolder,getItemCount。
public class MyAdapter extends RecyclerView.Adapter { private List list; //数据list private static final String TAG="MyAdapter"; public MyAdapter(List list) { this.list = list; }/** * 返回view类型 * * @param position * @return */ @Override public int getItemViewType(int position) { Log.e(TAG,"position:"+position); Log.e(TAG,"getItemViewType"); if (position == getItemCount() - 1)//如果是最后一个item,则是底部布局 return VIEW_TYPE_FOOTER; return VIEW_TYPE_NOMAL; //正常item }@Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Log.e(TAG,"onCreateViewHolder"); if (viewType == VIEW_TYPE_FOOTER) return MyViewHolder.createViewHolder(parent, R.layout.item_root_footer); //返回底部布局 return MyViewHolder.createViewHolder(parent, R.layout.item_normal); //返回正常item }@Override public void onBindViewHolder(MyViewHolder holder, int position) { Log.e(TAG,"onBindViewHolder"); int viewType = getItemViewType(position); if (viewType == VIEW_TYPE_NOMAL) {//正常item需要绑定数据 TextView textView=holder.getView(R.id.item_TV); textView.setText(list.get(position).toString()); } }/** * 返回item的数量 * 因为在原有数据数量基础上加了一个底部布局,所以总的item数量应该+1 * @return */ @Override public int getItemCount() { int count = list.size(); count++; //因为多了一个footerView,所有item的总数应该+1 return count; }/** * 刷新数据 * @param mList */ public void notifyAllDatas(List mList,MyRecyclerView recyclerView) { this.list = mList; recyclerView.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } }); } }

4.自定义一个RecyclerView
public class MyRecyclerView extends RecyclerView{ private static final String TAG="MyRecyclerView"; private OnFooterAutoLoadMoreListener listener; //监听底部 public static final int VIEW_TYPE_NOMAL = 0; //item的类型-正常的item public static final int VIEW_TYPE_FOOTER = 200; //item的类型-底部public MyRecyclerView(Context context) { this(context,null); }public MyRecyclerView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); }public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); addOnScrollListener(mOnScrollListener); //添加底部加载接口 }/** * 滑动监听 * 滑动到最后一个item的底部时加载更多信息 */ private OnScrollListener mOnScrollListener = new OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); }@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if(!canScrollVertically(1)){ if (listener != null) { listener.loadMore(); } } } }; /** * 添加底部加载接口 * @param listener */ public void addFooterAutoLoadMoreListener(OnFooterAutoLoadMoreListener listener){ this.listener=listener; } }

5.使用MyRecyclerView 布局如下

代码如下
package com.tinymonster.myrecyclerview; import android.content.Intent; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity implements OnFooterAutoLoadMoreListener{ private SwipeRefreshLayout swipeRefreshLayout; private MyRecyclerView myRecyclerView; private List dataList=new ArrayList<>(); private MyAdapter myAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.swipeRefreshLayout); myRecyclerView=(MyRecyclerView)findViewById(R.id.myRecyclerView); myRecyclerView.setLayoutManager(new LinearLayoutManager(this)); myRecyclerView.addFooterAutoLoadMoreListener(this); myAdapter=new MyAdapter(dataList); myRecyclerView.setAdapter(myAdapter); loadMore(); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { myAdapter.notifyAllDatas(dataList,myRecyclerView); dataList.clear(); dataList.addAll(Model.getData()); swipeRefreshLayout.setRefreshing(false); } }); }@Override public void loadMore() { myAdapter.notifyAllDatas(dataList,myRecyclerView); List list=Model.getData(); dataList.addAll(list); swipeRefreshLayout.setRefreshing(false); } }

代码已经上传到GitHub,如果你觉得有帮助,请帮忙点一个星星。
https://github.com/Tiny-Monster/MyRecyclerView
安卓RecyclerView那些事-(二)实现一个下拉刷新、划到底部加载更多的RecyclerView
文章图片
image.png

    推荐阅读