巧用设计模式实现Recyclerview各种复杂Item类型

版权声明:本文为博主原创文章,未经博主允许不得转载

引言

在实际项目的开发中,首页的布局基本上都是复杂的 UI,而我们的实现思路一般就是利用 RecyclerView 结合 getItemType(),并在适配器里根据不同的 item 类型去创建不同的 ViewHolder,最后在 onBindViewHolder() 中依然是根据 item 类型来绑定对应的数据。这种方法是最基本的方法,相信大家都懂。但是,其缺点也很明显,就是可扩展性太差。

接下来,我将介绍另一种更为巧妙的方法来实现,以期大大提高其扩展性。我们将以 item 的布局 id 作为区别item的唯一标志并结合两种设计模式,为大家呈现一种新颖,简洁的方式来实现此类复杂布局。其中如有纰漏,望请悉心指出。

必备知识

本实现方法主要用到了 Java 设计模式中的访问者模式和工厂方法模式,亦涉及到 ViewHolder 的封装技巧。

访问者模式

  1. 概念
    Java 中的访问者模式属于一种行为型设计模式,核心主要由访问者与被访问者两部分组成。一般对于同一场景来说,被访问者都是由不同类的类型所表示,而不同的访问者可以对被访问者进行不同的访问操作。其中,被访问者常利用集合结构来存储(比如 List ),访问者通过遍历集合实现对其中存储的元素的逐个操作。

  2. UML类图

访问者模式UML类图
访问者模式UML类图

出处:维基百科

UML解读

UML类图中有两个类:访问者(Visitor)和被访问者(Element),然后有多个具体访问者继承访问者 Visitor(eg: ConcreateVisitor1 ),也有多个具体被访问者继承被访问者 Element(eg: ConcreateElementA ) 。首先,Visitor 中为每个具体被访问者定义了一个可访问具体被访问者操作的方法(通过注入具体被访问者的引用);其次,Element 中定义了一个接受访问的方法 accept,并且依赖注入访问者 visitor,以便访问者可以访问被访问者;然后,Object Structure 对象结构主要用于存储被访问者。因此,对于每个被访问者都应先从该对象结构中取出来。最后,客户端 Client 定义集合对象收集被访问者数据,通过对集合的遍历完成访问者对每一个被访问元素的访问操作。

具体例子详见推荐博客 Java设计模式之 访问者模式【Visitor Pattern】

工厂方法模式

  1. 概念

    工厂方法模式是一种实现了“工厂”概念的面向对象设计模式 ,是处理在不指定对象具体类型的情况下创建对象的问题。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。”

  2. UML类图

    工厂模式
    工厂模式

出处:维基百科

UML解读

首先在创建器 Creator 中定义一个工厂方法用于生产未指定具体类型的产品,其次,子类--具体创建器 ConcreteCreator 实现父类工厂方法,给出创建具体产品类型的实现,最后,客户端只需调用具体创建者中的方法即可得到所需的产品。

具体例子详见推荐博客工厂方法模式

BaseViewHolder的封装

/**
 * 封装的 viewholder 用于获取各个 item 上的控件 采用集合存储取过的控件
 */

public  class BaseViewHolder extends RecyclerView.ViewHolder {
protected View itemView;//每个item的布局视图view
protected SparseArray<View> list;//itemView上所有控件的集合

public BaseViewHolder(View itemView) {
    super(itemView);
    this.itemView = itemView;
    list=new SparseArray<>();
}
/**
*获取 itemView 上控件
*/
 public <T> T getView(int id) {
    View view = list.get(id);
    if (view == null) {
        view = itemView.findViewById(id);
        list.put(id, view);
    }
    return (T) view;
}

BaseViewHolder 继承至 RecyclerView.ViewHolder ,可作为 Recyclerview 适配器中通用的 ViewHolder 来使用,因此你无需在每个适配器里面再定义内部类 MyViewHolderBaseViewHolder 可以在构造器中获取到每个 itemitemView,然后就可以定义一个快速获取 itemView 上各个子控件的方法 getView,以后在适配器中获取 item 子控件能够随时调用此方法.

BetterViewHolder

public abstract class BetterViewHolder<T> extends BaseViewHolder {
public BetterViewHolder(View itemView) {
    super(itemView);
}

/**
 * 绑定 item 的数据
 * @param t 每个item的实体引用
 */
public abstract void bindDataToItem(T t,int position);
}  

BetterViewHolder 声明为抽象类型,在 BaseViewHolder 基础上再次封装了一层。主要定义了一个抽象方法用于实现适配器中 onBindViewHolder(T t,int position) 的功能。但由于子类的实体类型不可确定,故需要借助泛型技巧,定义T来表示子类的泛型。因此,子类只需继承该类,并指定子类所需的实体类型即可。

实例解析

