欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

RecyclerView使用详解

程序员文章站 2024-02-23 20:40:10
recylerview介绍 recylerview是support-v7包中的新组件,是一个强大的滑动组件,与经典的listview相比,同样拥有item回收复用的功能,...

recylerview介绍

recylerview是support-v7包中的新组件,是一个强大的滑动组件,与经典的listview相比,同样拥有item回收复用的功能,这一点从它的名字recylerview即回收view也可以看出。官方对于它的介绍则是:recyclerview 是 listview 的升级版本,更加先进和灵活。recyclerview通过设置layoutmanager,itemdecoration,itemanimator实现你想要的效果。

  • 使用layoutmanager来确定每一个item的排列方式。
  • 使用itemdecoration自己绘制分割线,更灵活
  • 使用itemanimator为增加或删除一行设置动画效果。

注意

新建完项目,需要在app/build.gradle增加recylerview依赖,不然找不到recyclerview类

compile 'com.android.support:recyclerview-v7:23.1.0'

recylerview简单的demo

我们来看activity代码,跟listview写法差不多,只是这边多设置了布局管理器。

public class linearlayoutactivity extends appcompatactivity {
 private recyclerview recyclerview;
 private recyclerviewadapter adapter;
 private list<string> datas;
 @override
 public void oncreate(bundle savedinstancestate) {
 super.oncreate(savedinstancestate);
 setcontentview(r.layout.recycler_main);
 initdata();
 recyclerview= (recyclerview) findviewbyid(r.id.recyclerview);
 recyclerview.setlayoutmanager(new linearlayoutmanager(this));//设置布局管理器
 recyclerview.additemdecoration(new divideritemdecoration(this));
 recyclerview.setadapter(adapter=new recyclerviewadapter(this,datas));
 }
 private void initdata(){
 datas=new arraylist<>();
 for(int i=0;i<100;i++){
  datas.add("item:"+i);
 }
 }
}

activity对应的布局文件:recycler_main.xml

<?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
 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" />
</relativelayout>

adapter相对listview来说变化比较大的。把viewholder逻辑封装起来了,代码相对简单一些。

  • 需要继承recyclerview.adapter,重写三个方法
  • myviewholder需要继承recyclerview.viewholder
public class recyclerviewadapter extends recyclerview.adapter<recyclerviewadapter.myviewholder>{
private list<string> datas;
private layoutinflater inflater;
public recyclerviewadapter(context context,list<string> datas){
 inflater=layoutinflater.from(context);
 this.datas=datas;
}
//创建每一行的view 用recyclerview.viewholder包装
@override
public recyclerviewadapter.myviewholder oncreateviewholder(viewgroup parent, int viewtype) {
 view itemview=inflater.inflate(r.layout.recycler_item,null);
 return new myviewholder(itemview);
}
//给每一行view填充数据
@override
public void onbindviewholder(recyclerviewadapter.myviewholder holder, int position) {
 holder.textview.settext(datas.get(position));
}
//数据源的数量
@override
public int getitemcount() {
 return datas.size();
}
class myviewholder extends recyclerview.viewholder{
 private textview textview;

 public myviewholder(view itemview) {
 super(itemview);
 textview= (textview) itemview.findviewbyid(r.id.textview);
 }
}
}

我们来看看效果图:

 RecyclerView使用详解

recyclerview增加分隔线

recyclerview是没有android:divider跟android:dividerheight属性的,如果我们需要分割线,就只能自己动手去实现了。

  • 需要继承itemdecoration类,实现ondraw跟getitemoffsets方法。
  • 调用recyclerview的additemdecoration方法。

我们先写一个divideritemdecoration类,继承recyclerview.itemdecoration,在getitemoffsets留出item之间的间隔,然后就会调用ondraw方法绘制(ondraw的绘制优先于每一行的绘制)

