Android开发中RecyclerView组件使用的一些进阶技讲解
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"