引言

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

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

必备知识

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

访问者模式

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

recyclerview获取item 控件 recyclerview多种item_访问者

UML解读

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

工厂方法模式

  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 间就不会相互影响,变得易维护和易扩展,相信你学会之后,一定会爱上此法。

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