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

Android开发中RecyclerView组件使用的一些进阶技讲解

程序员文章站 2024-03-31 17:44:58
recyclerview的优势: 它自带viewholder来实现view的复用机制,再也不用listview那样在getview()里自己写了 使用lay...

recyclerview的优势:

  • 它自带viewholder来实现view的复用机制,再也不用listview那样在getview()里自己写了
  • 使用layoutmanager可以实现listview,gridview以及流式布局的列表效果
  • 通过setitemanimator(itemanimator animator)可以实现增删动画(懒的话,可以使用默认的itemanimator对象,效果也不错)
  • 控制item的间隔,可以使用additemdecoration(itemdecoration decor),不过里边的itemdecoration是一个抽象类,需要自己去实现...

用法介绍:

导入recyclerview的v7库:
recyclerview是一个android.support.v7库里的控件,因此在使用的时候我们需要在gradle配置文件里加上compile 'com.android.support:recyclerview-v7:22.2.1'来引入google官方的这个库
xml布局中,使用常规的控件引入方式,来引入recyclerview,如下:

 <android.support.v7.widget.recyclerview
 android:id="@+id/recyclerview_content"
 style="?recyclerview_style"
 android:scrollbars="vertical"
 android:fadescrollbars="true"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:layout_marginbottom="-55dp" />

代码中的写法基本和listview相差无几,但还是要重点说一下:

在实例化recyclerview之后,我们需要使用setlayoutmanager()给它设置布局管理器,其中的实参即就是layoutmanager,这里总共有两种layoutmanager:

1.staggeredgridlayoutmanager,是我们之前提到的流式布局:
它有一个构造方法staggeredgridlayoutmanager(int spancount, int orientation),第一个是网格的列数,第二个参数是数据呈现的方向
(如果是竖直,那么第一个参数的意义就是列数,反之为行数。而且第二个参数在staggeredgridlayoutmanager里也有同样名称的常量,请同学们自行采纳[这里还是建议大家使用库里自带的常量,因为他们一般都是整型值,这样可以避免各个类里所判别的常量值不一样而导致的其他问题]);
2.gridlayoutmanager,网格布局(流式布局应该是它的一个特殊情况):
gridlayoutmanager(context context, int spancount)或
gridlayoutmanager(context context, int spancount, int orientation,
 boolean reverselayout)需要说明的是,最后一个参数表示的是是否逆向布局(意思是将数据反向显示,原先从左向右,从上至下。设为true之后全部逆转)。
小提示:在这两个layoutmanager中,默认的orientation为vertical,reverselayout为false。对应的参数在gridlayoutmanager中都有对应的方法来进行补充设置。而在staggeredgridlayoutmanager中所有的方法都针对reverselayout做了判断,然而它并没有给出这个参数设定值的api。

线性布局, linearlayoutmanager
linearlayoutmanager(context context)构建一个默认布局方向为vertical的recyclerview,这种样式也是listview默认的。
linearlayoutmanager(context context, int orientation, boolean reverselayout)。发现没有,线性布局和网格布局几乎是一样的。只是少了一个spancount参数。

