面向: 列表控件多样化的viewType和高度相似的adapter代码重用问题。
背景
作为android开发人员,我们都知道一般像listView,recyclerView等数据列表型的控件在数据绑定过程中一般都会采取adapter+viewHolder这样的做法来实现,是的,这样会优化view的性能,从而让列表滚动显示数据更流畅,谷歌之前也是推荐这样做的,但是随着项目的进行与更替,需求的不断增加和变幻,接踵而来的问题也是非常明显的,根据以往经验主要列举两点:
- 不断新增的adapter;
- viewType 类型的多样(if语句不够用)导致代码质量下降。
分析
一:不断新增的adapter
我们知道adapter主要作用就是数据绑定,但是作为懒惰的程序员,重复写那些类似或者一样的代码实在是很消耗生命和激情的,我们也是需要时间去创造的。所以我们能不能只写那些我们关注的逻辑代码,忽略掉那些让人抓狂的getItem(),getItemCount()… 之流,于是下面这个想法就有必要了:
lsv.setAdapter(new MutiTypeAdapter(){
protected void convert(Data item, ViewHolder holder){
holder.getTextView().setText(item.txt);
……
}
});
二:针对viewType的多样化。
这一块可能遇到的较少,场景可能不是很多,毕竟一般两三种viewType已经够用了,但是还是不得不提啊,防范于未然嘛。
一般viewType的选择我们会这样写:
@Override public int getItemViewType(int position) { if (items.get(position) instanceof A){ return TYPE_A; } else { // accessories and special offers return TYPE_B; } } @Override publicRecycerView. ViewHolderonCreateViolder(ViewGro up par nt, int viewType) { if (TYPE_A == viewType){ return new NewsTeaserItem( inflater.inflate(R.layout.item_news_teaser, parent)); } else { // accessories and special offers return super.onCreateViewHolder(parent, viewType); } ...
确实,还不错,可以接受,但你可以想象一下七八上十个这样的type你肿么办,造成代码的臃肿和逻辑混乱怎么逃,妈妈咪啊,想想就皮痒。当然,莫要恐慌,这只是假设,我们还是可以通过前期预判来及时将病魔掐死在别人娘胎中。
可能有些人已经预料到了,是的,这种情况伟大的先贤也早已经预判到了,对,没错,就是策略模式的应用,说穿了就是组合,即插即用,不插也用。
所以,亲耐的小孩,我们可不可以这样子啊:
new Adapter(){ protected void addType(TypeManager manager){ manager.addType(Type_one).addType(Type_two); ... } }
这般如此,如此这般,是不是就会省去我们大堆码堆时间,毕竟世界那么大,我也想去看看,想起来就好兴奋好期待啊!!!
BUT,但是,这都不是我们接下来的重点,哈哈哈,怎么有种骗人的感觉啊!想想就好兴奋啊,但是额是 印真 滴, 鉴于上面的两种实现都已经有很多人实现了,代码也托管到Git上面了,所以今天的重点是怎么把这两种组合到一起呢,毕竟真要两种都遇到的时候不能我管我的你管你的,大家都不管吧,难道我们要写两套,难道我们只能做胸妹?只能是胸妹?难道我们注定不能在一起?OH,MY GOD !!!想想就皮痒啊。 可是我们都是很负责任的银,对不对。(想看两种实现源码的胸妹直接忽略跳到末尾就好。1下面我们就以recyclerView作为基础去分析一下我们想要的。
思路
不急,我们需要整理一下:首先,我需要一个什么样的东东呢,我希望调用的时候,它应该尽可能长这个样子
MutiTypeAdapter = new QucikAdapter(this,dataItemlist) ;
MutiTypeAdapter.addTypeDelegation(
new Type_A_Delegation(Type_A,layoutId_A)
{
doYourSelf(ViewHolderHelper helper,DataItem item){
helper.getTextView().setText(item.tv);
}
});
new Type_B_Delegation(Type_B,layoutId_B)
{
doYourSelf(ViewHolderHelper helper,DataItem item){
helper.getTextView().setText(item.tv);
}
});
接下来我就需要去构造这样一个模式的东东,让我们抽丝剥茧:
我们的目的:把数据绑定和业务逻辑抽调出来自己写,其他的乱七八糟的我们的不要的。
从目前我们需要调用的形式上看,暂时我们需要一些东西,这些都是行为基本,稍后在构造这些东西时遇到的新的东东也都是从之延伸出来的。都有哪些东东呢,来看:
NEEDS:
- DataList:
一个数据集,基本的填充列表数据,可忽略; - MutiTypeAdapter:
很明显,一个二次封装的adapter,提供addTypeDelegation( ) 方法,可以实现添加多个ViewType封装类 - typeDelegation:
viewType的封装实现类,该类中提供doYurSelf( ) 这样一个业务方法; - ViewHolderHelper:
viewHolder的封装工具类,从前文不难看出这是一个简化viewholder绑定UI的工具;
这样我们知道主要有三个需要去构造的类型:MutiTypeAdapter,typeDelegation,ViewHolderHelper;而要构造,就要先知道他们之间的联系,我们不能瞎造对吧。
上面已经简单的分析了,我们的MutiTypeAdapter需要提供一个list性质的东东去装载我们的typeDelegation,然后adapter中我们要做的creatView啊,bindHolder这些方法的具体实现都需要由typeDelegation去实现,谁叫你就是个组装的。所以啊,typeDelegation,很明显,这B分量最重啊。根据我们需要实现的抽象方法doYourSelf(ViewHolderHelper helper,DataItem item)来看:它是把所有的琐事都藏在自己心里了,不管它愿不愿意,它都要这样做,它已经没有余地了。那么要做什么,首先要实现doyourself这样一个抽象方法的回调,方便我们做自己的事情,要分配一个viewholderhelper,因为它要根据layoutId实现typeview的加载与viewHolder的绑定,而viewHolder的绑定与优化是由viewholderhelper对象来做的,所以对于它们我们大致就有这样的一个雏形
我们的viewholderhelper
因为recyclerView已经内部实现了ViewHolder,所以我们只需要继承一下RecyclerView.ViewHolder这个类,然后再简化一下findViewByID的过程,这里我们借鉴之前网上的轮子:
public class ViewHolderHelper extends RecyclerView.ViewHolder{
private SparseArray<View> views;
public ViewHolderHelper(View itemView){
super(itemView);
this.views = new SparseArray<View>();
}
public TextView getTextView(int viewId) {
return retrieveView(viewId);
}
public Button getButton(int viewId) {
return retrieveView(viewId);
}
public ImageView getImageView(int viewId) {
return retrieveView(viewId);
}
public View getView(int viewId) {
return retrieveView(viewId);
}
@SuppressWarnings("unchecked")
protected <T extends View> T retrieveView(int viewId) {
View view = views.get(viewId);
if (view == null) {
view = itemView.findViewById(viewId);
views.put(viewId, view);
}
return (T) view;
}
}
它提供了一个retrieveView的方法去简化findViewByID的过程,这个可以借鉴一下,毕竟不断findView和强制转化也会让人拙计,这里推荐一个优化findView,里面三种方法,可以试试。然后就是一个SparseArray优化的容器去缓存装载view,就这么多。
我们的MutiTypeAdapter
根据上面的分析,MutiTypeAdapter就是一个继承自RecyclerView.Adapter的类,就多了一个viewtypedelegation类的容器需要维护。
public class MutiTypeAdapter<E> extends RecyclerView.Adapter<ViewHolderHelper>{
private List<E> mDatas;
private Context mContext;
private DelegationManager<E> mManager;
public MutiTypeAdapter(Context mContext,List<E> mDatas) {
mManager = new DelegationManager<>();
this.mDatas = mDatas;
this.mContext = mContext;
}
public void addTypeDelegation(DelegationInterface<E> delegation){
mManager.addDelegate(delegation);
}
@Override
public int getItemViewType(int position) {
return mManager.getItemViewType(mDatas,position);
}
@Override
public ViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) {
return mManager.onCreateViewHolder(parent,viewType);
}
@Override
public void onBindViewHolder(ViewHolderHelper helper, int position) {
mManager.onBindViewHolder(mDatas,position,helper);
}
@Override
public int getItemCount() {
return mDatas.size();
}
}
可以看到这里是提供了一个manager去管理ViewTypeDelegation类的,
这里又是借鉴相关博客的轮子,下面会给出链接.上面代码是没有提供一个可以让你去添加viewtypedelegation的接口,所以我们自己申明一个这样的方法
public void addTypeDelegation(BaseTypeDelegation delegation) {
manager.add(delegation);
}
很简单对不对,简单就对了,然后呢,我们看到它这里实际上是采用一个策略模式(不了解的亲可以先去补补),避免if else的繁复,那么具体究竟怎么实现的呢,我们看到manager把adapter中需要实现的三个重要方法都分配到了delegation中去维护,
那么我们由此得知,相应的delegation是必须要具备这样统一的行为规范的,什么样的规范呢,来我们看代码
public interface DelegationInterface<T> {
public int getItemViewType();
public boolean isForViewType(@NonNull T items, int position);
@NonNull
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);
public void onBindViewHolder(@NonNull T items, int position, @NonNull RecyclerView.ViewHolder holder);
}
都是干嘛的呢:
- 同名方法getItemViewType,返回当前delegation的ViewType,对应MutiTypeAdapter中的同名方法
- 判别输入的item中的viewtype是否对应该delegation
- 同名方法onCreateViewHolder,用于接管相应adapter中要做的事情
- 同名方法onBindViewHolder,同样用于接管相应adapter中要做的事情
接下来通过继承该接口,来实现我们的ViewTypeDelegation类吧
public abstract class ViewTypeDelegation<E> implements DelegationInterface<E> , View.OnClickListener{
private int layoutID;
private int VIEW_TYPE;
public ViewTypeDelegation(int layoutID,int VIEW_TYPE){
this.layoutID = layoutID;
this.VIEW_TYPE = VIEW_TYPE;
}
@Override
public int getItemViewType() {
return VIEW_TYPE;
}
@Override
public boolean isForViewType(@NonNull List<E> items, int position) {
if (items.get(position) instanceof MutiTypeClass) {
return ((MutiTypeClass)(items.get(position))).type == getItemViewType();
}else
return true;
}
@Override
public void onClick(View v) {
onItemClick(v, (Integer) v.getTag());
}
@Override
public ViewHolderHelper onCreateViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(layoutID, parent, false);
view.setOnClickListener(this);
ViewHolderHelper vh = new ViewHolderHelper(view);
return vh;
}
@Override
public void onBindViewHolder(@NonNull List<E> items, int position, @NonNull ViewHolderHelper helper) {
helper.itemView.setTag(position);
E item = items.get(position);
convert(helper, item);
}
protected abstract void doYours(ViewHolderHelper helper, E item);
protected abstract void onItemClick(View v,int position);
}
主要实现接口规范,然后提供两个方法:doYours和onItemClick;
doYours很明显是提供的外部回调,里面编写自己的逻辑代码,已经接近我们最开始介绍的方式了。onItemClick就是一个简单的点击事件监听回调,因为recyclerview主要关于布局的灵活性,点击事件没有,所以要自己定义。主要注意的地方就是isForViewType()这个方法,我这边自定义了一个MutiTypeClass这样的类,干嘛的呢,用来装载type的,凡是需要多样ViewType的都需要继承该类,类很简单:
public class MutiTypeClass {
public int type;
}
所以这样的三个东东就好了,刚才我们发现MutiTypeAdapter里面有一个manager,不错,这个就是用来维护我们的ViewTypeDelegation的,同学们肯定很好奇怎么实现的,这边也是主要借鉴下面的博客的
public class DelegationManager<E> {
SparseArrayCompat<DelegationInterface<E>> delegates = new SparseArrayCompat();
public DelegationManager<E> addDelegate(@NonNull DelegationInterface<E> delegate) {
return addDelegate(delegate, false);
}
public DelegationManager<E> addDelegate(@NonNull DelegationInterface<E> delegate,
boolean allowReplacingDelegate) {
if (delegate == null) {
throw new NullPointerException("AdapterDelegate is null!");
}
int viewType = delegate.getItemViewType();
if (!allowReplacingDelegate && delegates.get(viewType) != null) {
throw new IllegalArgumentException(
"An AdapterDelegate is already registered for the viewType = " + viewType
+ ". Already registered AdapterDelegate is " + delegates.get(viewType));
}
delegates.put(viewType, delegate);
return this;
}
public DelegationManager<E> removeDelegate(@NonNull DelegationInterface<E> delegate) {
if (delegate == null) {
throw new NullPointerException("AdapterDelegate is null");
}
DelegationInterface<E> queried = delegates.get(delegate.getItemViewType());
if (queried != null && queried == delegate) {
delegates.remove(delegate.getItemViewType());
}
return this;
}
public DelegationManager<E> removeDelegate(int viewType) {
delegates.remove(viewType);
return this;
}
public int getItemViewType(@NonNull List<E> items, int position) {
if (items == null) {
throw new NullPointerException("Items datasource is null!");
}
int delegatesCount = delegates.size();
for (int i = 0; i < delegatesCount; i++) {
DelegationInterface<E> delegate = delegates.valueAt(i);
if (delegate.isForViewType(items, position)) {
return delegate.getItemViewType();
}
}
throw new IllegalArgumentException(
"No AdapterDelegate added that matches position=" + position + " in data source");
}
@NonNull
public ViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) {
DelegationInterface<E> delegate = delegates.get(viewType);
if (delegate == null) {
throw new NullPointerException("No AdapterDelegate added for ViewType " + viewType);
}
ViewHolderHelper vh = delegate.onCreateViewHolder(parent);
if (vh == null) {
throw new NullPointerException(
"ViewHolder returned from AdapterDelegate " + delegate + " for ViewType =" + viewType
+ " is null!");
}
return vh;
}
public void onBindViewHolder(@NonNull List<E> items, int position, @NonNull ViewHolderHelper helper) {
DelegationInterface<E> delegate = delegates.get(helper.getItemViewType());
if (delegate == null) {
throw new NullPointerException(
"No AdapterDelegate added for ViewType " + helper.getItemViewType());
}
delegate.onBindViewHolder(items, position, helper);
}
}
这个类没有太多好介绍的,无非就是将MutiAdapter中需要实现的重要方法映射给ViewTypeDelegation类,然后管理注册过的ViewTypeDelegation对象。
说了这么多都没有一点点实际的,下面我们就来看看我们的成果:
参考链接:
- https://github.com/JoanZapata/base-adapter-helper
- http://hannesdorfmann.com/android/adapter-delegates/
项目已经放在github上了,暂时比较粗糙,有兴趣的可以看看。
- 待嘎好啊!我就是末尾,末尾就是我 ↩