RecyclerView item多布局显示新姿势

问题

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中包含两种布局,一种是文字,一种是图片
这里写图片描述

完整代码下载地址

猜你喜欢

转载自blog.csdn.net/qq_30496695/article/details/80143495