问题
RecyclerView正在逐渐取代ListView,在使用RecyclerView中遇到了一个问题,需要在RecyclerView的固定位置设置不同布局的特殊Item,其中最大的问题在于不同类型data和View布局如何进行正确对应。
思路
- 将需要显示的不同类型数据都放到同一个容器中
- 显示时根据不同的类型加载不同的布局
实现
在使用RecyclerView时最重要的一个角色是Adapter,所以先从它入手,看下Adapter的代码
public class MyAdapter extends RecyclerView.Adapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
}
从代码中可以看出其中有两个比较重要比较重要的函数,onCreateViewHolder、onBindViewHolder两个函数,含义从字面可以理解其含义,主要作用是用来将View和ViewHolder以及ViewHolder和data进行绑定。
我们知道每个不同类型的data将需要不同类型ViewHolder进行绑定,不同类型的ViewHolder也对应着不同的View布局,所以对于不同data在显示过程中执行的onCreateViewHolder、onBindViewHolder方法内容都不同,我们可以抽象一个用于绑定的接口如下:
public interface ItemViewBinder<T, H extends RecyclerView.ViewHolder> {
H onCreateViewHolder(LayoutInflater inflater, ViewGroup parent);
void onBindViewHolder(H viewHolder, T t);
}
不同data可以有不同的实现,其中一个实现如下,其他类似:
public class NormalItemViewBinder implements ItemViewBinder<NormalItemBean, NormalItemViewBinder.NormalViewHolder> {
@Override
public NormalViewHolder onCreateViewHolder(LayoutInflater inflater, ViewGroup parent) {
return new NormalViewHolder(inflater.inflate(R.layout.recycle_item_normal, null));
}
@Override
public void onBindViewHolder(NormalViewHolder viewHolder, final NormalItemBean normalItemBean) {
viewHolder.tv.setText(normalItemBean.content);
}
static class NormalViewHolder extends BaseViewHolder {
TextView tv;
public NormalViewHolder(View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tv);
}
}
}
解决了不同类型的绑定问题,接下来需要面对的问题就是如何得到想要类型的ItemViewBinder实例。
- onCreateViewHolder(ViewGroup parent, int viewType)方法中可以通过viewType参数判断类型,构造一个viewType和ItemViewBinder的对应关系即可;
- 要使用viewType需要重写getItemViewType(int position)方法,因为参数只有position,在item的位置和显示样式没有固定对应关系的情况下,position和viewType是没办法直接产生联系的,通过position可以直接获得的一般是data,所以可以通过data的类型让position和viewType产生联系,即通过position可以知道该位置的data类型,然后每个类型都会对应着一个viewType
- onBindViewHolder(RecyclerView.ViewHolder holder, int position)方法中传入的参数除了ViewHolder以外只有一个position参数,同样position和ItemViewBinder在一般情况下也不会有直接对应关系,我们仍然可以通过data的类型让两者产生联系,即通过position可以得到data类型,每个data类型都会对应特定的ItemViewBinder
综合上面问题我们知道需要找到一个合适的数据结构让viewType、data类型分别与ItemViewBinder实例产生对应关系,可以想到如下数据结构:
- 用两个Map分别储存两两对应关系
- 利用ArrayMap集合
这里使用ArrayMap集合实现,主要利用ArrayMap的特性,可以通过index或者key查找value,我们viewType作为index,让Class类型作为key,ItemViewBinder实例作为value,实现代码如下:
public class TypeTool {
private static volatile TypeTool instance;
private ArrayMap<Class, ItemViewBinder> itemViewBinders;
@TargetApi(Build.VERSION_CODES.KITKAT)
private TypeTool() {
itemViewBinders = new ArrayMap<>();
}
public static TypeTool getInstance() {
if (instance == null) {
synchronized (TypeTool.class) {
if (instance == null) {
instance = new TypeTool();
}
}
}
return instance;
}
public void register(Class clazz, ItemViewBinder itemViewBinder) {
itemViewBinders.put(clazz, itemViewBinder);
}
public int indexOfClass(Class clazz) {
return itemViewBinders.indexOfKey(clazz);
}
public ItemViewBinder getItemViewBinderByIndex(int index) {
return itemViewBinders.valueAt(index);
}
public ItemViewBinder getItemViewBinderByClass(Class clazz) {
return itemViewBinders.get(clazz);
}
public void clear() {
itemViewBinders.clear();
}
}
解决了上面问题基本就完成了RecyclerView的Item多布局显示问题,下面是对ItemViewBinder的调用方法:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<Object> datas;
LayoutInflater inflater;
TypeTool typeTool;
public MyAdapter(Context context, List<Object> datas) {
inflater = LayoutInflater.from(context);
this.datas = datas;
typeTool = TypeTool.getInstance();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ItemViewBinder itemViewBinder = typeTool.getItemViewBinderByIndex(viewType);
return itemViewBinder.onCreateViewHolder(inflater, parent);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
ItemViewBinder itemViewBinder = typeTool.getItemViewBinderByClass(datas.get(position).getClass());
itemViewBinder.onBindViewHolder(holder, datas.get(position));
}
@Override
public int getItemCount() {
return datas.size();
}
@Override
public int getItemViewType(int position) {
return typeTool.indexOfClass(datas.get(position).getClass());
}
public void register(Class clazz, ItemViewBinder itemViewBinder) {
typeTool.register(clazz, itemViewBinder);
}
}
Activity中代码如下:
public class MainActivity extends AppCompatActivity {
RecyclerView rv;
MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rv = (RecyclerView) findViewById(R.id.rv);
adapter = new MyAdapter(this, getDatas());
rv.setAdapter(adapter);
rv.setLayoutManager(new LinearLayoutManager(this));
initData();
}
public void initData() {
adapter.register(NormalItemBean.class, new NormalItemViewBinder());
adapter.register(ADItemBean.class, new ADItemViewBinder());
}
public List<Object> getDatas() {
List<Object> datas = new ArrayList<>();
for (int i = 0; i < 30; i++) {
datas.add(new NormalItemBean("item:" + i));
}
datas.add(2, new ADItemBean(R.drawable.ad1));
datas.add(8, new ADItemBean(R.drawable.ad2));
return datas;
}
}
效果
如图,RecyclerView中包含两种布局,一种是文字,一种是图片