当前位置: 移动技术网 > 移动技术>移动开发>Android > Android开发中RecyclerView组件使用的一些进阶技讲解

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

2019年07月24日  | 移动技术网移动技术  | 我要评论
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"

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网