Android RecyclerView 数据绑定实例代码
前言
在上一个项目里有很多很多很多很多的recyclerview,然后我需要写很多很多很多很多的adapter和viewholder——多倒没问题,但是里面有很多重复的代码这就不能忍了!每一个adapter和viewholder其实做的事情非常的像:视图绑定,数据绑定,点击事件分发。还有啥?既然它们做的事情都一样,为啥我们还要傻傻的继续写着重复的代码?
正文
baseadapter
通常我们要创建一个recyclerview.adapter是怎么做的?
- 接收一个数据列表
- 重写getitemcount()方法,确定item的个数
- 重写oncreateviewholder()方法,绑定layout,新建一个我们自己写的recyclerview.viewholder
- 重写onbindviewholder()方法,进行数据和视图绑定
- 由于recyclerview没有写点击事件,把点击事件分发出去
基本上就是这个套路,或者再加一个refreshdata()的方法——传新的数据进来然后notifydatasetchanged()。基于这些点,我写了一个baseadapter基类:
/** * adapter基类. * 适用于只有单个item的recyclerview. * * created by lypeer on 16-5-24. */ public abstract class baseadapter<v> extends recyclerview.adapter<recyclerview.viewholder> { /** * 装载了每个item的value的列表 */ private list<v> mvaluelist; /** * 我写的一个接口,通过回调分发点击事件 */ private onitemclicklistener<v> monitemclicklistener; @override public recyclerview.viewholder oncreateviewholder(viewgroup parent, int viewtype) { return createviewholder(parent.getcontext(), parent); } @override @suppresswarnings("unchecked")//一定会是baseviewholder的子类,因为createviewholder()的返回值 public void onbindviewholder(recyclerview.viewholder holder, int position) { //baseviewholder是我抽象出来的recyclerview.viewholder的基类,下面会有详细讲解 ((baseviewholder) holder).setdata(mvaluelist.get(position), position, monitemclicklistener); } /** * 设置每个item的点击事件 * @param listener */ public void setonclicklistener(onitemclicklistener<v> listener) { this.monitemclicklistener = listener; } /** * 刷新数据 * @param valuelist 新的数据列表 */ public void refreshdata(list<v> valuelist) { this.mvaluelist = valuelist; notifydatasetchanged(); } @override public int getitemcount() { return mvaluelist == null ? 0 : mvaluelist.size(); } /** * 生成viewholder * @param context * @param parent * @return */ protected abstract baseviewholder createviewholder(context context, viewgroup parent); }
它的子类在继承它的时候需要指定泛型的具体类型,因为不同的item也许其数据类型并不一样,这样就可以适应更多的item。另外,其中提到了一个接口onitemclicklistener,这个接口很简单:
/** * 点击事件的接口 * created by lypeer on 16-5-24. */ public interface onitemclicklistener<v> { /** * 当item被点击的时候进行事件分发 * * @param itemvalue 点击的item传递的值 * @param viewid 点击控件的id * @param position 被点击的item的位置 */ void onitemclick(v itemvalue, int viewid, int position); }
在使用它的时候同样需要使用泛型——原因和上面一样。
通过上面的baseadapter,我们把很多的共有操作都封装在了基类里面,而它的子类只需要根据需要新建不同的viewholder就行了——当然,这个viewholder必须继承自baseviewholder,而baseviewholder是什么下面会有详细讲解。接下来是一个例子,假设我们现在在一个界面要有一个recyclerview,它的每个item的数据是一个string值,那么怎么使用我们的baseadapter简化开发过程呢?
public class sampleadapter extends baseadapter<string> { @override protected baseviewholder createviewholder(context context, viewgroup parent) { //sampleviewholder继承自baseviewholder return new sampleviewholder(context, parent); } }
是的,你没有看错!就只有这么几行代码!5秒完成!惊喜么?!
你只需要新建一个sampleadapter继承自baseadapter,然后指定其泛型为string,再return new sampleviewholder(context, parent) , 就完成了整个操作。
但是,有些读者也许会有疑惑:也许我们需要在sampleviewholder里面做很多的操作呢?那岂不是只是把代码转换了一个地方而已,实质上并没有什么优化之处。
然而并不是。
baseviewholder
我一直认为,将viewholder写在adapter里面是挺不明智的一个做法。这样的话adapter这个类的职责太重了,它做得事情太多了,从数据接收到界面绑定,从控件初始化到点击事件分发,它简直什么都做完了。而这样是很不好的。很轻易的,一个比较复杂的recyclerview的adapter的代码就能达到成百上千行,这很可怕,这意味着这个类将变得冗杂且难以维护。那么怎么避免这种情况发生呢?我选择了将viewholder分离,同时加重viewholder的职责,使它们能比较均衡。
直接看baseviewholder的代码:
/** * viewholder基类 * * created by lypeer on 16-5-27. */ public abstract class baseviewholder<v> extends recyclerview.viewholder { public baseviewholder(context context, viewgroup root, int layoutres) { super(layoutinflater.from(context).inflate(layoutres, root, false)); //这里使用了butterknife来进行控件的绑定 butterknife.bind(this, itemview); } /** * 方便其子类进行一些需要context的操作. * * @return 调用者的context */ public context getcontext() { return itemview.getcontext(); } /** * 抽象方法,绑定数据. * 让子类自行对数据和view进行绑定 * * @param itemvalue item的数据 * @param position 当前item的position * @param listener 点击事件监听者 */ protected abstract void binddata(v itemvalue, int position, onitemclicklistener listener); /** * 用于传递数据和信息 * * @param itemvalue * @param position * @param listener */ public void setdata(v itemvalue, int position, onitemclicklistener listener) { binddata(itemvalue, position, listener); } }
baseviewholder同样采用了泛型,以适应不同的数据类型。同时,我在baseviewholder里面使用了butterknife来简化代码,从此再也不用反反复复的findviewbyid了。
另外,大家可以看到baseviewholder里面的构造方法中传入了三个参数,但是在上面baseadapter的例子里面sampleviewholder的构造方法我们却只传入了它的前两个构造参数,而第三个参数layoutres并没有传进去,这是怎么回事呢?是上面写错了么?当然不是。baseviewholder的构造方法中有三个传参是因为它需要三个传参,而它的子类只有两个传参是因为它只能有两个传参。_baseviewholder必须要满足它的super构造,所以必须要有那三个参数,而它的子类如果那三个参数都是由外界传进来的,那么它怎么进行针对那个布局进行特异化的操作?它必须在类里面显式的指定layout id_——这其实是我很想优化的一个地方,因为这样的话子类继承baseviewholder之后还要修改它的构造方法,这是比较让人不省心的,但是目前还没有想到什么好点子来优雅地优化它。
baseviewholder里面有两个看起来很像的方法:setdata()和binddata(),然而实际上除了传参相同,它们其他方面根本完全不一样。setdata()方法是一个public的方法,可以由baseviewholder的子类的对象调用,其作用是从外部传入数据,以供viewholder所hold的那个view初始化,它可以说是一座传输信息的桥梁;而getdata()是一个抽象的方法,它的具体实现在每一个baseviewholder的子类中,这些子类在这个方法里面进行控件的绑定和初始化,以及对控件的点击事件的处理等等。
说到点击事件的处理,它的子类应该怎么完成这件事呢?通过onitemclicklistener。我们可以借助于onitemclicklistener的接口回调来将需要处理的点击事件传递到外界,然后由外界进行处理。那么如果有多个控件的点击事件需要处理怎么办?不用担心,因为在onitemclicklistener的onclick方法中你需要传入点击的控件的id,这样一来就可以在外界进行对传入id的判断,从而针对不同的id执行不同的点击事件了。
接下来通过一个例子来看下具体怎么用,就是上面的那个sampleviewholder吧:
public class sampleviewholder extends baseviewholder<string> { //一个普通的可点击的textview @bind(r.id.is_tv_content) textview mistvcontent; public sampleviewholder(context context, viewgroup root) { //修改了构造方法,在这里显式指定layout id super(context, root, r.layout.item_sample); } @override protected void binddata(final string itemvalue, final int position, final onitemclicklistener listener) { //在这里完成控件的初始化,将其与数据绑定 if (itemvalue != null) { mistvcontent.settext(itemvalue); } //如果需要有点击事件,就通过listener把它传递出去 mistvcontent.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { //如果外界没有调用baseadapter.setonclicklistener(), //listener就为null if(listener == null){ return; } //listener不为null就将这个事件传递给外界处理 listener.onitemclick(itemvalue , v.getid() , position); } }); } }
同样很简单方便快捷清晰。但是还是有几点值得注意的地方。首先,不能忘了修改构造方法,显式的在super里面指定layout id,不然下一步都没法做,那就懵逼了。另外,在调用listener.onclick()方法的时候必须进行listener的验空——因为listener真的有可能为空!这样的话非常容易产生空异常导致程序崩溃。
宾果,就这样,sampleviewholder就完成了,同样几乎不费吹灰之力。
结语
这篇博文的目的是分享一些我总结出来的东西,希望能让大家的开发加速那么一点点。当然,这里面也许还有我没发现的bug什么的,如果大家在使用的过程中发现了问题请不要客气,狠狠地砸给我吧!
最后还是再总结一下在有了baseadapter和baseviewholder的情况下怎么最快的搞定recyclerview的那一套:
viewholder相关
新建 xxxviewholder 继承自baseviewholder,指定泛型类型(也就是item中数据的数据类型)。
删掉构造方法中的layoutres参数,在super里面显式指定layout id。
用butterknife绑定控件。
在binddata()方法中完成控件的初始化以及点击事件的传递(别忘了listener的验空)
adapter相关
新建 xxxadapter 继承自baseadapter,指定泛型类型(也就是item中数据的数据类型)。
return new xxxviewholder(context, parent);
外界相关
绑定recyclerview,新建xxxadapter。
调用 baseadapter.refreshdata()方法传入数据列表。
如果有对点击事件处理的需求,则调用baseadapter.setonclicklistener()方法。
目前我只做了针对recyclerview中单个的item的baseadapter,但是baseviewholder使可以通用的,并且其在多item下也可以大大的简化adapter的体积。
以上就是对android recyclerview 数据绑定的资料整理,后续继续补充相关资料谢谢大家对本站的支持!