Android RecyclerView的使用及下拉刷新和上拉加载更多
一. 简介
RecyclerView 是android5.0提出的代替ListView的新控件,还可以实现GridView的效果,自带分割线,也可以自定义分割线,增加List显示的美观性,而新增的LayoutManager可用来确定item的排列方式,可以通过LayoutManager来设置list要展示的是垂直还是水平,还添加了默认的增加和删除item动画。
二. 配置
在model下添加:
compile 'com.android.support:recyclerview-v7:21.0.0'
三. RecyclerView的使用和下拉刷新,上拉加载更多
(1)简单布局
activity_layout.xml布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipe" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout> </LinearLayout>
注:在布局中SwipeRefreshLayout是下拉加载的控件,需要包裹RecyclerView.
recycler_layout.xml list的布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="45dp" android:orientation="vertical"> <TextView android:id="@+id/tv_recycler" android:gravity="center" android:background="#0f0" android:textColor="#f00" android:textSize="20dp" android:layout_margin="3dp" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
footview_layout.xml上拉加载更多的foot布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/foot" android:layout_width="match_parent" android:layout_height="45dp" android:gravity="center" android:textSize="15sp" android:background="#fff" android:layout_gravity="center" android:layout_marginBottom="1dp"/> </LinearLayout>
注:列表布局我只显示了一个TextView,简单明了。需要注意的是父控件的高度是wrap_content,这样当TextView隐藏的时候,LinnearLayout也就跟着隐藏了,从而达到foot的整体隐藏。
(2)RecyclerView适配器
都知道写ListView展示数据都需要适配器,RecyclerView也不例外
RecyclerViewAdapter.java :
import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import java.util.List; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyHolder> { List<String> list; //数据集合 Context context; public RecyclerViewAdapter(List<String> list, Context context){ this.list = list; this.context = context; } //初始化item布局 @Override public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.recycler_layout, parent, false); MyHolder holder = new MyHolder(view); return holder; } //想要怎么处理item,等同于BaseAdapter的getView @Override public void onBindViewHolder(MyHolder holder, int position) { holder.textView.setText(list.get(position)); } //展示的数量 @Override public int getItemCount() { return list.size(); } //自动的HolderView优化,初始化item控件 public class MyHolder extends RecyclerView.ViewHolder { TextView textView; public MyHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.tv_recycler); } } }
注:RecyclerView的适配器跟ListView略不同,但可以看出解耦性很高。
(3)MainActivity.java 下拉刷新,上拉加载更多整体实现代码
import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private RecyclerView recyclerView; private SwipeRefreshLayout swipe; private List<String> list; private int lastVisibleItem = 0; private final int PAGE_COUNT = 20; private GridLayoutManager mLayoutManager; private ItemAdapter adapter; private Handler mHandler = new Handler(Looper.getMainLooper()); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); initView(); } //添加演示数据 private void initData() { list = new ArrayList<>(); for (int i = 1; i <= 50; i++) list.add("item " + i); //下拉刷新 swipe = (SwipeRefreshLayout) findViewById(R.id.swipe); swipe.setColorSchemeColors(Color.RED); //设置下拉刷新的动画颜色 swipe.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { //下拉刷新的处理事件 @Override public void onRefresh() { swipe.setRefreshing(true); Log.i("textShow", "now : " + adapter.getRealLastPosition()); updateRecyclerView2(0, adapter.getRealLastPosition());//刷新加载50条数据 mHandler.postDelayed(new Runnable() { @Override public void run() { swipe.setRefreshing(false); //关闭下拉刷新,此处做下拉刷新的结果处理 } }, 1000); } }); } private void initView() { recyclerView = (RecyclerView) findViewById(R.id.recyclerView); adapter = new ItemAdapter(getDatas(0, PAGE_COUNT), this, getDatas(0, PAGE_COUNT).size() > 0 ? true : false); mLayoutManager = new GridLayoutManager(this, 1); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setAdapter(adapter); // recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); // 实现上拉加载重要步骤,设置滑动监听器,RecyclerView自带的ScrollListener recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); // 在newState为滑到底部时 if (newState == RecyclerView.SCROLL_STATE_IDLE) { // 如果没有隐藏footView,那么最后一个条目的位置就比我们的getItemCount少1,自己可以算一下 if (adapter.isFadeTips() == false && lastVisibleItem + 1 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Override public void run() { // 然后调用updateRecyclerview方法更新RecyclerView if (adapter.getRealLastPosition() == list.size()) updateRecyclerView(0, 0); else updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); Log.i("textShow", "sum1 = " + adapter.getRealLastPosition()); } }, 500); } // 如果隐藏了提示条,我们又上拉加载时,那么最后一个条目就要比getItemCount要少2 if (adapter.isFadeTips() == true && lastVisibleItem + 2 == adapter.getItemCount()) { mHandler.postDelayed(new Runnable() { @Override public void run() { Log.i("textShow", "sum2 = " + adapter.getRealLastPosition()); updateRecyclerView(adapter.getRealLastPosition(), adapter.getRealLastPosition() + PAGE_COUNT); } }, 500); } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // 在滑动完成后,拿到最后一个可见的item的位置 lastVisibleItem = mLayoutManager.findLastVisibleItemPosition(); // recyclerView.scrollToPosition(49); } }); } //还有数据需要加载 private List<String> getDatas(final int firstIndex, final int lastIndex) { List<String> resList = new ArrayList<>(); for (int i = firstIndex; i < lastIndex; i++) { if (i < list.size()) { resList.add(list.get(i)); } } return resList; } // 上拉加载时调用的更新RecyclerView的方法 private void updateRecyclerView(int fromIndex, int toIndex) { // 获取从fromIndex到toIndex的数据 List<String> newDatas = getDatas(fromIndex, toIndex); if (newDatas.size() > 0) { // 然后传给Adapter,并设置hasMore为true adapter.updateList(newDatas, true); } else { adapter.updateList(null, false); } } // 下拉刷新时调用的更新RecyclerView的方法 private void updateRecyclerView2(int fromIndex, int toIndex) { // 获取从fromIndex到toIndex的数据 List<String> newDatas = getDatas(fromIndex, toIndex); if (newDatas.size() > 0) { // 然后传给Adapter,并设置hasMore为true adapter.updateList2(newDatas, false); } else { adapter.updateList2(null, false); } } }
四. 添加分割线
(1)新建MyItemDecoration.java类,用于分割线的处理
package com.jmg.recyclerviewtext; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; public class MyItemDecoration extends RecyclerView.ItemDecoration { /* * 定义4个常量值,代表布局方向,分别是竖向线性布局、横向线性布局、竖向网格布局、横向网格布局 */ public static final int LINEAR_LAYOUT_ORIENTATION_VERTICAL = 0; public static final int LINEAR_LAYOUT_ORIENTATION_HORIZONTAL = 1; public static final int GRID_LAYOUT_ORIENTATION_VERTICAL = 2; public static final int GRID_LAYOUT_ORIENTATION_HORIZONTAL = 3; private int orientation = -1; // 当前的布局方向 // 如果是网格布局我们要计算出每一行或者每一列(取决于布局方向)中的子项数目 private int rawOrColumnSum = 0; // Drawable 对象用于绘制分隔线 private Drawable myDivider = null; public MyItemDecoration(Context context, int orientation) { /* 这个构造方法用于处理线性布局传入的情况,我们要对myDivider对象进行初始化 * (绘制的颜色和宽度等等) * R.drawable.my_list_divider 是我们自定义的一个drawable资源文件,我们通过 * myContext来获取它 */ myDivider = context.getResources().getDrawable(R.drawable.my_list_divider); if(orientation == LinearLayoutManager.HORIZONTAL) { this.orientation = LINEAR_LAYOUT_ORIENTATION_HORIZONTAL; }else if(orientation == LinearLayoutManager.VERTICAL) { this.orientation = LINEAR_LAYOUT_ORIENTATION_VERTICAL; } } public MyItemDecoration(Context context, int orientation, int rawOrColumnSum) { // 这个构造方法用于处理网格布局传入的情况,原理同上 myDivider = context.getResources().getDrawable(R.drawable.my_list_divider); if(orientation == GridLayoutManager.HORIZONTAL) { this.orientation = GRID_LAYOUT_ORIENTATION_HORIZONTAL; } else if(orientation == GridLayoutManager.VERTICAL) { this.orientation = GRID_LAYOUT_ORIENTATION_VERTICAL; } this.rawOrColumnSum = rawOrColumnSum; } // 在这个方法中。我们对布局方向进行判断,由此来调用正确的分隔线绘制方法 @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); if(orientation == LINEAR_LAYOUT_ORIENTATION_HORIZONTAL || orientation == LINEAR_LAYOUT_ORIENTATION_VERTICAL) { linearLayoutDrawItemDecoration(c, parent); } else if(orientation == GRID_LAYOUT_ORIENTATION_HORIZONTAL || orientation == GRID_LAYOUT_ORIENTATION_VERTICAL) { gridLayoutItemDecoration(c, parent); } } /* * 当排布方式为线性布局的时候,绘制分割线的方法: */ private void linearLayoutDrawItemDecoration(Canvas canvas, RecyclerView parent) { int childCount = parent.getChildCount(); // 获取RecyclerView控件中的子控件总数 int left, top, right, bottom; View child = parent.getChildAt(0); // 获取分割线的高度(把分割线看成一个小矩形) int drawableHeight = myDivider.getIntrinsicHeight(); // 如果是竖直排布,那么分割线为横线 if(orientation == LINEAR_LAYOUT_ORIENTATION_VERTICAL) { left = parent.getLeft(); right = parent.getRight(); // 获取子控件开始 x 坐标和结束 x 坐标 for (int i = 1; i < childCount; i++) { top = child.getBottom() - drawableHeight/2; // 获取开始点y坐标 bottom = child.getBottom() + drawableHeight/2; // 获取结束点y坐标 myDivider.setBounds(left, top, right, bottom); // 设置绘制区域,下同 myDivider.draw(canvas); // 在Canvas对象上绘制区域 child = parent.getChildAt(i); } // 如果是水平排布,那么分割线为竖线 } else if(orientation == LINEAR_LAYOUT_ORIENTATION_HORIZONTAL) { top = child.getTop(); bottom = child.getBottom(); // 获取子控件的开始 y 坐标和结束 y 坐标 for(int i = 1; i < childCount; i++) { left = child.getRight() - drawableHeight/2; // 获取开始点 x 坐标 right = child.getRight() + drawableHeight/2; // 获取结束点 x 坐标 myDivider.setBounds(left, top, right, bottom); // 设置绘制区域 myDivider.draw(canvas); child = parent.getChildAt(i); } } } /* * 当排布方式为网格布局的时候,分割线的绘制方法: */ private void gridLayoutItemDecoration(Canvas canvas, RecyclerView parent) { // 顺着布局方向上的要绘制的分割线条数 int childCount = parent.getChildCount(); int lineSum = childCount / rawOrColumnSum - 1; lineSum += childCount % rawOrColumnSum == 0 ? 0 : 1; // 获取分割线的高度(把分割线看成一个小矩形) int drawableHeight = myDivider.getIntrinsicHeight(); int left, right, top, bottom; View child = parent.getChildAt(0); // 布局方向为竖直排布方式 if(orientation == GRID_LAYOUT_ORIENTATION_VERTICAL) { left = parent.getLeft(); right = parent.getRight(); for(int i = 0; i < lineSum; i++) { // 循环用于绘制横向分割线 child = parent.getChildAt(i*rawOrColumnSum); top = child.getBottom() - drawableHeight/2; bottom = child.getBottom() + drawableHeight/2; myDivider.setBounds(left, top, right, bottom); myDivider.draw(canvas); } top = parent.getTop(); bottom = parent.getBottom(); for(int i = 0; i < rawOrColumnSum-1; i++) { // 循环用于绘制竖向分割线 child = parent.getChildAt(i); left = child.getRight() - drawableHeight/2; right = child.getRight() + drawableHeight/2; myDivider.setBounds(left, top, right, bottom); myDivider.draw(canvas); } // 布局方向为横向排布方式 } else if(orientation == GRID_LAYOUT_ORIENTATION_HORIZONTAL) { top = parent.getTop(); bottom = parent.getBottom(); for(int i = 0; i <= lineSum; i++) { // 循环绘制竖向分割线 child = parent.getChildAt(i*rawOrColumnSum); left = child.getRight() - drawableHeight/2; right = child.getRight() + drawableHeight/2; myDivider.setBounds(left, top, right, bottom); myDivider.draw(canvas); } left = parent.getLeft(); right = parent.getRight(); for(int i = 0; i < rawOrColumnSum; i++) { // 循环绘制横向分割线 child = parent.getChildAt(i); top = child.getBottom() - drawableHeight/2; bottom = child.getBottom() + drawableHeight/2; myDivider.setBounds(left, top, right, bottom); myDivider.draw(canvas); } } } }(2)添加分割线颜色,在drawable下新建my_list_divider.xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <size android:height="4dp"></size> <gradient android:startColor="#ff0000" android:centerColor="#00ff00" android:endColor="#0000ff"> </gradient> </shape>
(3)在MainActivity.java中引用
我这里引用的是两个参数的构造方法,第一个参数:context,第二个参数:分割线显示的方向
五. 效果图
下拉刷新及默认分割线:
上拉加载更多及自定义分割线:
六. 拓展
在MyItemDecoration的构造方法中,获取图片资源的getDrawable()方法已经过时,这样虽然不会报错,但是并不好
myDivider = context.getResources().getDrawable(R.drawable.my_list_divider);
谷歌给了新的方法来替换,过时方法被替换肯定是有原因的,我们就是要与时俱进
myDivider = ContextCompat.getDrawable(context,R.drawable.my_list_divider);
每天进步一点点!