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的体积。












  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android数据绑定是一个支持双向绑定的库,它可以帮助你将UI组件和数据模型绑定起来,从而可以轻松地更新UI组件或数据模型的值。 Android RecyclerView是一种用于显示大量数据的高级组件,它可以在滚动时动态地加载和卸载数据项,并提供了类似于ListView布局管理器。 使用Android数据绑定RecyclerView,可以更轻松地实现列表的数据绑定。你可以在适配器中使用数据绑定表达式来更新列表项视图的值,同时还可以监听数据模型的变化,以便在数据更新时刷新列表项。 要在RecyclerView中使用数据绑定,需要将适配器继承自RecyclerView.Adapter,并且在绑定视图时使用DataBindingUtil类来获取视图的绑定对象。然后,可以在绑定对象中使用数据绑定表达式来更新视图的值。 例如,下面的代码片段演示了如何使用数据绑定更新RecyclerView列表项的文本视图: ```java public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> { private List<MyData> mDataList; // ... @Override public void onBindViewHolder(MyViewHolder holder, int position) { MyData data = mDataList.get(position); ViewDataBinding binding = DataBindingUtil.bind(holder.itemView); binding.setVariable(BR.myData, data); binding.executePendingBindings(); } } public class MyViewHolder extends RecyclerView.ViewHolder { // ... public MyViewHolder(View itemView) { super(itemView); } } ``` 在MyViewHolder类中,我们只需要简单地继承自RecyclerView.ViewHolder,并且不需要实现任何逻辑。在适配器的onBindViewHolder()方法中,我们首先获取当前位置的数据模型,然后使用DataBindingUtil.bind()方法获取视图的绑定对象。接下来,我们使用setVariable()方法将数据模型设置绑定对象的属性,然后调用executePendingBindings()方法来立即更新视图。 需要注意的是,在使用数据绑定时,你需要在build.gradle文件中添加以下依赖: ```groovy android { // ... dataBinding { enabled = true } } dependencies { // ... implementation 'com.android.databinding:library:1.4.0' } ``` 希望这个回答能够帮助你!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值