public class divideritemdecoration extends recyclerview.itemdecoration{
 /*
 * recyclerview的布局方向,默认先赋值 为纵向布局
 * recyclerview 布局可横向,也可纵向
 * 横向和纵向对应的分割线画法不一样
 * */
 private int morientation = linearlayoutmanager.vertical;
 private int mitemsize = 1;//item之间分割线的size,默认为1
 private paint mpaint;//绘制item分割线的画笔,和设置其属性
 public divideritemdecoration(context context) {
 this(context,linearlayoutmanager.vertical,r.color.coloraccent);
 }
 public divideritemdecoration(context context, int orientation) {
 this(context,orientation, r.color.coloraccent);
 }
 public divideritemdecoration(context context, int orientation, int dividercolor){
 this(context,orientation,dividercolor,1);
 }
 /**
 * @param context
 * @param orientation 绘制方向
 * @param dividercolor 分割线颜色 颜色资源id
 * @param mitemsize 分割线宽度 传入dp值就行
 */
 public divideritemdecoration(context context, int orientation, int dividercolor, int mitemsize){
 this.morientation = orientation;
 if(orientation != linearlayoutmanager.vertical && orientation != linearlayoutmanager.horizontal){
  throw new illegalargumentexception("请传入正确的参数") ;
 }
 //把dp值换算成px
 this.mitemsize = (int) typedvalue.applydimension(typedvalue.complex_unit_dip,mitemsize,context.getresources().getdisplaymetrics());
 mpaint = new paint(paint.anti_alias_flag);
 mpaint.setcolor(context.getresources().getcolor(dividercolor));
 }
 @override
 public void ondraw(canvas c, recyclerview parent, recyclerview.state state) {
 if(morientation == linearlayoutmanager.vertical){
  drawvertical(c,parent) ;
 }else {
  drawhorizontal(c,parent) ;
 }
 }
 /**
 * 绘制纵向 item 分割线
 * @param canvas
 * @param parent
 */
 private void drawvertical(canvas canvas,recyclerview parent){
 final int left = parent.getpaddingleft() ;
 final int right = parent.getmeasuredwidth() - parent.getpaddingright();
 final int childsize = parent.getchildcount() ;
 for(int i = 0 ; i < childsize ; i ++){
  final view child = parent.getchildat( i ) ;
  recyclerview.layoutparams layoutparams = (recyclerview.layoutparams) child.getlayoutparams();
  final int top = child.getbottom() + layoutparams.bottommargin ;
  final int bottom = top + mitemsize ;
  canvas.drawrect(left,top,right,bottom,mpaint);
 }
 }
 /**
 * 绘制横向 item 分割线
 * @param canvas
 * @param parent
 */
 private void drawhorizontal(canvas canvas,recyclerview parent){
 final int top = parent.getpaddingtop() ;
 final int bottom = parent.getmeasuredheight() - parent.getpaddingbottom() ;
 final int childsize = parent.getchildcount() ;
 for(int i = 0 ; i < childsize ; i ++){
  final view child = parent.getchildat( i ) ;
  recyclerview.layoutparams layoutparams = (recyclerview.layoutparams) child.getlayoutparams();
  final int left = child.getright() + layoutparams.rightmargin ;
  final int right = left + mitemsize ;
  canvas.drawrect(left,top,right,bottom,mpaint);
 }
 }
 /**
 * 设置item分割线的size
 * @param outrect
 * @param view
 * @param parent
 * @param state
 */
 @override
 public void getitemoffsets(rect outrect, view view, recyclerview parent, recyclerview.state state) {
 if(morientation == linearlayoutmanager.vertical){
  outrect.set(0,0,0,mitemsize);//垂直排列 底部偏移
 }else {
  outrect.set(0,0,mitemsize,0);//水平排列 右边偏移
 }
 }
}

不要忘记调用additemdecoration方法哦

recyclerview.additemdecoration(new divideritemdecoration(this));//添加分割线

重新运行,效果图:

