通用的recyclerview adapter 适配

[原文链接]
http://hannesdorfmann.com/android/adapter-delegates

统一管理适配Adapter多类型处理,更加方便的管理和操作。

joe 伟大的地狱逃脱者

让我来和你讲个故事,关于joe 的MyLittleZoo公司 。关于他是怎么创造可复用的不同类型的Recyclerview adapter来终结他的噩梦。和他最终是怎么用最小的代价管理和实现可复用adapters。
从前,有一个Android developer的小伙叫joe ,他在一个初创公司叫MyLittleZoo 的地方上班。这家公司是在网上销售宠物物品的。joe 的工作是负责维护一个和pc网页上相同功能的android 客户端。所以他90%的工作是需要早recycler view上展示一系列的item。在版本1.0 需要展示 accessories 类型的list。joe通过实现AccessoiresAdapter来展示accessories类型。但是列表的特殊展示需用使用item_accessory_offer.xml,而正常的展示是使用item_accessory.xml。所以这个adapter是有两种类型。adapter的一种类型是为不同的item填充不同的布局。本质上来说,一个view type 只有一个唯一的integer id。所以joe的 AccessoiresAdapter实现起来如下

public class AccessoiresAdapter extends RecyclerView.Adapter {

  final int VIEW_TYPE_ACCESSORY = 0;
  final int VIEW_TYPE_ACCESSORY_SPECIAL_OFFER = 1;

  List<Accessory> items;

@Override
public int getItemViewType(int position) {
   Accessory accessory = items.get(postion);
   if (accessory.hasSpecialOffer()){
       return VIEW_TYPE_ACCESSORY_SPECIAL_OFFER;
   } else {
       return VIEW_TYPE_ACCESSORY;
   }
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (VIEW_TYPE_ACCESSORY_SPECIAL_OFFER == viewType){
      return new SpecialOfferAccessoryViewHolder(inflater.inflate(R.layout.item_accessory_offer, parent));
    } else {
      return new AccessoryViewHolder (inflater.inflate(R.layout.item_accessory)):
    }
  }
}

到目前为止,MyLittleZoo 1.0版本在市场上进展的还不错。随着MyLittleZoo的用户量不断的增长,app也要进行不断的迭代。joe的新需求是在一个新的Activity去展示不同类型的item。NewsTeaser类型要和Accessories类型展示在一起。由于HomeAdapter需要展示Accessories,因此他决定通过继承的方式复用AccessoriesAdapter。

public class HomeAdapter extends AccessoriesAdapter {

  final int VIEW_TYP_NEWS_TEASER = 2;

  @Override
  public int getItemViewType(int position) {
   if (items.get(position) instanceof NewsTeaser){
       return VIEW_TYP_NEWS_TEASER;
     } else {
       // accessories and special offers
       return super.getItemViewType(position);
     }
   }

  @Override
  public RecyclerView.ViewHolder   onCreateViewHolder(ViewGroup parent, int viewType) {
    if (VIEW_TYP_NEWS_TEASER == viewType){
      return new NewsTeaserItem( inflater.inflate(R.layout.item_news_teaser, parent));
    } else {
      // accessories and special offers
      return super.onCreateViewHolder(parent, viewType);
    }
  }

  ...
}

包括一个新的Activity需要实现一些关于宠物食物的提示。因此joe需要实现PetFoodTipAdapter。

public class PetFoodTipAdapter extends RecyclerView.Adapter {

  final int VIEW_TYP_FOOD_TIP = 0;

  @Override 
  public int getItemViewType(int position) {
     return VIEW_TYP_FOOD_TIP;
  }

  @Override 
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return new PetFoodViewHolder(inflater.inflate(R.layout.item_pet_food, parent))
  }

  ...

}

他能够及时提交项目上线产品经理很开心。MyLittleZoo 2.0版本在市场上成功发布。
几个星期后,产品经理找到joe告诉他项目发展的没有预期的好。为了赚钱,公司决定和广告公司签订一份合同。广告公司可以在MyLittleZoo的app上展示一个banner。换句话说:他们出卖了他们的灵魂。joe 的工作是将广告sdk嵌入,并展示banner 在app里。随着时间的流逝,公司需要钱(通过广告运营商获取)。APP 版本的更新越快越好。因为广告banner需要和其他的item一起展示在recyclerview中,joe决定创建一个base adapter 基类的adapter.称之为AdvertismentAdapter:
public class AdvertismentAdapter extends RecyclerView.Adapter {

final int VIEW_TYP_ADVERTISEMENT = 0;

@Override
public int getItemViewType(int position) {
return VIEW_TYP_ADVERTISEMENT;
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new AdvertismentViewHolder(inflater.inflate(R.layout.item_advertisment, parent))
}

}

