概述
RecyclerView
是一个用于在有限的窗口中展示大量数据集的控件。那么,类似的控件当然就有常用的ListView
和GridView
,那么有了ListView
和GridView
为什么还需要RecyclerView
这样的控件呢?下面就开始一步步了解RecyclerView
的不同之处。
依赖
RecyclerView
是 Support Library 的一部分,所以使用前需要在 app/build.gradle 中添加依赖
dependencies {
compile 'com.android.support:recyclerview-v7:25.0.1'
}
结构
要想使用RecyclerView
,需要做以下操作:
- RecyclerView.Adapter – 处理数据集合并负责绑定视图
- ViewHolder – 持有所有的用于绑定数据或者需要操作的View
- LayoutManager – 负责摆放视图等相关操作
- ItemDecoration – 负责绘制Item附近的分割线
- ItemAnimator – 为Item的一般操作添加动画效果,如,增删条目等
想要在ListView
中实现条目的增删动画是一件非常困难的事情,但是RecyclerView
为我们提供了很好的便利。而且RecyclerView
增强了ViewHolder
设计模式,这在当前所使用的ListView
中是不曾有的。
与传统ListView
比较
RecyclerView
与老前辈ListView
的不同点,主要在于以下几个特性:
Adapter
中的ViewHolder
模式 – 对于ListView
来说,通过创建ViewHolder
来提升性能并不是必须的,因为ListView
并没有严格的ViewHolder
设计模式。但是在使用RecyclerView
的时候,Adapter
必须实现至少一个ViewHolder
,必须遵循ViewHolder
设计模式。- 定制Item条目 –
ListView
只能实现垂直线性排列的列表视图,与之不同的是,RecyclerView
可以通过设置RecyclerView.LayoutManager
来定制不同风格的视图,比如水平滚动列表或者不规则的瀑布流列表。 - Item动画 – 在
ListView
中没有提供任何方法或者接口,方便开发者实现Item的增删动画。相反地,可以通过设置RecyclerView
的RecyclerView.ItemAnimator
来为条目增加动画效果。 - 设置数据源 – 在
LisView
中针对不同数据封装了各种类型的Adapter
,比如用来处理数组的ArrayAdapter
和用来展示Database
结果的CursorAdapter
。相反地,在RecyclerView
中必须自定义实现RecyclerView.Adapter
并为其提供数据集合。 - 设置条目分割线 – 在
ListView
中可以通过设置android:divider
属性来为两个Item间设置分割线。如果想为RecyclerView
添加此效果,则必须使用RecyclerView.ItemDecoration
,这种实现方式不仅更灵活,而且样式也更加丰富。 - 设置点击事件 – 在
ListView
中存在AdapterView.OnItemClickListener
接口,用来绑定条目的点击事件。但是,很遗憾的是在RecyclerView
中,并没有提供这样的接口,不过,提供了另外一个接口RcyclerView.OnItemTouchListener
,用来响应条目的触摸事件。
实战
首先我实现了RecyclerView.Adapter
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
public class ListAdapter extends RecyclerView.Adapter{
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
但接下来,我不知道该怎么做了,可能这是习惯了使用ListView
吧。而后查阅资料,然后实现了RecyclerView.ViewHolder
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class ListViewHolder extends RecyclerView.ViewHolder{
public ListViewHolder(View itemView) {
super(itemView);
}
}
而这个时候,首先想到的是,这和RecyclerView.Adapter
的实现类有什么关联呢?首先看了下源码
public static abstract class Adapter<VH extends ViewHolder> {
发现,RecyclerView.Adapter
传递了一个继承RecyclerView.ViewHolder
的泛型,那么ListAdapter
就应该改成
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
public class ListAdapter extends RecyclerView.Adapter<ListViewHolder> {
@Override
public ListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(ListViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
那么将数据集和布局样式都加入进去,就应该是
import android.content.Context;
import android.databinding.DataBindingUtil;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import org.eking.hainancsp.R;
import org.eking.hainancsp.databinding.ItemTaskBinding;
import org.eking.hainancsp.model.TaskListModel;
import java.util.List;
import java.util.Map;
import static org.eking.hainancsp.adapter.TaskListAdapter.TASK_LIST_KEY_DATE;
import static org.eking.hainancsp.adapter.TaskListAdapter.TASK_LIST_KEY_STATUS;
import static org.eking.hainancsp.adapter.TaskListAdapter.TASK_LIST_KEY_TITLE;
public class ListAdapter extends RecyclerView.Adapter<ListViewHolder> {
private Context context;
private List<Map> arrData;
public ListAdapter(Context context) {
this.context = context;
}
public ListAdapter(Context context, List<Map> arrData) {
this.context = context;
this.arrData = arrData;
}
public void setData(List<Map> data) {
this.arrData = data;
}
@Override
public ListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ItemTaskBinding binding = DataBindingUtil.
inflate(LayoutInflater.from(context), R.layout.item_task, parent, false);
return new ListViewHolder(binding);
}
@Override
public void onBindViewHolder(ListViewHolder holder, int position) {
Map map = arrData.get(position);
String date = map.get(TASK_LIST_KEY_DATE).toString();
String status = map.get(TASK_LIST_KEY_STATUS).toString();
String title = map.get(TASK_LIST_KEY_TITLE).toString();
TaskListModel model = new TaskListModel(context, date, status, title);
holder.getBinding().setModel(model);
}
@Override
public int getItemCount() {
return arrData == null ? 0 : arrData.size();
}
}
然后就是ListViewHolder
import android.support.v7.widget.RecyclerView;
import android.view.View;
import org.eking.hainancsp.databinding.ItemTaskBinding;
class ListViewHolder extends RecyclerView.ViewHolder {
private ItemTaskBinding binding;
ListViewHolder(View itemView){
super(itemView);
}
ListViewHolder(ItemTaskBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
ItemTaskBinding getBinding() {
return binding;
}
}
哦,对了,最后就是给RecyclerView设置ListAdapter
@BindingAdapter(value = {"adapter", "data"}, requireAll = false)
public static void setTaskListAdapter(RecyclerView recyclerView, ListAdapter adapter, List<Map> data) {
if (data == null || adapter == null || data.size() == 0) return;
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
adapter.setData(data);
recyclerView.setAdapter(adapter);
}
接下来运行效果看下
这么看来,实现还是蛮简单的!接下来就该添加分割线了,需要实现RecyclerView.ItemDecoration
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.view.View;
public class ListItemDecoration extends RecyclerView.ItemDecoration {
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
}
额,傻眼了,然后该怎么做?然后继续查阅了下资料,记录下重写的三个方法
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
装饰的绘制在Item条目绘制之前调用,所以这有可能被Item的内容所遮挡public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
装饰的绘制在Item条目绘制之后调用,因此装饰将浮于Item之上public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent, RecyclerView.State state)
与padding或margin类似,LayoutManager在测量阶段会调用该方法,计算出每一个Item的正确尺寸并设置偏移量(详细参考这篇文章https://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/)
了解了这些,就可以简单地实现分割线了
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
public class ListItemDecoration extends RecyclerView.ItemDecoration {
private Drawable divider;
private int dividerHeight;
public ListItemDecoration(Context context) {
TypedArray ta = context.obtainStyledAttributes(new int[]{android.R.attr.listDivider});
divider = ta.getDrawable(0);
ta.recycle();
dividerHeight = 2;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
boolean signed = false;
int left = parent.getPaddingLeft();
int right = parent.getWidth() - parent.getPaddingRight();
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
if (!signed && child instanceof ViewGroup) {
ViewGroup item = (ViewGroup) child;
for (int j = 0; j < item.getChildCount(); j++) {
if (item.getChildAt(j) instanceof ImageView) {
ImageView imageView = (ImageView) item.getChildAt(j);
left += imageView.getRight();
signed = true;
}
}
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + params.topMargin;
int bottom = top + dividerHeight;
divider.setBounds(left, top, right, bottom);
divider.draw(c);
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
}
运行下效果,如下
至此,用RecyclerView
实现ListView
的效果成功了。