快速开发偷懒必备(二) 支持DataBinding啦~爽炸,花式列表一行实现
本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
概述
在前文快速开发偷懒必备(一)中,我们利用Adapter模式封装了一个库,能快速为任意ViewGroup添加子View。
有如下特点:
-
快速简单使用
-
支持任意ViewGroup
-
无耦合
-
无侵入性
-
Item支持多种类型
在库中V1.1.0版本,我也顺手加入了RecyclerView、ListView、GridView的通用Adapter功能,库地址在这里。
现在V1.2.0版本发布,我又加入了我最近超爱的一个技术,DataBinding。
封装了一套一行代码实现花式列表的Adapter。
即利用DataBinding实现RecyclerView中快速使用的Adapter。
以后不管写多种type还是单type的列表,利用DataBinding 和本库,都只需要一行代码!
这里也算是安利DataBinding吧,真的超好用。还没使用的朋友们,在看到本文可以如此简单写花式列表后,建议去学习一下。
先看用法吧,简单粗暴到没朋友。
用法
使用必读:
BaseBindingAdapter
利用DataBinding提供的动态绑定技术,使用BR.data
封装数据、BR.itemP
封装点击事件。所以对layout
有以下要求:
-
layout中 数据name起名data
-
layout中 点击事件Presenter起名 itemP
如:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="itemP"
type="mcxtzhang.commonviewgroupadapter.databinding.rv.single.DBSingleActivity.SingleItemPresenter"/>
<variable
name="data"
type="mcxtzhang.commonviewgroupadapter.databinding.rv.single.DBSingleBean"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:background="@color/colorAccent"
android:onClick="@{v->itemP.onItemClick(data)}"
android:orientation="horizontal">
<ImageView
android:id="@+id/ivAvatar"
android:layout_width="200dp"
android:layout_height="200dp"
app:netUrl="@{data.avatar}"
tools:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{data.name}"
tools:text="测试多种"/>
</LinearLayout>
</layout>
1 单Item列表
效果如图:
顺带演示了BaseBindingAdapter封装的一些增删功能。
单Item
用法:
和其他BaseAdapter用法一致:
- 构造函数只需要传入context,datas,layout
mAdapter = new BaseBindingAdapter(this, mDatas, R.layout.item_db_single);
好了,列表已经出来了。我不骗你,就这一句话。
如果需要设置点击事件(点击事件设置所有类型都一样,下不赘述):
//★ 设置Item点击事件
mAdapter.setItemPresenter(new SingleItemPresenter());
/**
* ★ Item点击事件P
*/
public class SingleItemPresenter {
public void onItemClick(DBSingleBean data) {
data.setName("修改之后立刻见效");
}
}
特殊需求:
如果有特殊需求,可传入两个泛型,重写onBindViewHolder搞事情:
// ★泛型D:是Bean类型,如果有就传。 泛型B:是对应的xml Layout的Binding类
mAdapter = new BaseBindingAdapter<DBSingleBean, ItemDbSingleBinding>(this, mDatas, R.layout.item_db_single) {
@Override
public void onBindViewHolder(BaseBindingVH<ItemDbSingleBinding> holder, int position) { //★super一定不要删除
super.onBindViewHolder(holder, position); //如果有特殊需求,可传入两个泛型,重写onBindViewHolder搞事情。
ItemDbSingleBinding binding = holder.getBinding();
DBSingleBean data = mDatas.get(position);
}
};
2 多Item同种数据类型列表
一般是像IM那种列表,虽然Item不同,但是数据结构是同一个。用法,一句话~
效果如图:
多Item同数据结构
用法:
-
数据结构(JavaBean)需实现
IBaseMulInterface
接口,根据情况返回不同的layout。 -
构造函数只需要传入context,datas.
mAdapter = new BaseMulTypeBindingAdapter(this, mDatas);
复杂列表依然一句话。
public class MulTypeSingleBean extends BaseObservable implements IBaseMulInterface {
private String avatar;
private String name;
private boolean receive;
@Override
public int getItemLayoutId() { if (isReceive()) { return R.layout.item_db_mul_1;
} else { return R.layout.item_db_mul_2;
}
}
}
特殊需求:
如果有特殊需求,可传入数据结构的泛型,避免强转,重写onBindViewHolder()
方法,但是Binding类 不可避免的需要强转了:
mAdapter = new BaseMulTypeBindingAdapter<MulTypeSingleBean>(this, mDatas) {
@Override
public void onBindViewHolder(BaseBindingVH<ViewDataBinding> holder, int position) { super.onBindViewHolder(holder, position); //如果有特殊需求,可传入数据结构的泛型,避免强转
MulTypeSingleBean data = mDatas.get(position); //Binding类 不可避免的需要强转了
ViewDataBinding binding = holder.getBinding(); switch (data.getItemLayoutId()) { case R.layout.item_db_mul_1:
ItemDbMul1Binding itemDbMul1Binding = (ItemDbMul1Binding) binding; break; case R.layout.item_db_mul_2:
ItemDbMul2Binding itemDbMul2Binding = (ItemDbMul2Binding) binding; break;
}
}
};
3 多Item、多种数据类型列表
各大APP首页,Banner、列表、推荐混排,数据结构肯定不同,但是依然只要一句代码搞定Adapter!
效果如图:
多Item、多数据结构
用法:
-
数据结构(JavaBean)需分别实现
IBaseMulInterface
接口,返回数据结构对应的layout。 -
构造函数只需要传入context,datas.
mAdapter = new BaseMulTypeBindingAdapter(this, mDatas);
public class MulTypeMulBean1 extends BaseObservable implements IBaseMulInterface {
private String avatar;
private String name;
@Override
public int getItemLayoutId() { return R.layout.item_db_mulbean_1;
}
}
public class MulTypeMulBean2 extends BaseObservable implements IBaseMulInterface {
private String background;
@Override
public int getItemLayoutId() { return R.layout.item_db_mulbean_2;
}
}
特殊需求:
如果有特殊需求,重写onBindViewHolder()
方法,但是数据结构 和 Binding类 都不可避免的需要强转了:
mAdapter = new BaseMulTypeBindingAdapter(this, mDatas) {
@Override
public void onBindViewHolder(BaseBindingVH holder, int position) { super.onBindViewHolder(holder, position); //如果有特殊需求 重写onBindViewHolder方法
// 数据结构 和 Binding类 都不可避免的需要强转了
ViewDataBinding binding = holder.getBinding(); switch (getItemViewType(position)) { case R.layout.item_db_mul_1:
ItemDbMul1Binding itemDbMul1Binding = (ItemDbMul1Binding) binding;
MulTypeMulBean1 data1 = (MulTypeMulBean1) mDatas.get(position); break; case R.layout.item_db_mul_2:
ItemDbMul2Binding itemDbMul2Binding = (ItemDbMul2Binding) binding;
MulTypeMulBean2 data2 = (MulTypeMulBean2) mDatas.get(position); break;
}
}
};
4 不能忘了上文的ViewGroup呀
对上文封装的ViewGroup类型Adapter也提供DataBinding的支持。
效果如图:
当然还是流式布局搭配史上集成最叼侧滑菜单控件。
用法:
和上文快速开发偷懒必备(一)一样,只是Adapter换成SingleBindingAdapter
mAdapter = new SingleBindingAdapter<>(this, mDatas = iniDatas(), R.layout.item_db_flow_swipe);
如果需要设置点击事件:
mAdapter.setItemPresenter(new ItemDelPresenter());
设计思路与实现
使用起来如此爽快,其实写起来也很简单。
注意类BaseBindingAdapter
和BaseMulTypeBindingAdapter
都不是abstract
的,这说明我们不需要重写任何方法。
利用DataBinding,我们在BasexxxAdapter内部和xml分别做View的创建和数据绑定的工作。
UML类图
UML类图
先简要概括
-
BaseBindingVH
继承自RecyclerView.ViewHolder
,持有T extends ViewDataBinding
类型的mBinding
变量。利用ViewDataBinding
我们将不用再写任何ViewHolder
。 -
BaseBindingAdapter
,继承自RecyclerView.Adapter
,依赖BaseBindingVH
,onCreateViewHolder(ViewGroup parent, int viewType)
方法返回BaseBindingVH
作为ViewHolder
。
内部持有三个重要变量:数据对应layout,数据集,Item点击事件处理类。数据对应layout会在onCreateViewHolder(ViewGroup parent, int viewType)
用到。剩下两个变量在onBindViewHolder()
用到。对外暴漏setItemPresenter(Object itemPresenter)
供设置点击事件处理类。 -
IBaseMulInterface
接口和快速开发偷懒必备(一)提到的一样,返回某个数据结构对应的layout,除此之外,本文还有一个十分tricky之处,利用返回的R.layout.itemxxxx
作为ItemViewType
,在BaseMulTypeBindingAdapter
会用到。 -
BaseMulTypeBindingAdapter
继承自BaseBindingAdapter
,但是它不再关心mLayoutId
变量,它利用IBaseMulInterface
接口返回的R.layout.itemxxxx
作为ItemViewType
,这样在onCreateViewHolder(ViewGroup parent, int viewType)
的时候,就可以直接用viewType
构造出ItemView
。不再依赖mLayoutId
变量。这是一个我很得意的设计,我在优雅为RecyclerView增加HeaderView一文中,也曾用过这个方法。
BaseBindingVH
BaseBindingVH
算是一个核心类,但是又十分简单。它继承自RecyclerView.ViewHolder
,持有由泛型传入的T extends ViewDataBinding
类型的mBinding
变量。
唯一构造函数,需要一个T t
变量,然后调用super()
传入t.getRoot()
完成itemView
的赋值。同时对mBinding
变量赋值。
对外暴漏getBinding()
返回mBinding
变量。
利用ViewDataBinding
我们将不用再写任何ViewHolder
。
public class BaseBindingVH<T extends ViewDataBinding> extends RecyclerView.ViewHolder {
protected final T mBinding;
public BaseBindingVH(T t) { super(t.getRoot());
mBinding = t;
}
public T getBinding() { return mBinding;
}
}
BaseBindingAdapter
BaseBindingAdapter
,继承自RecyclerView.Adapter
,依赖BaseBindingVH
,将BaseBindingVH
作为泛型传给RecyclerView.Adapter
。
同时BaseBindingAdapter
本身接受两个泛型,<D, B extends ViewDataBinding>
。
-
泛型没有特殊需求可以不传
-
泛型D:是Bean类型,如果有就传。
-
泛型B:是对应的xml Layout的Binding类
传入不传入泛型的区别已经在第二节具体用法里进行了演示,不再赘述。
内部持有三个重要变量:
-
数据对应layout
int mLayoutId;
-
数据集
List<D> mDatas;
-
Item点击事件处理类。
Object ItemPresenter;
mLayoutId
和mDatas
都由构造函数传入,没啥好说的。
对外暴漏setItemPresenter(Object itemPresenter)
供设置点击事件处理类ItemPresenter
。
ItemPresenter
是Object
类型,这样才不care你set的Item点击事件处理类是什么鬼。
onCreateViewHolder(ViewGroup parent, int viewType)
方法返回BaseBindingVH
作为ViewHolder
。
mLayoutId
会在onCreateViewHolder(ViewGroup parent, int viewType)
用到,再根据泛型B强转成对应的ViewDataBinding
:
BaseBindingVH<B> holder = new BaseBindingVH<B>((B) DataBindingUtil.inflate(mInfalter, mLayoutId, parent, false));
会在onBindViewHolder()
方法里,利用DataBinding动态绑定ViewDataBinding.setVariable(BR.itemP, ItemPresenter);
为每个Item设置点击事件。
同时,数据也是同样在里面绑定的:setVariable(BR.data, mDatas.get(position))
。
重点代码如下:
public class BaseBindingAdapter<D, B extends ViewDataBinding> extends RecyclerView.Adapter<BaseBindingVH<B>> {
protected Context mContext;
protected int mLayoutId;
protected List<D> mDatas;
protected LayoutInflater mInfalter; //用于设置Item的事件Presenter
protected Object ItemPresenter;
public BaseBindingAdapter(Context mContext, List mDatas, int mLayoutId) { this.mContext = mContext; this.mLayoutId = mLayoutId; this.mDatas = mDatas; this.mInfalter = LayoutInflater.from(mContext);
}
@Override
public BaseBindingVH<B> onCreateViewHolder(ViewGroup parent, int viewType) {
BaseBindingVH<B> holder = new BaseBindingVH<B>((B) DataBindingUtil.inflate(mInfalter, mLayoutId, parent, false));
onCreateViewHolder(holder); return holder;
} /**
* 如果需要给Vh设置监听器啥的 可以在这里
*
* @param holder
*/
public void onCreateViewHolder(BaseBindingVH<B> holder) {
} /**
* 子类除了绑定数据,还要设置监听器等其他操作。
* 可以重写这个方法,不要删掉super.onBindViewHolder(holder, position);
*
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(BaseBindingVH<B> holder, int position) {
holder.getBinding().setVariable(BR.data, mDatas.get(position));
holder.getBinding().setVariable(BR.itemP, ItemPresenter);
holder.getBinding().executePendingBindings();
} /**
* 用于设置Item的事件Presenter
*
* @param itemPresenter
* @return
*/
public BaseBindingAdapter setItemPresenter(Object itemPresenter) {
ItemPresenter = itemPresenter; return this;
}
}
BaseBindingAdapter
内部也封装了如下方法,方便数据刷新,增删(定向刷新)调用:
/**
* 刷新数据,初始化数据
*
* @param list
*/
public void setDatas(List<D> list) { if (this.mDatas != null) { if (null != list) {
List<D> temp = new ArrayList<D>();
temp.addAll(list); this.mDatas.clear(); this.mDatas.addAll(temp);
} else { this.mDatas.clear();
}
} else { this.mDatas = list;
}
notifyDataSetChanged();
} /**
* 删除一条数据
* 会自动定向刷新
*
* @param i
*/
public void remove(int i) { if (null != mDatas && mDatas.size() > i && i > -1) {
mDatas.remove(i);
notifyItemRemoved(i);
}
} /**
* 添加一条数据 至队尾
* 会自动定向刷新
*
* @param data
*/
public void add(D data) { if (data != null && mDatas != null) {
mDatas.add(data);
notifyItemInserted(mDatas.size());
}
} /**
* 在指定位置添加一条数据
* 会自动定向刷新
* <p>
* 如果指定位置越界,则添加在队尾
*
* @param position
* @param data
*/
public void add(int position, D data) { if (data != null && mDatas != null) { if (mDatas.size() > position && position > -1) {
mDatas.add(position, data);
notifyItemInserted(position);
} else {
add(data);
}
}
} /**
* 加载更多数据
*
* @param list
*/
public void addDatas(List<D> list) { if (null != list) {
List<D> temp = new ArrayList<D>();
temp.addAll(list); if (this.mDatas != null) { this.mDatas.addAll(temp);
} else { this.mDatas = temp;
}
notifyDataSetChanged();
}
}
IBaseMulInterface接口
来点简单的.
IBaseMulInterface
接口和快速开发偷懒必备(一)提到的一样,返回某个数据结构对应的layout.
除此之外,本文还有一个十分tricky之处,利用返回的R.layout.itemxxxx
作为ItemViewType
,在BaseMulTypeBindingAdapter
会用到。
因为不同的R.layout.itemxxxx
对于RecyclerView来说一定是不同的Item,
BaseMulTypeBindingAdapter
多种ItemType的Base类
BaseMulTypeBindingAdapter
继承自BaseBindingAdapter
,但是它不再关心mLayoutId
变量。因此它传给父类的泛型B就是ViewDataBinding
类本身。解释如下:
-
基类的泛型B:不用传,因为多种ItemType 肯定Layout长得不一样,那么Binding类也不一样,传入没有任何意义
-
泛型T:多Item多Bean情况可以不传。如果只有一种Bean类型,可以传入Bean,实现IBaseMulInterface接口。
或者传入IBaseMulInterface接口,可以拿到 getItemLayoutId(),
但是通过getItemViewType(int position),一样。所以多Item多Bean建议不传。
传入不传入泛型的区别已经在第二节具体用法里进行了演示,不再赘述。
getItemViewType()
直接返回 IBaseMulInterface
接口的返回值。
在onCreateViewHolder(ViewGroup parent, int viewType)
的时候,直接用viewType
构建ViewDataBinding
(ItemView
)。不再依赖mLayoutId
变量。
这是一个我很得意的设计,我在优雅为RecyclerView增加HeaderView一文中,也曾用过这个方法添加头部。
完整代码如下:
public class BaseMulTypeBindingAdapter<T extends IBaseMulInterface> extends BaseBindingAdapter<T, ViewDataBinding> {
public BaseMulTypeBindingAdapter(Context mContext, List<T> mDatas) { super(mContext, mDatas);
}
@Override
public int getItemViewType(int position) { return mDatas.get(position).getItemLayoutId();
}
@Override
public BaseBindingVH<ViewDataBinding> onCreateViewHolder(ViewGroup parent, int viewType) {
BaseBindingVH<ViewDataBinding> holder = new BaseBindingVH<ViewDataBinding>(DataBindingUtil.inflate(mInfalter, viewType, parent, false));
onCreateViewHolder(holder); return holder;
}
}
ViewGroup Adapter的实现
单item
继承SingleAdapter
,增加ItemPresenter
,在getView()
完成View创建和绑定。
public class SingleBindingAdapter<D, B extends ViewDataBinding> extends SingleAdapter<D> { //用于设置Item的事件Presenter
protected Object ItemPresenter; /**
* 用于设置Item的事件Presenter
*
* @param itemPresenter
* @return
*/
public SingleBindingAdapter setItemPresenter(Object itemPresenter) {
ItemPresenter = itemPresenter; return this;
}
public SingleBindingAdapter(Context context, List<D> datas, int itemLayoutId) { super(context, datas, itemLayoutId);
} //重写利用DataBinding做
@Override
public View getView(ViewGroup parent, int pos, D data) {
ViewDataBinding binding = DataBindingUtil.inflate(mInflater, mItemLayoutId, parent, false);
View itemView = binding.getRoot();
onBindView(parent, itemView, data, pos);
binding.setVariable(BR.data, data);
binding.setVariable(BR.itemP, ItemPresenter); return itemView;
} //空实现即可,因为DataBinding的实现都是在xml里做
@Override
public void onBindView(ViewGroup parent, View itemView, D data, int pos) {
}
}
多Item:
更简单了,继承SingleBindingAdapter
。重写getView()
即可。
public class MulTypeBindngAdapter<T extends IMulTypeHelper> extends SingleBindingAdapter<T, ViewDataBinding> {
public MulTypeBindngAdapter(Context context, List<T> datas) { super(context, datas, -1);
} //重写利用DataBinding做
@Override
public View getView(ViewGroup parent, int pos, T data) {
ViewDataBinding binding = DataBindingUtil.inflate(mInflater, data.getItemLayoutId(), parent, false);
View itemView = binding.getRoot();
onBindView(parent, itemView, data, pos);
binding.setVariable(BR.data, data);
binding.setVariable(BR.itemP, ItemPresenter); return itemView;
}
}
总结
代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/all-base-adapter
本文利用DataBinding
的ViewDataBinding
直接略去写ViewHolder
。
利用Object
类型的ItemPresenter
,兼容解决了点击事件的设置。
最得意的设计,还是利用R.layout.xxxx
这些布局文件int类型的的RID,作为ItemViewType
,一箭双雕。
DataBinding
很强,希望大家快点拥抱它。
to do list
-
ViewGroup Adapter 考虑加入复用缓存池
-
ViewGroup Adapter ,考虑替换
onBindView()
的ItemView
->通用的ViewHolder
,这样可以少写一些findViewById()
代码 -
整合DataBinding 的通用Adapter入库。
-
完善 RecyclerView、ListView的通用Adapter,支持多种ItemViewType。
-
加入一些自定义ViewGroup入库,例如流式布局,九宫格,Banner轮播图。