从那以后,所有的adapter都需要继承AdvertisementAdapter
AccessoiresAdapter extends AdvertisementAdapter
HomeAdapter extends AccessoiresAdapter extends AdvertisementAdapter
PetFoodTipAdapter extends AdvertisementAdapter

版本3.0 的在漫天的广告中终于在市场上发布。当然再一次产品经理对于joe的工作表示非常的赞赏。
半年后,产品经理再次来找joe的茬。世事万变啊,joe。我们惊奇的发现,MyLittleZoo 的Android用户不喜欢blinking blinking的广告,几乎亮瞎了用户的双眼。导致app在市场上获得大面积的负面回复。活跃用户戏剧性的掉了一大截,公司不能好好地赚钱了。但是MyLittleZoo 不能单纯的把广告从应用上移除,他们和魔鬼,额。。。就是他们的广告商签订了长期有效的广告合同。
然后销售部一个小伙有个很赞的主意,我们制定第二个app在recyclerview只展示 NewsTeaser 类型和 PetFoodTipl类型,不要亮瞎眼的广告,这个计划会重新获取用户的信任。此外,产品经理告诉joe这个版本必须要在两天时间内发布版本。因为这个周末是即将到来的这个是宠物博览会。到时候这个app一定要及时出现然后震惊全场。joe认为这个是可行的。他已经有了NewsTeaser 类型and PetFoodTip类型这些xml布局,而且adapter已经实现了。所以joe需要做的就是把这些共有的放置到公有的library里。实现MyLittleZoo和新的app能够共享使用
joe开始准备把方法移动到library,然而他意识到了他有一堆乱七八糟的事情要面对。还记得继承adapter的那一些类吗。
1.每个adapter都是继承 至AdvertisementAdapter,但是没有广告需要展示在新的app里。此外,提供广告展示的sdk是十分的冗余的,经常因为内存泄露导致crash。即使sdk没有广告展示也会经常在后台做一系列的操作。最重要的是,需要导入广告sdk 在新的app是没办法接受的
2.没有adapter可以复用展示NewsTeaser类型(HomeAdapter的部分)和PetFoodTip类型(PetFoodTipAdapter的部分)。joe这个时候要怎么做。他可以创建一个新的NewsTipAdapter继承自HomeAdapter 然后他需要添加PetFoodTip为新的type.但是这意味着他讲有两个adapter持有一样类型的petFoodTip的类型

欢迎来再次回到joe的adapter地狱

我的天啊,joe陷入异常的绝望,失落。绝望之外紧接而至的是恐慌。他应该怎么去修复这个bug,他需要怎么去修复才不会重蹈覆辙,可能该死的产品汪在一个月后又有新的特性新的类型需要展示。
joe紧忙开始把需求都写在白板上。但是脑海里没有一丝的思路。伤心难过的时候,他想起了小时候,当他还是一个孩子。童年的时候是过得多么的轻松。每天需要唯一需要做的事情就是在玩完乐高积木的时候清理房间。就这么简单。额。。。乐高。。等等,乐高。。。突然有一个绝妙的思路在脑海里闪现。我真正需要构建一个adapter就想搭乐高积木一样。先做一个空白的基础架构然后根据图片再把积木拼凑好。如果你需要一个窗口,那就拿窗户的模块拼凑上。如果需要一个屋顶,就拿符合屋顶的模块。如果屋子需要一个后花园,则拿一个乐高的花朵。我勒个去,脑海里满满的都是画面感。

组合优于继承
多少次他和其他开发者讨论的时候也是说“组合优于继承”。直到现在这也只是一个很好的slogan,他从来都没有根据这个准则构建。因此一个空白adapter是一个基础架构。viewtype是可复用的部件。
因此joe开始定义可复用的乐高模块,像NewsTeaserAdapterDelegate和PetFoodTipAdapterDelegate

public class NewsTeaserAdapterDelegate {

  private int viewType;

  public NewsTeaserAdapterDelegate(int viewType){
    this.viewType = viewType;
  }

  public int getViewType(){
    return viewType;
  }

