Hello! 大家好,我是蜗牛君~ 我们又见面了,本篇文章是蜗牛君漫聊动态布局框架的第二篇。上一篇中我们讲解了框架的大致思路,以及复习了一下RecyclerView的基础使用方式。那么本篇文章我们就正式开始框架的搭建了。
首先我们要做一件事情,就是捋清楚整个框架的搭建思路。
思路讲解
1、思路的起点:getItemViewType(int position)方法
只要是有Android原生开发经验的朋友都知道在RecyclerView出现之前,我们一直熟练使用的是ListView,为了解决ListView的一些不理想之处,所以出现了RecyclerView,但是ListView的大部分功能RecyclerView还是继承了。蜗牛君在很久之前使用ListView做开发时就遇到过一个需求,在新闻列表中要穿插广告,广告的布局样式和新闻布局完全不同,这个需求很常见,所以蜗牛君在网上很轻易的就查到了大量的文章,这些文章的内容大同小异,他们都有一个共同点,那就是使用Adapter的getItemViewType(int position)方法。
于是蜗牛君就在网上查了一下这个方法的用途。大概意思就是说在Adapter创建ViewHolder之前,会先通过getItemViewType方法判断数据的类型,可以根据getItemViewType方法不同的返回值,确定加载不同的ViewHolder。因此,getItemViewType方法就是思路的起点。
那我们在getItemViewType方法中做什么呢?由于getItemViewType方法的返回值是int类型,因此这个int类型的值要能够确定ViewHolder的具体类型。
2、数据类型和ViewHolder类型对应关系的存储
我们框架的原理就是根据不同类型的数据选择不同类型的ViewHolder,因此这份一一对应的关系如何存储至关重要。
我们的思路是使用HashMap集合,数据类型的Class类为Key值,ViewHolder类的Class类为Value值,这样就把一一对应的关系存储起来了。
既然我们存储的是Class类,因此在实例化的时候就需要用到Java的反射机制了,这也就是动态的体现。
3、数据和ViewHolder对应关系的查询
在HashMap集合中,Key值是数据类型的Class类,我们接下来要思考的问题是如何确定Key值。上文提到过,getItemViewType方法返回的数据类型是int类型,而我们HashMap中存储的Key值是Class类型,这完全不匹配啊!我们的策略是中间增加一层,建立Key值的索引目录。
具体来讲就是,我们在初始化时,要进行一步存储操作,这步操作包含两项:第一项就是将数据类型的Class类依次存入ArrayList集合;第二项是依次将数据与ViewHolder的对应关系存入HashMap;有了存储所有数据类型Class类的ArrayList集合,我们就可以确定当前Item的数据类型是否存在,存在的话又是什么。确定了数据的Class类,就能从HashMap中获取到对应ViewHolder的Class类。
4、程序的设计
对于ViewHolder的创建,我们使用工厂模式,不同类型的ViewHolder是我们要生产的产品,我们还需要一个生产ViewHolder的工厂,对于工厂模式的基本结构这里不再赘述。
对外提供使用方法的时候,使用建造者模式,以此达到链式调用的目的。
代码实现:框架的核心功能—工厂模式
1、工厂模式中的抽象产品类:BaseViewHolder
需要做到以下几点:
- 继承官方RecyclerView.ViewHolder类;
- 以泛型的方式传递要绑定的数据类型;
- 重写构造方法;
- 重载构造方法;
- 填充Item布局文件(以参数形式);
- 绑定数据到Item布局(以参数形式);
/**
* 工厂模式中---抽象产品类
*
* 职责:
* 1、填充Item布局文件;
* 2、绑定数据到Item布局
*
* @param <V> 需要绑定的数据类型
*/
public abstract class BaseViewHolder<V> extends RecyclerView.ViewHolder {
/**
* 自定义构造方法
*
* 无论是ListView还是RecyclerView,我们都是使用LayoutInflater的方式填充Item的布局
* 因此这里要传递的参数是Context、ViewGroup、LayoutId
*
* @param context
* @param parent
* @param layoutId
*/
public BaseViewHolder(Context context, ViewGroup parent, int layoutId) {
this(LayoutInflater.from(context).inflate(layoutId, parent, false));
}
public BaseViewHolder(@NonNull View itemView) {
super(itemView);
// 使用ButterKnife绑定布局文件
ButterKnife.bind(this,itemView);
}
/**
* ViewHolder与数据进行绑定
*
* @param position
* @param value
* @param adapter
*/
public abstract void bindTo(int position, V value, RecyclerAdapter adapter);
}
注意:
- 我们使用了ButterKnife进行布局关联;
- 抽象方法bindTo中的参数定义,value是必须的,这是我们进行绑定的具体数据,其他参数可根据业务需要进行修改,具体实现由子类完成;
2、工厂模式中的具体产品类—CommonViewHolder
需要做到一下几点:
- 传递具体ViewHolder所要绑定的数据类型给父类;
- 重载父类的构造方法,确定了父类构造方法中三个参数里面的layoutId的值;
- 进行真正的数据与布局的绑定工作;
/**
* ViewHolder具体实现类
*/
public class CommonViewHolder extends BaseViewHolder<CommonModel> {
@BindView(R.id.txt_content)
TextView mTxtContent;
/**
* 重载构造方法,确定LayoutId参数值
*
* @param context
* @param parent
*/
public CommonViewHolder(Context context, ViewGroup parent) {
super(LayoutInflater.from(context).inflate(R.layout.recycler_view_item_common, parent, false));
}
@Override
public void bindTo(int position, CommonModel value, RecyclerAdapter adapter) {
mTxtContent.setText(value.content);
}
}
recycler_view_item_common.xml(使用ConstraintLayout布局)
<TextView
android:id="@+id/txt_content"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.097"
tools:ignore="MissingConstraints" />
3、工厂模式中的具体工厂类—ViewHolderFactory
因为我们模块只需要一个工厂类,因此不必设置抽象工厂类。
需要做到以下几点:
- 数据类型与ViewHolder对应关系的存储策略:
创建List类型的索引集合,用来存储所有数据的具体类型;创建Map类型的ViewHolder集合,Key值为数据的具体类型,Value值为ViewHolder的具体类型。 - 动态创建ViewHolder的核心技术:Java的反射机制。
- 封装对外提供的基本方法。
/**
* ViewHolder工厂类
*/
public class ViewHolderFactory {
private Context mContext;
// 数据类型的Class类作为索引被存储在集合中
private List<Class> valueClassType = new ArrayList<>();
// 数据类型的Class类为Key值,需要绑定的ViewHolder类型的Class类为Value值
private Map<Class, Class<? extends BaseViewHolder>> boundViewHolder = new HashMap<>();
/**
* 构造方法,需要传递上下文
*
* @param context
*/
public ViewHolderFactory(Context context) {
mContext = context;
}
/**
* 创建ViewHolder
*
* @param viewType Map中的Key值所在List集合中的下标
* @param parent
* @return
*/
public BaseViewHolder create(int viewType, ViewGroup parent) {
Class valueClass = valueClassType.get(viewType);
try {
Class<? extends BaseViewHolder> viewHolderClass = boundViewHolder.get(valueClass);
Constructor<? extends BaseViewHolder> constructor = viewHolderClass.getDeclaredConstructor(Context.class, ViewGroup.class);
return constructor.newInstance(mContext, parent);
} catch (Exception e) {
throw new RuntimeException("Unable to create ViewHolder for " + valueClass + "."
+ e.getCause().getMessage(), e);
}
}
/**
* 存储ViewHolder索引,存储ViewHolder
*
* @param valueClass
* @param viewHolder
*/
public void bind(Class valueClass, Class<? extends BaseViewHolder> viewHolder) {
// 存储索引值
valueClassType.add(valueClass);
// 存储ViewHolder
boundViewHolder.put(valueClass, viewHolder);
}
/**
* 查询指定数据类型在索引集合中的下标,有下标就能在Map中找到对应的ViewHolder类型
*
* @param object
* @return
*/
public int itemViewType(Object object) {
return valueClassType.indexOf(object.getClass());
}
/**
* 获取索引集合
*
* @return
*/
public List<Class> getValueClassType() {
return valueClassType;
}
/**
* 获取ViewHolder集合
*
* @return
*/
public Map<Class, Class<? extends BaseViewHolder>> getBoundViewHolder() {
return boundViewHolder;
}
}
以上就是框架核心功能工厂模式的全部代码了,由于篇幅原因,本篇文章先介绍到这里。上述内容有一定的难度,首先第一步要做到的就是缕清思路,理解制定的方案与意图。第二步就是一些基础知识的获取,工厂模式的相关知识,Java反射机制的相关知识等等。好了,蜗牛君想说的就这么多,我们下篇文章见。