RecyclerView使用详解

大家读到这里肯定会有一个疑问,这货比listview麻烦多了啊,但是google官方为什么要说是listview的升级版呢?接下来开始放大招。。。

gridlayoutmanager

在recyclerview中实现不同的列表,只需要切换不同的layoutmanager即可。recyclerview.layoutmanager跟recyclerview.itemdecoration一样,都是recyclerview静态抽象内部类,但是layoutmanager有三个官方写好的实现类。

  • linearlayoutmanager 线性布局管理器 跟listview功能相似
  • gridlayoutmanager 网格布局管理器 跟gridview功能相似
  • staggeredgridlayoutmanager 瀑布流布局管理器

刚刚我们用的是linearlayoutmanager,现在我们切换到gridlayoutmanager,看到下面这句代码,有没有感觉分分钟切换不同列表显示。

recyclerview.setlayoutmanager(new gridlayoutmanager(this,2));

如果要显示多列或者要纵向显示就new不同的构造方法,以下代码纵向显示4列。当前如果你还需要反方向显示,把false改成true就可以。

recyclerview.setlayoutmanager(new gridlayoutmanager(this,4,gridlayoutmanager.horizontal,false));

因为用的是网格布局,所以呢绘制分割线的代码需要重新修改一下。网格布局一行可以有多列,并且最后一列跟最后一行不需要绘制,所以我们得重新创建一个类。

dividergriditemdecoration.java

