多样ViewType Adapter封装与优化

面向: 列表控件多样化的viewType和高度相似的adapter代码重用问题。

背景

作为android开发人员,我们都知道一般像listView,recyclerView等数据列表型的控件在数据绑定过程中一般都会采取adapter+viewHolder这样的做法来实现,是的,这样会优化view的性能,从而让列表滚动显示数据更流畅,谷歌之前也是推荐这样做的,但是随着项目的进行与更替,需求的不断增加和变幻,接踵而来的问题也是非常明显的,根据以往经验主要列举两点:

  1. 不断新增的adapter;
  2. 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对象。

说了这么多都没有一点点实际的,下面我们就来看看我们的成果:

效果图

参考链接:

项目已经放在github上了,暂时比较粗糙,有兴趣的可以看看


  1. 待嘎好啊!我就是末尾,末尾就是我
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一种优雅的方式来使用RecyclerView核心思想  想必大家都遇到过,在一个列表中显示不同样式的需求。在RecyclerView中可以通过ViewType进行区分,如果样式特别多的时候就会使得代码非常冗余,不利于开发及维护。那么有没有一种优雅的方法解决这个问题呢?  技术经理给你说,接下来的项目由你负责,明天下班前把排期同步出来。这时你应该怎么做?由于你是Android端的RD,你对Android的排期是比较了解的,但是iOS端、FE端、Server端的排期怎么办呢?聪明的你立即把任务派发下去了,给每个端的负责人说,明天中午之前把排期汇总给我。  没错,在多样式列表的设计中也可以采用这种策略,给RecyclerView设置的Adapter不做具体的处理,而是由它派发出去。实现方案  1. addDelegate 向Adapter中注册委托Adapter;  2. addDataList 设置数据;  3. layout 渲染布局,Adapter查找到对应的委托Adapter,由委托Adapter去做具体渲染。如何使用引入compile 'com.kevin:delegationadapter:1.0.2' // 扩展库,使得databinding更简易,可以不引入 compile 'com.kevin:delegationadapter-extras:1.0.0'用法 1. 同一数据类型多种样式// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); // 设置Adapter delegationAdapter = new DelegationAdapter(); // 添加委托Adapter delegationAdapter.addDelegate(new OnePicAdapterDelegate()); delegationAdapter.addDelegate(new ThreePicAdapterDelegate()); delegationAdapter.addDelegate(new MorePicAdapterDelegate()); delegationAdapter.addDelegate(new VideoAdapterDelegate()); recyclerView.setAdapter(delegationAdapter);2. 不同数据类型多种样式// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); // 设置Adapter mDelegationAdapter = new DelegationAdapter(); mDelegationAdapter.addDelegate(new StringAdapterDelegate()); mDelegationAdapter.addDelegate(new IntegerAdapterDelegate()); mDelegationAdapter.addDelegate(new FloatAdapterDelegate()); mDelegationAdapter.addDelegate(new DoubleAdapterDelegate()); // 添加委托Adapter mRecyclerView.setAdapter(mDelegationAdapter); // 设置数据 List<Object> dataList = new ArrayList<>(); dataList.add("今天天气怎么样?");  // 添加一个String类型数据 dataList.add("大晴天,有点热。");  // 添加一个String类型数据 dataList.add("温度多少度呢?");    // 添加一个String类型数据 dataList.add(29);                // 添加一个int类型数据 dataList.add("具体是多少?");      // 添加一个String类型数据 dataList.add(29.5F);             // 添加一个Float类型数据 dataList.add(29.527921364978D);  // 添加一个Double类型数据 mDelegationAdapter.setDataItems(dataList); 3. 同一数据多种类型// 设置LayoutManager mBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this)); // 设置LayoutManager mDelegationAdapter = new DelegationAdapter(); // 添加委托Adapter mDelegationAdapter.addDelegate(new ServiceInfoAdapterDelegate()); mDelegationAdapter.addDelegate(new BillItemAdapterDelegate()); mDelegationAdapter.addDelegate(new ChargeInfoAdapterDelegate()); mBinding.recyclerView.setAdapter(mDelegationAdapter); // 设置数据 String newsListStr = LocalFileUtils.getStringFormAsset(this, "bill.json"); Bill bill = new Gson().fromJson(newsListStr, Bill.class); List<Object> dataList = new ArrayList<>(); dataList.add(new ItemData(bill, ServiceInfoDelegateAdapter.TAG)); dataList.addAll(bill.details); dataList.add(new ItemData(bill, ChargeInfoDelegateAdapter.TAG)); mDelegationAdapter.setDataItems(dataList);4. 添加头部// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); // 设置Adapter mDelegationAdapter = new DelegationAdapter(); // 添加委托Adapter mDelegationAdapter.addDelegate(new TextAdapterDelegate()); mDelegationAdapter.addDelegate(new BannerAdapterDelegate()); mRecyclerView.setAdapter(mDelegationAdapter); // 添加头部 mDelegationAdapter.addHeaderItem("这是添加的添加的头部数据"); 5. 添加尾部// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); // 设置Adapter mDelegationAdapter = new DelegationAdapter(); // 添加委托Adapter mDelegationAdapter.addDelegate(new TextAdapterDelegate()); mDelegationAdapter.addDelegate(new BannerAdapterDelegate()); mRecyclerView.setAdapter(mDelegationAdapter); // 添加尾部 mDelegationAdapter.addFotterItem("这是添加的添加的尾部数据"); 6. 带兜底的委托Adapter// 设置LayoutManager LinearLayoutManager layoutManager = new LinearLayoutManager(this); mRecyclerView.setLayoutManager(layoutManager); // 设置Adapter mDelegationAdapter = new DelegationAdapter(); // 添加委托Adapter mDelegationAdapter.addDelegate(new TextAdapterDelegate()); // 添加兜底的委托Adapter mDelegationAdapter.setFallbackDelegate(new FallbackAdapterDelegate()); mRecyclerView.setAdapter(mDelegationAdapter);THANKS TO 1. MultiItem 委托思想来源 2. AdapterDelegates 委托架子来源

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值