RecycleView 使用详解
简介
RecycleView是一个类似 ListView GridView的控件。但是相比于这两者Recycleview更加的灵活、强大;使用Recycleview你可以更轻松的在开发中实现一些特殊的布局,并使你的应用更加的酷炫
基本使用
下面我们来看一下最简单的使用步骤 (No works, show you the code.)
要使用Recycleview 需要在gradle 中依赖下面的包
implementation 'com.android.support:recyclerview-v7:23.4.0'
Activity 中代码
itemList.add(new RecycleViewItem("ddd", "This is the 5 description !"));
itemList.add(new RecycleViewItem("ddd", "This is the 6 description !"));
//步骤 (1)(必要)
RecyclerView recyclerView = findViewById(R.id.recycleView);
//步骤 (2)(必要)
//设置 layoutManager 这个是Recycleview必须的 不然Recycleview 的内容不会显示。
//RecyclView提供了 LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager 3 个系统的。当然也可以继承LayoutManager自定义
//下面是线性 layoutManager
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//如果将 orientation 设置为 HORIZONTAL 可以轻易的实现横向的 listview 效果
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//步骤 (3)(必要)
//像listview一样设置 adapter;
Adapter adapter = new Adapter(this, itemList);
recyclerView.setAdapter(adapter);
//设置分隔线 (需要时设置)
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL));
//设置增加或删除条目的动画(需要时设置)
recyclerView.setItemAnimator(new DefaultItemAnimator());
从上面的代码中我们可以看出recycleView 比 listView 等多了一步设置 layoutManager 的步骤,正是这一步骤使得RecycleView变的异常灵活。
Adapter的编码
//此处 继承的 RecyclerView.Adapter 中的泛型 指定为我们自定义的 ViewHOlder的类型
public class Adapter extends RecyclerView.Adapter<Adapter.CzyViewHolder> {
Context context;
List<RecycleViewItem> itemList;
public Adapter(Context context, List<RecycleViewItem> itemList) {
this.context = context;
this.itemList = itemList;
}
/** 创建 viewHolder 将 xml 传递给ViewHolder */
@NonNull
@Override
public CzyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycleview_adapter, viewGroup, false);
CzyViewHolder czyViewHolder = new CzyViewHolder(view);
return czyViewHolder;
}
/** 这里是设置item操作的地方 */
@Override
public void onBindViewHolder(@NonNull CzyViewHolder czyViewHolder, int i) {
czyViewHolder.textView.setText(itemList.get(i).getContent());
czyViewHolder.imageView.setImageResource(R.drawable.dreams_distance);
}
/** 跟 BaseAdapter 的 getCount 作用一样。返回 */
@Override
public int getItemCount() {
return itemList.size();
}
/** ViewHolder类,这个类用来初始化控件 */
class CzyViewHolder extends RecyclerView.ViewHolder{
TextView textView;
ImageView imageView;
public CzyViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_description);
imageView = itemView.findViewById(R.id.imageView);
}
}
}
注意: 在onCreateViewHolder中解析视图的方法必须这样调用
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycleview_adapter, viewGroup, false);
这样调用是错误的
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);
分割线 ItemDecoration
recycleView 的分割线设置 recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
这里系统提供了一个分割线类。上面的 VERTICAL表示分割线在item的底部,如果改为HORIZONTAL 则分割线在item的右侧。这个Decoration类还可以设置 setDrawable()
要实现更加酷炫的分割线效果需要继承 ItemDecoration 进行自定义。
ItemDecoration源码:
public abstract static class ItemDecoration {
public ItemDecoration() {
}
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDraw(c, parent);
}
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.onDrawOver(c, parent);
}
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
this.getItemOffsets(outRect, ((RecyclerView.LayoutParams)view.getLayoutParams()).getViewLayoutPosition(), parent);
}
}
.当我们设置了recyclerView.addItemDecoration
后,当recycleView绘制的时候,会绘制我们设置的 Decoration。
onDraw()
方法在drawChildren之前调用onDrawOver()
方法在drawChildren之后调用getItemOffsets()
方法 中可以利用outRect
为每个item设置一定的偏移量
getItemOffsets() 方法
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
//这里我们设置item(left,top,right,bottom)四周的间隔分别为(20, 20, 0, 20);
outRect.set(20, 20, 0, 20);
}
这里 outRect.set()
设置的是item四边的偏移量,我们看到的红色是我们设置的recycleView的背景色。
onDraw() 方法 和 onDrawOver()方法
- 这两个方法的参数是一样的。他们的区别在于,
onDraw()
的绘制在item的绘制之前,而onDrawOver()
的绘制在item的绘制之后,我们来看 - getItemOffsets方法针对的是每一个item,而onDraw和onDrawOver方法针对的是RecycleView本身。所以,在这两个方法中要遍历屏幕上可见的item,分别计算和绘制分割线。
这三者形成的关系如图
可以看到onDraw在item下面,item在中间,onDrawOver是可以盖住onDraw和item的。
-我们来看三者的代码-
@Override
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDraw(c, parent, state);
// 这个方法得到的 RecycleView 中 child view 的个数,也就是屏幕中显示的item的个数。并不是adapter中数据源的大小
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i ++) {
View child = parent.getChildAt(i);
int left = child.getLeft() + mDividerW / 2;
int right = child.getRight() + 60;
int top = child.getBottom() - mDividerW;
int bottom = child.getBottom() + mDividerW / 2;
c.drawRect(left, top, right, bottom, paint);
c.drawText("onDraw", (right)/2, bottom - 40, paintText);
}
}
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i ++) {
View child = parent.getChildAt(i);
int left = child.getLeft() + mDividerW / 2;
int right = child.getRight() + 60;
int top = child.getTop() - mDividerW / 2;
int bottom = child.getTop() + mDividerW / 2;
c.drawRect(left, top, right, bottom, paintOver);
c.drawText("onDrawOver", (right)/2, bottom - 40, paintText);
}
}
@Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(200, 200, 200, 200);
}
注意: 代码中注释的地方,parent.getChildCount()这个方法获取到的是显示在屏幕上的item的个数,千万不要以为是adapter中数据源的个数。这一点很重要,因为我们经常要根据数据源中的index对不同的item进行不同的处理,如果这里搞不清楚就会出现混乱。下面我们看个例子
我们想实现每隔5个数据添加一个头部
先上代码吧(这是一个错误的代码,你可以先设想一下这段代码的效果)
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
// 这个方法得到的 RecycleView 中 child view 的个数,也就是屏幕中显示的item的个数。并不是adapter中数据源的大小
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i ++) {
if (i % 5 == 0) {
View view = parent.getChildAt(i);
int left = 0;
int top = view.getTop() - 50;
int right = view.getRight();
int bottom = view.getTop();
c.drawRect(left, top, right, bottom, paintOver);
}
}
}
我们看到这段代码的效果是这个绿色的分割线总是随着滑动不同的变换。
那么我们来看正确的写法
@Override
public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
super.onDrawOver(c, parent, state);
// 这个方法得到的 RecycleView 中 child view 的个数,也就是屏幕中显示的item的个数。并不是adapter中数据源的大小
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
//获取这个 view 对应于 adapter 中的数据下标
int index = parent.getChildAdapterPosition(view);
if (index % 5 == 0) {
int left = 0;
int top = view.getTop() - 50;
int right = view.getRight();
int bottom = view.getTop();
c.drawRect(left, top, right, bottom, paintOver);
}
}
}
LayoutManager 布局管理器
RecycleView之所以能做到灵活多变的布局,是因为提供了一个可供用户自定义布局管理抽象类RecycleView.LayoutManager类。当然,系统也为我们提供了三个实现类
- LinearLayoutManager 线性管理器, 支持纵向、横向(可以很轻松的实现横向listview的效果)
- GridLayoutManager 网格布局管理器
- StaggeredGridLayoutManager 瀑布流布局管理器
前面我们已经使用过了LinearLayoutManager下面我们来看一下GridLayoutManager - GridLayoutManager
很简单,我们像LinearLayoutManager一样设置
mRecyclerView.setLayoutManager(new GridLayoutManager(this,4));
很简单吧。对于GridLayoutManager时添加分割线来说。我们可以添加两个分割线
val dividerItemDecoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
dividerItemDecoration.setDrawable(ColorDrawable(Color.parseColor("#ff0000")))
recycleView.addItemDecoration(dividerItemDecoration)
val dividerItemDecoration1 = DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL)
dividerItemDecoration1.setDrawable(ColorDrawable(Color.parseColor("#ff0000")))
recycleView.addItemDecoration(dividerItemDecoration1)
我们来看一下效果
看我们可以利用添加一个横向分割线和一个纵向分割线来实现网格效果
注意: 这里有一点不得不吐槽一下,那就是系统提供的这个分割线是在ItemDecoration的onDraw方法中实现的。那么造成的一个后果就是,当我们给Item设置了背景色之后。这个分割线是无法显示出来的。但是,作为一个程序猿很明显这是难不倒我们的。同样也难不倒你,所以自己去实现吧。
- StaggeredGridLayoutManager
上面说到的 LinearLayout和GridLayoutManager看起来并没有什么厉害的,貌似整出来的东西和listView、GridView也没有太大的区别。那么下面我们来看些真正厉害 的东西–StaggeredGridLayoutManager。
- 最简单的实现和GridLayoutManager一样的效果
recyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
这句代码实现的效果和GridLayoutManager是一样的。和GridLayoutManager一样StaggeredGridLayoutManager也是可以设置VERTICAL
或者HORIZONTAL
;相应的设置的列数也就变成了行数。 - 这,好像也很平常的东西呀。下面我们来看一下厉害的东西。
我们想一下以前我们要实现一个瀑布流,就是那个像淘宝首页一样的错落有致的很好看的瀑布流呀。是不是很麻烦?现在用RecycleView就很easy了。
我们来看一下效果
要实现的代码很简单,只要在adapter中的onBindViewHolder方法中给Item设置一个随机的高度就可以了。下面是代码
//此处 继承的 RecyclerView.Adapter 中的泛型 指定为我们自定义的 ViewHOlder的类型
public class Adapter extends RecyclerView.Adapter<Adapter.CzyViewHolder> {
Context context;
List<RecycleViewItem> itemList;
//存储Item的高度
List<Integer> heights = new ArrayList<>();
public Adapter(Context context, List<RecycleViewItem> itemList) {
this.context = context;
this.itemList = itemList;
}
public void update(List<RecycleViewItem> items) {
itemList = items;
notifyDataSetChanged();
}
/**
* 创建 viewHolder 将 xml 传递给ViewHolder
* @param viewGroup
* @param i
* @return
*/
@NonNull
@Override
public CzyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
//此处 映射 Layout 的方法如下
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycleview_adapter, viewGroup, false);
//映射方法,View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false); 是错误的。
CzyViewHolder czyViewHolder = new CzyViewHolder(view);
return czyViewHolder;
}
/**
* 这里是设置item操作的地方
* @param czyViewHolder
* @param position
*/
@Override
public void onBindViewHolder(@NonNull CzyViewHolder czyViewHolder, int position) {
//高度默认值
int height = 300;
//当heights中有高度时我们取heights中的高度。
if (heights != null && heights.size() > position) {
height = heights.get(position);
}else {
//heights中没有时我们生成一个随机的高度并存储在heights中
height = new Random().nextInt(200) + 100;//[100,300)的随机数
}
heights.add(height);
//设置Item的高度
ViewGroup.LayoutParams params = czyViewHolder.textView.getLayoutParams();
params.height = heights.get(position);
czyViewHolder.textView.setLayoutParams(params);
czyViewHolder.textView.setText(itemList.get(position).getImgUrl());
}
/**
* 跟 BaseAdapter 的 getCount 作用一样。返回
* @return
*/
@Override
public int getItemCount() {
return itemList.size();
}
/**
* ViewHolder类,这个类用来初始化控件
*/
class CzyViewHolder extends RecyclerView.ViewHolder{
TextView textView;
public CzyViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_description);
}
}
}
以上三种是系统提供的LayoutManager,当然我们也可以继承LayoutManger自定义我们自己的LayoutManager。这个和布局的自定义差不多。细致的问题篇幅限制就不讲了。可以网上找几篇文章参考下,很简单
ItemAnimator(动画)
为RecycleView设置动画,只要调用recyclerView.setItemAnimator(new DefaultItemAnimator());
方法即可
这也是一个抽象类,我们可以继承这个类自定义动画。
这里要注意呈现ItemAnimator需要调用RecycleView.Adapter中notifyItemRemoved(position)/notifyItemInserted();/notifyItemChanged
等方法,调用notifyDataSetChanged();方法动画是无效的。
系统为我们提供了一个默认动画DefaultItemAnimator
我们来看一下DefaultItemAnimator的效果
看起来还是不错的吧;
我们再来看一下代码
public class Adapter extends RecyclerView.Adapter<Adapter.CzyViewHolder> {
Context context;
List<RecycleViewItem> itemList;
//存储Item的高度
List<Integer> heights = new ArrayList<>();
public Adapter(Context context, List<RecycleViewItem> itemList) {
this.context = context;
this.itemList = itemList;
}
public void update(List<RecycleViewItem> items) {
itemList = items;
notifyDataSetChanged();
}
/**
* 创建 viewHolder 将 xml 传递给ViewHolder
* @param viewGroup
* @param i
* @return
*/
@NonNull
@Override
public CzyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
//此处 映射 Layout 的方法如下
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recycleview_adapter, viewGroup, false);
//映射方法,View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false); 是错误的。
CzyViewHolder czyViewHolder = new CzyViewHolder(view);
return czyViewHolder;
}
/**
* 这里是设置item操作的地方
* @param czyViewHolder
* @param position
*/
@Override
public void onBindViewHolder(@NonNull final CzyViewHolder czyViewHolder, int position) {
//高度默认值
int height = 300;
//当heights中有高度时我们取heights中的高度。
if (heights != null && heights.size() > position) {
height = heights.get(position);
}else {
//heights中没有时我们生成一个随机的高度并存储在heights中
height = new Random().nextInt(200) + 100;//[100,300)的随机数
}
heights.add(height);
//设置Item的高度
ViewGroup.LayoutParams params = czyViewHolder.textView.getLayoutParams();
params.height = heights.get(position);
czyViewHolder.textView.setLayoutParams(params);
czyViewHolder.textView.setText(itemList.get(position).getImgUrl());
czyViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
//删除数据源中的数据
itemList.remove(czyViewHolder.getAdapterPosition());
//删除这一项的高度
heights.remove(czyViewHolder.getAdapterPosition());
//adapter中移除这一项
notifyItemRemoved(czyViewHolder.getAdapterPosition());
return false;
}
});
}
/**
* 跟 BaseAdapter 的 getCount 作用一样。返回
* @return
*/
@Override
public int getItemCount() {
return itemList.size();
}
/**
* ViewHolder类,这个类用来初始化控件
*/
class CzyViewHolder extends RecyclerView.ViewHolder{
TextView textView;
public CzyViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.tv_description);
}
}
}
动画相关的代码在 onBindViewHolder
方法中,
//删除数据源中的数据
itemList.remove(czyViewHolder.getAdapterPosition());
//删除这一项的高度
heights.remove(czyViewHolder.getAdapterPosition());
//adapter中移除这一项
notifyItemRemoved(czyViewHolder.getAdapterPosition());
注意: 这三句代码中很多人会在这里使用onBindViewHolder
的第二个参数position作为remove、notifyItemRemoved方法的参数,但是这样会引起position混乱的问题。这是因为如果在这里使用onBindViewHolder的传入参数,那么需要将这个参数变成final类型的参数,因此造成混乱。所以,这里要用viewHolder.getAdapterPosition()获取我们需要的position
到这里RecycleView 的基本使用就讲完了。从以上的讲解我们看到RecycleView相交于listview等更灵活。使用RecycleView可以更轻松的实现较为复杂酷炫的布局。
相信你很快就会对RecycleView欲罢不能。