public class dividergriditemdecoration extends recyclerview.itemdecoration {
 /*
 * recyclerview的布局方向,默认先赋值 为纵向布局
 * recyclerview 布局可横向,也可纵向
 * 横向和纵向对应的分割线画法不一样
 * */
 private int morientation = linearlayoutmanager.vertical;
 private int mitemsize = 1;//item之间分割线的size,默认为1
 private paint mpaint;//绘制item分割线的画笔,和设置其属性
 public dividergriditemdecoration(context context) {
 this(context,linearlayoutmanager.vertical,r.color.coloraccent);
 }
 public dividergriditemdecoration(context context, int orientation) {
 this(context,orientation, r.color.coloraccent);
 }
 public dividergriditemdecoration(context context, int orientation, int dividercolor){
 this(context,orientation,dividercolor,1);
 }
 /**
 * @param context
 * @param orientation 绘制方向
 * @param dividercolor 分割线颜色 颜色资源id
 * @param mitemsize 分割线宽度 传入dp值就行
 */
 public dividergriditemdecoration(context context, int orientation, int dividercolor, int mitemsize){
 this.morientation = orientation;
 if(orientation != linearlayoutmanager.vertical && orientation != linearlayoutmanager.horizontal){
  throw new illegalargumentexception("请传入正确的参数") ;
 }
 //把dp值换算成px
 this.mitemsize = (int) typedvalue.applydimension(typedvalue.complex_unit_dip,mitemsize,context.getresources().getdisplaymetrics());
 mpaint = new paint(paint.anti_alias_flag);
 mpaint.setcolor(context.getresources().getcolor(dividercolor));
 }
 @override
 public void ondraw(canvas c, recyclerview parent, recyclerview.state state) {
 drawhorizontal(c, parent);
 drawvertical(c, parent);
 }
 private int getspancount(recyclerview parent) {
 // 列数
 int spancount = -1;
 recyclerview.layoutmanager layoutmanager = parent.getlayoutmanager();
 if (layoutmanager instanceof gridlayoutmanager) {
  spancount = ((gridlayoutmanager) layoutmanager).getspancount();
 } else if (layoutmanager instanceof staggeredgridlayoutmanager) {
  spancount = ((staggeredgridlayoutmanager) layoutmanager).getspancount();
 }
 return spancount;
 }
 public void drawhorizontal(canvas canvas, recyclerview parent) {
 int childcount = parent.getchildcount();
 for (int i = 0; i < childcount; i++) {
  final view child = parent.getchildat(i);
  final recyclerview.layoutparams params = (recyclerview.layoutparams) child.getlayoutparams();
  final int left = child.getleft() - params.leftmargin;
  final int right = child.getright() + params.rightmargin + mitemsize;
  final int top = child.getbottom() + params.bottommargin;
  final int bottom = top + mitemsize;
  canvas.drawrect(left,top,right,bottom,mpaint);
 }
 }
 public void drawvertical(canvas canvas, recyclerview parent) {
 final int childcount = parent.getchildcount();
 for (int i = 0; i < childcount; i++) {
  final view child = parent.getchildat(i);
  final recyclerview.layoutparams params = (recyclerview.layoutparams) child.getlayoutparams();
  final int top = child.gettop() - params.topmargin;
  final int bottom = child.getbottom() + params.bottommargin;
  final int left = child.getright() + params.rightmargin;
  final int right = left + mitemsize;
  canvas.drawrect(left,top,right,bottom,mpaint);
 }
 }
 @override
 public void getitemoffsets(rect outrect, int itemposition,recyclerview parent) {
 int spancount = getspancount(parent);
 int childcount = parent.getadapter().getitemcount();
 if (islastrow(parent, itemposition, spancount, childcount)){//如果是最后一行,不需要绘制底部
  outrect.set(0, 0, mitemsize, 0);
 } else if (islastcolum(parent, itemposition, spancount, childcount)){// 如果是最后一列,不需要绘制右边
  outrect.set(0, 0, 0, mitemsize);
 } else {
  outrect.set(0, 0, mitemsize,mitemsize);
 }
 }
 private boolean islastcolum(recyclerview parent, int pos, int spancount, int childcount) {
 recyclerview.layoutmanager layoutmanager = parent.getlayoutmanager();
 if (layoutmanager instanceof gridlayoutmanager) {
  if ((pos + 1) % spancount == 0){// 如果是最后一列,则不需要绘制右边
  return true;
  }
 } else if (layoutmanager instanceof staggeredgridlayoutmanager) {
  int orientation = ((staggeredgridlayoutmanager) layoutmanager).getorientation();
  if (orientation == staggeredgridlayoutmanager.vertical) {
  if ((pos + 1) % spancount == 0){// 如果是最后一列,则不需要绘制右边
   return true;
  }
  } else {
  childcount = childcount - childcount % spancount;
  if (pos >= childcount)// 如果是最后一列,则不需要绘制右边
   return true;
  }
 }
 return false;
 }
 private boolean islastrow(recyclerview parent, int pos, int spancount, int childcount) {
 recyclerview.layoutmanager layoutmanager = parent.getlayoutmanager();
 if (layoutmanager instanceof gridlayoutmanager) {
  childcount = childcount - childcount % spancount;
  if (pos >= childcount)//最后一行
  return true;
 } else if (layoutmanager instanceof staggeredgridlayoutmanager) {
  int orientation = ((staggeredgridlayoutmanager) layoutmanager).getorientation();
  if (orientation == staggeredgridlayoutmanager.vertical){//纵向
  childcount = childcount - childcount % spancount;
  if (pos >= childcount)//最后一行
   return true;
  } else{ //横向
  if ((pos + 1) % spancount == 0) {//是最后一行
   return true;
  }
  }
 }
 return false;
 }
}

写了这两个画分割线的类,主流的布局:线性列表跟网格列表都能展示了。。。赶紧运行代码看看结果:

 RecyclerView使用详解

staggeredgridlayoutmanager

actviity中修改下布局管理器,大家应该感觉很熟悉了吧~~~

recyclerview.setlayoutmanager(new staggeredgridlayoutmanager(3,staggeredgridlayoutmanager.vertical));

瀑布流列表一般列的高度是不一致的,为了模拟不同的宽高,数据源我把string类型改成了对象.然后初始化的时候随机了一个高度.