  • 本例中定义了四种 item 布局类型,如下图所示

布局代码见底部源码

  • 定义访问者 TypeFactory
    public abstract class TypeFactory {  

    public abstract int type(Banner banner);

    public abstract int type(Category category);

    public abstract int type(Item item);

    public abstract int type(Footer footer);
    //工厂方法模式应用 
    public  abstract BetterViewHolder onCreateViewHolder(View itemView,int  type);
    }
  • 定义被访问者 Visitable
    /*
    *定义抽象的被访问者 type方法,用来接收/引用一个抽象访问者对象,以便利用这个对象进行操作;
    */
    public abstract class Visitable {
    public abstract int type(TypeFactory factory);
    }  
  • 定义四个实体类并继承Visitable

      /**
       * Created by hrx on 2017/4/30.
       * 具体的被访问者
       * 实现type抽象方法,通过传入的具体访问者参数、
       * 调用具体访问者对该对象的访问操作方法实现访问逻辑;
       * 比如这就是利用抽象访问者 TypeFactory 引用来调用操作方法获取对应该 Banner 关联的布局  id
       */
    
      public class Banner extends Visitable{
        @Override
        public int type(TypeFactory factory) {
           return factory.type(this);
        }
       }  
    
      public class Category extends Visitable {
        @Override
        public int type(TypeFactory factory) {
           return factory.type(this);
        }
      }  
      public class Item extends Visitable {
          private int position;
          @Override
          public int type(TypeFactory factory) {
              return factory.type(this);
          }
      
          public int getPosition() {
              return position;
          }
      
          public void setPosition(int position) {
              this.position = position;
          }
      }
      public class Footer extends Visitable {
        @Override
         public int type(TypeFactory factory) {
            return factory.type(this);
         }
       }  
    
  • 具体访问者 TypeFactoryList

      /*
      * 具体的访问者实现了抽象访问者的方法
      * 同时 onCreateViewHolder 也是利用工厂方法模式创建了各个 item 的 viewholder 实例
      */
    
      public class TypeFactoryList extends TypeFactory {
      //声明每个item的布局id
      public static final int BANNER = R.layout.banner;
      public static final int CATEGORY = R.layout.category;
      public static final int ITEM = R.layout.item;
      public static final int FOOTER = R.layout.footer;
    
      @Override
      public int type(Banner banner) {
          return BANNER;
      }
    
      @Override
      public int type(Category category) {
          return CATEGORY;
      }
    
      @Override
      public int type(Item item) {
          return ITEM;
      }
    
      @Override
      public int type(Footer footer) {
          return FOOTER;
      }
    
      @Override
      public BetterViewHolder onCreateViewHolder(View itemView, int type) {
          BetterViewHolder viewHolder = null;
          switch (type) {
              case BANNER:
                  viewHolder = new BannerViewHolder(itemView);
                  break;
              case CATEGORY:
                  viewHolder = new CategoryViewHolder(itemView);
                  break;
              case ITEM:
                  viewHolder = new ItemViewHolder(itemView);
                  break;
              case FOOTER:
                  viewHolder = new FooterViewHolder(itemView);
                  break;
              default:
                  break;
          }
          return viewHolder;
       }
      }  
    
  • 定义适配器

      public class MainActivityAdapter extends RecyclerView.Adapter<BetterViewHolder> {
      private List<Visitable> mVisitables;
      private TypeFactory factory;
    