  public boolean isForViewType(List items, int position) {
    return  items.get(position) instanceof NewsTeaser;
  }

  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
    return new NewsTeaserViewHolder(inflater.inflate(R.layout.item_news_teaser, parent, false));
  }

  public void onBindViewHolder(List items, int position, RecyclerView.ViewHolder holder) {
      NewsTeaser teaser = (NewsTeaser) items.get(position);
      NewsTeaserViewHolder vh = (NewsTeaserViewHolder) vh;

      vh.title.setText(teaser.getTitle());
      vh.text.setText(teaser.getText());
  }
}

public class PetFoodTipAdapterDelegate {

  private int viewType;

  public PetFoodTipAdapterDelegate(int viewType){
    this.viewType = viewType;
  }

  public int getViewType(){
    return viewType;
  }

  public boolean isForViewType(List items, int position{
    return  items.get(position) instanceof PetFoodTip;
  }

  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
    return new     PetFoodTipViewHolder(inflater.inflate(R.layout.item_pet_food, parent, false));
  }

  public void onBindViewHolder(List items, int position, RecyclerView.ViewHolder holder) {
      PetFoodTip tip = (PetFoodTip) items.get(position);
      PetFoodTipViewHolder vh = (PetFoodTipViewHolder) vh;

      vh.image.setImageRes(tip.getImage());
      vh.text.setText(tip.getText());
  }
}

然后他拿着这个基础架构,一个空的adapter.然后将乐高的模块放到新的app的NewsTipAdapter

public class NewsTipAdapter extends RecyclerView.Adapter{

  final int VIEW_TYP_NEWS_TEASER = 0;
  final int VIEW_TYP_FOOD_TIP = 1;

  NewsTeaserAdapterDelegate newsTeaserDelegate;
  PetFoodTipAdapterDelegate foodTipDelegate;

  List items;

  public NewsTipAdapter(){
    newsTeaserDelegate = new NewsTeaserAdapterDelegate(VIEW_TYP_NEWS_TEASER);
    foodTipDelegate = new PetFoodTipAdapterDelegate(VIEW_TYP_FOOD_TIP);
  }

  @Override
  public int getItemViewType(int position) {
     if (newsTeaserDelegate.isForViewType(items, position)){
       return newsTeaserDelegate.getViewType();
     }
     else if (foodTipDelegate.isForViewType(items, position)){
       return foodTipDelegate.getViewType();
     }

     throw new IllegalArgumentException("No delegate found");
  }

  @Override
  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    if (newsTeaserDelegate.getViewType() == viewType){
      return newsTeaserDelegate.onCreateViewHolder(parent);
    }
    else if (foodTipDelegate.getViewType() == viewType){
      return foodTipDelegate.onCreateViewHolder(parent);
    }

    throw new IllegalArgumentException("No delegate found");
  }


  @Override
  public void onBindViewHolder(VH holder, int position){
    int viewType = holder.getViewType();
    if (newsTeaserDelegate.getViewType() == viewType){
      newsTeaserDelegate.onBindViewHolder(items, position, holder);
    }
    else if (foodTipDelegate.getViewType == viewType){
      foodTipDelegate.onBindViewHolder(items, position, holder);
    }
  }
}

我猜你已经get到了。代替继承,joe已经定义了delegate给一系列的view类型。每个delegate的职责是创建和绑定view holder。正如上面你所看到的代码片段。有一系列的样板代码需要填写。joe发现了一个更好的方法解决问题。

/**
 * @param <T> the type of adapters data source i.e. List<Accessory>
 */
public interface AdapterDelegate<T> {

  /**
   * Called to determine whether this AdapterDelegate is the responsible for the given data
   * element.
   *
   * @param items The data source of the Adapter
   * @param position The position in the datasource
   * @return true, if this item is responsible,  otherwise false
   */
  public boolean isForViewType(@NonNull T items, int position);

  /**
   * Creates the  {@link RecyclerView.ViewHolder} for the given data source item
   *
   * @param parent The ViewGroup parent of the given datasource
   * @return The new instantiated {@link RecyclerView.ViewHolder}
   */
  @NonNull public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);

  /**
   * Called to bind the {@link RecyclerView.ViewHolder} to the item of the datas source set
   *
   * @param items The data source
   * @param position The position in the datasource
   * @param holder The {@link RecyclerView.ViewHolder} to bind
   */
  public void onBindViewHolder(@NonNull T items, int position, @NonNull RecyclerView.ViewHolder holder);
}


public class AdapterDelegatesManager<T> {