public class itemdata {
 private string content;//item内容
 private int height;//item高度
 public itemdata() {
 }
 public itemdata(string content, int height) {
 this.content = content;
 this.height = height;
 }
 public string getcontent() {
 return content;
 }
 public void setcontent(string content) {
 this.content = content;
 }
 public int getheight() {
 return height;
 }
 public void setheight(int height) {
 this.height = height;
 }
}

瀑布流列表没有添加分割线,给item布局设置了android:padding属性。recycler_staggered_item.xml

<?xml version="1.0" encoding="utf-8"?>
<framelayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:padding="5dp"
 android:layout_width="wrap_content"
 android:layout_height="match_parent">
 <textview
 android:id="@+id/textview"
 android:background="@color/coloraccent"
 android:layout_width="100dp"
 android:layout_height="wrap_content"
 android:gravity="center"
 android:text="122"
 android:textsize="20sp"/>
</framelayout>

最后我们在适配器的onbindviewholder方法中给itemd中的textview设置一个高度

@override
public void onbindviewholder(staggeredgridadapter.myviewholder holder, int position) {
 itemdata itemdata=datas.get(position);
 holder.textview.settext(itemdata.getcontent());
 //手动更改高度,不同位置的高度有所不同
 holder.textview.setheight(itemdata.getheight());
}

是不是感觉so easy,赶紧运行看看效果:

RecyclerView使用详解

添加header跟footer

recyclerview添加头部跟底部是没有对应的api的,但是我们很多的需求都会用到,于是只能自己想办法实现了。我们可以通过适配器的getitemviewtype方法来实现这个功能。

修改后的适配器代码:recyclerheadfootviewadapter.java

public class recyclerheadfootviewadapter extends recyclerview.adapter<recyclerview.viewholder>{
 private list<string> datas;
 private layoutinflater inflater;
 public static final int type_header=1;//header类型
 public static final int type_footer=2;//footer类型
 private view header=null;//头view
 private view footer=null;//脚view
 public recyclerheadfootviewadapter(context context, list<string> datas){
 inflater=layoutinflater.from(context);
 this.datas=datas;
 }
 //创建每一行的view 用recyclerview.viewholder包装
 @override
 public recyclerview.viewholder oncreateviewholder(viewgroup parent, int viewtype) {
 if(viewtype==type_header){
  return new recyclerview.viewholder(header){};
 }else if(viewtype==type_footer){
  return new recyclerview.viewholder(footer){};
 }
 view itemview=inflater.inflate(r.layout.recycler_item,null);
 return new myviewholder(itemview);
 }
 //给每一行view填充数据
 @override
 public void onbindviewholder(recyclerview.viewholder holder, int position){
 if(getitemviewtype(position)==type_header||getitemviewtype(position)==type_footer){
  return;
 }
 myviewholder myholder= (myviewholder) holder;
 myholder.textview.settext(datas.get(getrealposition(position)));
 }
 //如果有头部 position的位置是从1开始的 所以需要-1
 public int getrealposition(int position){
 return header==null?position:position-1;
 }
 //数据源的数量
 @override
 public int getitemcount() {
 if(header == null && footer == null){//没有head跟foot
  return datas.size();
 }else if(header == null && footer != null){//head为空&&foot不为空
  return datas.size() + 1;
 }else if (header != null && footer == null){//head不为空&&foot为空
  return datas.size() + 1;
 }else {
  return datas.size() + 2;//head不为空&&foot不为空
 }
 }
 @override
 public int getitemviewtype(int position){
 //如果头布局不为空&&位置是第一个那就是head类型
 if(header!=null&&position==0){
  return type_header;
 }else if(footer!=null&&position==getitemcount()-1){//如果footer不为空&&最后一个
  return type_footer;
 }
 return super.getitemviewtype(position);
 }
 public void setheader(view header) {
 this.header = header;

 notifyiteminserted(0);//在位置0插入一条数据,然后刷新
 }
 public void setfooter(view footer) {
 this.footer = footer;
 notifyiteminserted(datas.size()-1);//在尾部插入一条数据,然后刷新
 }
 class myviewholder extends recyclerview.viewholder{
 private textview textview;