      public MainActivityAdapter(List<Visitable> mVisitables) {
          this.mVisitables = mVisitables;
          factory = new TypeFactoryList();
      }
      //此 ViewHolder 的创建细节已经抽象到 TypeFactoryList 中去实现了 此处等同与获取工厂生产的产品
      @Override
      public BetterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
          View itemView = View.inflate(parent.getContext(), viewType, null);
          return factory.onCreateViewHolder(itemView, viewType);
      }
    
      //此处的实现交由 BetterViewHolder 的各个子类去实现,故此处 Java 会根据相应的子类去获取其下实现的 bindDataToItem(),利用JAVA动态分派而无需进行类型检查
      @Override
      public void onBindViewHolder(BetterViewHolder holder, int position) {
          holder.bindDataToItem(mVisitables.get(position),position);
      }
    
      //此处即代表访问者模式中的客户端调用被访问者的 type(),进行访问操作获取其布局 id
      @Override
      public int getItemViewType(int position) {
          return mVisitables.get(position).type(factory);
      }
      @Override
      public int getItemCount() {
          return mVisitables.size();
      }
      }  
    

该适配器的数据就是得到访问者模式中的对象结构 Object Structure 中存储的被访问者集合,对应到例子中就是 mVisitables 集合。同时需要一个访问者引用,以便该适配器(客户端)能够利用这个引用获取每个被访问者关联到的布局Id(利用该引用执行某些操作),如mVisitables.ge(position).type(factory)可以获取到每个被访问者关联到的布局 id

  • 四个 BetterViewHolder 的子类

    四个子类代表了其对应 itemViewHolder ,可以在该类上实现 item 上的操作,比如点击事件,设置 item 上子控件的所有数据等。

代码见底部源码

  • Activity 收集数据并设置适配器
    public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private List<Visitable> mVisitable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        //默认4列
        final GridLayoutManager manager = new GridLayoutManager(this, 4);
        //此方法定义每个item占几列,有点类似线性布局的权重属性
        manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                if (position > 2 && position < 7) {
                    return 1;
                }
                return 4;
            }
        });
        recyclerView.setLayoutManager(manager);
        initData();
        recyclerView.setAdapter(new MainActivityAdapter(mVisitable));

    }

    private void initData() {
        mVisitable = new ArrayList<>();
        //按布局的顺序依次加入各个被访问者
        Banner banner = new Banner();
        mVisitable.add(banner);
        Category category = new Category();
        mVisitable.add(category);
        //加入4个item
        for (int i = 0; i < 5; i++) {
            Item item = new Item();
            item.setPosition(i + 2);
            mVisitable.add(item);
        }
        Footer footer = new Footer();
        mVisitable.add(footer);
    }
}

注:mVisitable 集合中被访问者的加入顺序即代表了最终显示出来的顺序,并且集合中的每个元素仅能代表其中一个 item,意味着如果你要重复该 item 就要重复声明一个实体再加入集合中。

总结

利用访问者模式和工厂方法模式大大解耦了上述复杂布局的实现过程,同时可扩展性大大提高。如果往后还需修改布局,只需修改对应 item 的布局文件和数据的绑定。而若是增加 item,那么只需定义新的实体加入被访问者集合中,同时编写布局文件及对应的 ViewHolder 实现即可。这样一来,不同 item 间就不会相互影响,变得易维护和易扩展,相信你学会之后,一定会爱上此法。

最后,谢谢你看到这里,欢迎交流意见。

源码地址

感谢

[译]关于 Android Adapter,你的实现方式可能一直都有问题
Java设计模式之 访问者模式【Visitor Pattern】
Java设计模式之 工厂方法模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 写在前面:参考YoKey,感谢。附上他的链接:http://www.jianshu.com/p/d30fd8da4...
    喜欢丶下雨天阅读 3,215评论 0 9
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 对象的创建与销毁 Item 1: 使用static工厂方法,而不是构造函数创建对象:仅仅是创建对象的方法,并非Fa...
    孙小磊阅读 1,969评论 0 3
  • 将所有的月光聚集 浓缩成一颗明亮的心 在你的窗前 静静的看着你,看着你 将所有的河流流淌成澎湃的心潮 那是我爱你的...
    晓宇启航阅读 421评论 2 5