  public AdapterDelegatesManager<T> addDelegate(@NonNull AdapterDelegate<T> delegate) {
    ...
  }

  public int getItemViewType(@NonNull T items, int position) {
    ...
  }

  public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    ...
  }

  public void onBindViewHolder(@NonNull T items, int position, @NonNull RecyclerView.ViewHolder viewHolder) {
    ...
  }
}

这个想法是注册一个AdapterDelegates给AdapterDelegatesManager管理。AdapterDelegatesManager通过一系列的逻辑处理给出符合view type 的正确AdapterDelegate。所以综上所述,NewsTipAdapter 的代码如下

public class NewsTipAdapter extends RecyclerView.Adapter{

  final int VIEW_TYP_NEWS_TEASER = 0;
  final int VIEW_TYP_FOOD_TIP = 1;

  List items;

  AdapterDelegatesManager delegates = new AdapterDelegatesManager();

  public NewsTipAdapter(){
    delegates.add(new NewsTeaserAdapterDelegate()); // Assigns internally ViewType integer
    delegates.add(new PetFoodTipAdapterDelegate());
  }

  @Override public int getItemViewType(int position) {
     return delegates.getItemViewType(items, position);
  }

  @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return delegates.onCreateViewHolder(parent, viewType);
  }

  @Override public void onBindViewHolder(VH holder, int position){
      delegates.onBindViewHolder(items, position, holder);
  }
}

我猜你已经能够想象出MyLittle app其他的adapter是怎么展现。这里有AdvertisementAdapterDelegate,NewsTeaserAdapterDelegate,PetFoodTipAdapterDelegate和AccessoryAdapterDelegate。从现在开始,多个adapter可以通过非常重要的view type (AdapterDelegates) 类型组合起来。其他的优点是,可以从一个庞大的adapter的逻辑里抽取出,填充布局,构造view holder 和绑定view holder等逻辑让代码分离开模块话,可复用的adapterDelegate。你有意识到,adapter代码里非常的清净,还有你特别关心的,可以让代码可扩展性更强和更高的解耦。另外一个更好的是可以很多team成员同时在同一个adapter上进行代码操作不用考虑复杂的merge.因为没有人会触碰到adapter复杂的代码。team成员可以更加专注的写每个类型的AdapterDelegate。
joe很开心,产品经理和开心,用户很开心,大家都很开心。joe 太开心了,然后决定把AdapterDelegates放到自己的额库里准备开源。结局总算是美好的。

你可以在github上找到AdapterDelegates这个项目,同时在maven中也是可以找到的
https://github.com/sockeqwe/AdapterDelegates

ps. 这个库也提供ListDelegationAdapter 的基类,已经把RecyclerView.Adapter的方法和AdapterDelegatesManager的方法放到一起,这样你可以减少大量重读代码的工作

public class NewsTipAdapter extends ListDelegationAdapter {

  public NewsTipAdapter(){
    // delegatesManager is a field defined in super class
    // ViewType integer is assigned internally by delegatesManager
    delegatesManager.add(new NewsTeaserAdapterDelegate());
    delegatesManager.add(new PetFoodTipAdapterDelegate());
  }

}

最后:joe 和MyLittlezoo 都不是真实的。都是作者的想想。但是文章的代码片段可以不编写。这个是java里伪代码,是帮助你理解这个思想的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RecyclerViewAdapter是一个用于加载RecyclerView子项的布局并返回ViewHolder对象的适配器。它继承自RecyclerView.Adapter类,并实现了onBindViewHolder(ViewHolder viewHolder, int i)方法,该方法用于绑定数据到ViewHolder上。\[1\] 在RecyclerViewAdapter中,当适配器的数据发生变化时,可以通过观察者模式来通知观察者。观察者是一个继承自抽象类AdapterDataObserver的类,它可以观察适配器的变化并做出相应的对策。为了实现观察者模式,RecyclerViewAdapter使用了AdapterDataObserverable来通知观察者适配器的变化。\[2\] 观察者AdapterDataObserver中定义了多个方法,这些方法对应了Adapter中的notify相关方法。当适配器调用notify相关方法时,会触发相应的观察者方法。通过这种方式,观察者可以及时响应适配器的变化并进行相应的处理。\[3\] #### 引用[.reference_title] - *1* [androidRecyclerView.Adapter介绍](https://blog.csdn.net/u012739527/article/details/124011765)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [【RecyclerViewRecyclerViewAdapter](https://blog.csdn.net/qq_29266921/article/details/80630432)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值