 public myviewholder(view itemview) {
  super(itemview);
  textview= (textview) itemview.findviewbyid(r.id.textview);
 }
 }
}

getitemcount

有header跟footer的时候需要在源数据长度基础上进行增加。

getitemviewtype

通过getitemviewtype判断不同的类型

oncreateviewholder

通过不同的类型创建item的view

onbindviewholder

如果是header跟footer类型是不需要绑定数据的,header跟footer的view一般在actvity中创建,不需要这边做处理,所以这两种类型我们就不往下执行,如果有头布局,position==0的位置被header占用了,但是我们的数据源也就是集合的下标是从0开始的,所以这里需要-1。

setheader

设置头布局,在第一行插入一条数据,然后刷新。注意这个方法调用后会有插入的动画,这个动画可以使用默认的,也可以自己定义

setfooter

设置尾部布局,在尾部插入一条数据,然后刷新。

添加header跟footer的方法终于封装好了,在activity中只需要两行代码就能添加header,跟listview调用addheader方法一样简单,又可以happy的玩耍了。这里需要注意的是我们初始化view的时候,inflate方法需要三个参数。

  • resource 资源id
  • root 父view
  • attachtoroot true:返回父view false:返回资源id生成的view
//添加header
view header=layoutinflater.from(this).inflate(r.layout.recycler_header,recyclerview,false);
adapter.setheader(header);
//添加footer
view footer=layoutinflater.from(this).inflate(r.layout.recycler_footer,recyclerview,false);
adapter.setfooter(footer);

recycler_header跟recycler_footer布局文件我就不贴出来了,就一个textview,我们直接看效果图:

RecyclerView使用详解

item点击事件&&增加或删除带动画效果

当我们调用recyclerview的setonitemclicklistener方法的时候,发现居然没有,用了recyclerview你要习惯什么东西都自己封装。。。

首先我们从adapter开刀,内部写一个接口,一个实例变量,提供一个公共方法,设置监听。

private recyclerviewitemclick recyclerviewitemclick;
public void setrecyclerviewitemclick(recyclerviewitemclick recyclerviewitemclick) {
 this.recyclerviewitemclick = recyclerviewitemclick;
}
public interface recyclerviewitemclick{
 /**
 * item点击
 * @param realposition 数据源position
 * @param position view position
 */
 void onitemclick(int realposition,int position);
}

在onbindviewholder方法中给item监听点击事件

if(recyclerviewitemclick!=null) {
 myholder.itemview.setonclicklistener(new view.onclicklistener() {
 @override
 public void onclick(view v) {
  recyclerviewitemclick.onitemclick(getrealposition(position),position);
 }
 });
}

在activity的oncreate方法中进行监听,顺便设置item增加删除动画。我用的是sdk自带的默认动画。

adapter.setrecyclerviewitemclick(recyclerviewitemclick);
recyclerview.setitemanimator(new defaultitemanimator());
private recyclerheadfootviewadapter.recyclerviewitemclick recyclerviewitemclick=new recyclerheadfootviewadapter.recyclerviewitemclick() {
 @override
 public void onitemclick(int realposition, int position) {
 log.i("ansen","删除数据:"+realposition+" view位置:"+position);
 log.i("ansen","当前位置:"+position+" 更新item数量:"+(adapter.getitemcount()-position-1));
 datas.remove(realposition);//删除数据源
 adapter.notifyitemremoved(position);//item移除动画
 //更新position至adapter.getitemcount()-1的数据
 adapter.notifyitemrangechanged(position,adapter.getitemcount()-position-1);
 }
};

源码下载

recyclerview

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!