和listview一样,为recyclerview设置adapter

 class homeadapter extends recyclerview.adapter<homeadapter.myviewholder>
 {

 @override
 public myviewholder oncreateviewholder(viewgroup parent, int viewtype)
 {
   myviewholder holder = new myviewholder(layoutinflater.from(
       homeactivity.this).inflate(r.layout.item_home, parent,
       false));
   return holder;
 }

 @override
 public void onbindviewholder(myviewholder holder, int position)
 {
   holder.tv.settext(mdatas.get(position));
 }

 @override
 public int getitemcount()
 {
   return mdatas.size();
 }

 class myviewholder extends viewholder
 {
   textview tv;

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

如上,即就是adapter的写法。其中的viewholder就是拿来负责view的回收和复用的,这样就不需要我们自己写完viewholder之后,还要在getview(int position, view convertview, viewgroup parent)里一顿判断,一顿绑定,一顿find了。而且这里的viewholder成为了recyclerview中必须继承的一部分,重写完后就需要放入 recyclerview.adapter< >这里来对基类的范型初始化。

在这里,recyclerview已经为你封装好了:

  • getitemcount()就不必多说了,和listview是一样的
  • getitemviewtype(int position)是用来根据position的不同来实现recyclerview中对不同布局的要求。从这个方法中所返回的值会在oncreateviewholder中用到。比如头部,尾部,等等的特殊itemview(这里说成viewholder比较好)都可以在这里进行判断。
  • oncreateviewholder(viewgroup parent, int viewtype)是用来配合写好的viewholder来返回一个viewholder对象。这里也涉及到了条目布局的加载。viewtype则表示需要给当前position生成的是哪一种viewholder,这个参数也说明了recyclerview对多类型itemview的支持。
  • onbindviewholder(myviewholder holder, int position)专门用来绑定viewholder里的控件和数据源中position位置的数据。

这里,会有人问,那么item的子控件findviewbyid 去哪儿了?我们把它交给了viewholder的构造方法(其他方法也可以),它的本质是在oncreateviewholder方法里生成viewholder的时候执行的。

提升:
1.代码重构:
在上边看了这么多,有木有觉得,viewholder的功能并不是非常明确?它既负责了子控件的查询,又负责了子控件的装载工作。而布局加载和数据绑定却交给了adapter......
我们来看看掘金的做法:
首先,它把adapter和viewholder的功能以一种较为低耦合的方式进行了职能分离,让viewholder里所有的逻辑代码全部都只出现在viewholder中。我们现在就对前边提到的代码进行重构:

viewhodler:

class myviewholder extends viewholder{
  textview tv;    
  public myviewholder(context context, view view){
     super(view);
     tv = (textview) view.findviewbyid(r.id.id_num);

     //当然,我们也可以在这里使用view来对recyclerview的一个item进行事件监听,也可以使用 
     //tv等子控件来实现item的子控件的事件监听。这也是我之所以要传context的原因之一呢~
     ......
  }

  public static myviewholder newinstance(activity context, viewgroup parent){
      view view = layoutinflater.from(
       context).inflate(r.layout.item_home, parent,false);
     return new myviewholder(context, view);
  }
 }  

 //你没看错,数据绑定也被整合进来了, 
 //将adapter里的数据根据position获取到后传进来。当然,也可以根据具体情况来做调整。
  public void onbinviewholder(string data){   
    tv.settext(data);//既然这里也有子控件,那么这里也可以做item子控件的事件监听喽
}

recyclerview:

class homeadapter extends recyclerview.adapter<myviewholder>{
 @override
 public myviewholder oncreateviewholder(viewgroup parent, int viewtype){//如需要,还要对viewtype做判断
   return myviewholder.newinstance(this, parent)
 }

 @override
 public void onbindviewholder(myviewholder holder, int position){
   holder.onbindviewholder(mdatas.get(position));
 }

 @override
 public int getitemcount(){
   return mdatas.size();
 }
}

抽取一个条目点击事件,让它更像listview:

class homeadapter extends recyclerview.adapter<myviewholder>{
 private onitemclicklistener monitemclicklistener;

 public void setonitemclicklitener(onitemclicklitener monitemclicklitener) 
 { 
   this.monitemclicklitener = monitemclicklitener; 
 } 

 @override
 public myviewholder oncreateviewholder(viewgroup parent, int viewtype){//如需要,还要对viewtype做判断
   return myviewholder.newinstance(this, parent)
 }

 @override
 public void onbindviewholder(myviewholder holder, int position){
   holder.onbindviewholder(mdatas.get(position));

   //如果设置了回调,则设置点击事件 
   if (monitemclicklitener != null) { 
     viewholder.itemview.setonclicklistener(new onclicklistener() { 
       @override 
       public void onclick(view v) { 
         monitemclicklitener.onitemclick(viewholder.itemview, i); 
       } 
     }); 
   } 
 }

 @override
 public int getitemcount(){
   return mdatas.size();
 }

 public interface onitemclicklitener { 
   void onitemclick(view view, int position); 
 } 
}

接口调用

 madapter.setonitemclicklitener(new onitemclicklitener() { 
 @override 
 public void onitemclick(view view, int position) { 
   ......
 } 
});

2.external
上边提到了

控制item的间隔,可以使用additemdecoration(itemdecoration decor),不过里边的itemdecoration是一个抽象类,需要自己去实现...
这个问题,那我们来实际解决一下:

这是羊神实现的一个子类

package com.zhy.sample.demo_recyclerview;

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.linearlayoutmanager;
import android.support.v7.widget.recyclerview;
import android.support.v7.widget.recyclerview.state;
import android.util.log;
import android.view.view;

public class divideritemdecoration extends recyclerview.itemdecoration {

  private static final int[] attrs = new int[]{
      android.r.attr.listdivider
  };

  public static final int horizontal_list = linearlayoutmanager.horizontal;

  public static final int vertical_list = linearlayoutmanager.vertical;

  private drawable mdivider;

  private int morientation;

  public divideritemdecoration(context context, int orientation) {
    final typedarray a = context.obtainstyledattributes(attrs);
    mdivider = a.getdrawable(0);
    a.recycle();
    setorientation(orientation);
  }

  public void setorientation(int orientation) {
    if (orientation != horizontal_list && orientation != vertical_list) {
      throw new illegalargumentexception("invalid orientation");
    }
    morientation = orientation;
  }

  @override
  public void ondraw(canvas c, recyclerview parent) {
    log.v("recyclerview - itemdecoration", "ondraw()");

    if (morientation == vertical_list) {
      drawvertical(c, parent);
    } else {
      drawhorizontal(c, parent);
    }

  }


  public void drawvertical(canvas c, recyclerview parent) {
    final int left = parent.getpaddingleft();
    final int right = parent.getwidth() - parent.getpaddingright();

    final int childcount = parent.getchildcount();
    for (int i = 0; i < childcount; i++) {
      final view child = parent.getchildat(i);
      android.support.v7.widget.recyclerview v = new android.support.v7.widget.recyclerview(parent.getcontext());
      final recyclerview.layoutparams params = (recyclerview.layoutparams) child
          .getlayoutparams();
      final int top = child.getbottom() + params.bottommargin;
      final int bottom = top + mdivider.getintrinsicheight();
      mdivider.setbounds(left, top, right, bottom);
      mdivider.draw(c);
    }
  }

  public void drawhorizontal(canvas c, recyclerview parent) {
    final int top = parent.getpaddingtop();
    final int bottom = parent.getheight() - parent.getpaddingbottom();

    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 left = child.getright() + params.rightmargin;
      final int right = left + mdivider.getintrinsicheight();
      mdivider.setbounds(left, top, right, bottom);
      mdivider.draw(c);
    }
  }

  @override
  public void getitemoffsets(rect outrect, int itemposition, recyclerview parent) {
    if (morientation == vertical_list) {
      outrect.set(0, 0, 0, mdivider.getintrinsicheight());
    } else {
      outrect.set(0, 0, mdivider.getintrinsicwidth(), 0);
    }
  }
}

然后就是为我们的recyclerview实例添加分割线

additemdecoration(new divideritemdecoration(this,
divideritemdecoration.vertical_list));

系统默认的分割线往往达不到我们产品和设计师的要求,怎么办呢?

<item name="android:listdivider">@drawable/your_custom_divider</item>

通过以上xml属性,将系统的listdivider设为自己画出来的分割线,只需要放在你对应activity的主题下即可。

技巧:recyclerview 滚动条的显示与隐藏

<android.support.v7.widget.recyclerview
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:scrollbars="vertical"
      >

    </android.support.v7.widget.recyclerview>

纵向显示:

android:scrollbars="vertical"

横向显示:

android:scrollbars="horizontal"

隐藏:

android:scrollbars="none"