前情提要

最近项目我在项目中使用了RecyclerView代替了ListView.由于项目中有多出列表项使用RecyclerView,这就导致需要写多个AdapterViewHolder.

其实,怎么说呢?就是懒,想少写代码,所以想研究一下能否简化一下.

具体实现

封装分为AdapterViewHolder两部分,如下所示.

ViewHolder

抽象类BaseHolder继承RecyclerView.ViewHolder,并依赖注入的数据类型M,即和ViewHolder绑定的数据类型为M.

该抽象类包含一个构造方法,用于获取item对应的布局.一个抽象函数用于将数据设置到item上面.

/**
 * 基础的ViewHolder
 * Created by zyz on 2016/5/17.
 */
public abstract class BaseHolder<M> extends RecyclerView.ViewHolder {

    public BaseHolder(ViewGroup parent, @LayoutRes int resId) {
        super(LayoutInflater.from(parent.getContext()).inflate(resId, parent, false));
    }

    /**
     * 获取布局中的View
     * @param viewId view的Id
     * @param <T> View的类型
     * @return view
     */
    protected <T extends View>T getView(@IdRes int viewId){
        return (T) (itemView.findViewById(viewId));
    }

    /**
     * 获取Context实例
     * @return context
     */
    protected Context getContext() {
        return itemView.getContext();
    }

    /**
     * 设置数据
     * @param data 要显示的数据对象
     */
    public abstract void setData(M data);
}

Adapter

Adapter类也为抽象类,继承于RecyclerView.Adapter,并绑定了两个泛型:

  1. M : 用于该 Adapter 的列表的数据类型,即List<M>.
  2. H : 即和 Adapter 绑定的 Holder 的类型.

并且,该 Adapter 自带 List 数据集合,声明时可以不用传递数据集合.也包含了 List 的相关操作.同时还给该 Adapter 绑定了一个 item 的点击事件,且为可选操作,不需要点击操作,直接传null即可.

/**
 * 基础的Adapter
 * Created by zyz on 2016/5/17.
 */
public abstract class BaseAdapter<M, H extends BaseHolder<M>> extends RecyclerView.Adapter<H> {

    protected List<M> dataList;
    protected OnItemClickListener<H> listener;

    /**
     * 设置数据,并设置点击回调接口
     *
     * @param list 数据集合
     * @param listener 回调接口
     */
    public BaseAdapter(@Nullable List<M> list, @Nullable OnItemClickListener<H> listener) {
        this.dataList = list;
        if (this.dataList == null) {
            this.dataList = new ArrayList<>();
        }

        this.listener = listener;
    }

    @Override
    public void onBindViewHolder(final H holder, int position) {
        holder.setData(dataList.get(position));
        if (listener != null) {
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    listener.onItemClick(holder);
                }
            });
        }
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    /**
     * 填充数据,此方法会清空以前的数据
     *
     * @param list 需要显示的数据
     */
    public void fillList(List<M> list) {
        dataList.clear();
        dataList.addAll(list);
    }

    /**
     * 更新数据
     *
     * @param holder item对应的holder
     * @param data   item的数据
     */
    public void updateItem(H holder, M data) {
        dataList.set(holder.getLayoutPosition(), data);
    }

    /**
     * 获取一条数据
     *
     * @param holder item对应的holder
     * @return 该item对应的数据
     */
    public M getItem(H holder) {
        return dataList.get(holder.getLayoutPosition());
    }

    /**
     * 获取一条数据
     *
     * @param position item的位置
     * @return item对应的数据
     */
    public M getItem(int position) {
        return dataList.get(position);
    }

    /**
     * 追加一条数据
     *
     * @param data 追加的数据
     */
    public void appendItem(M data) {
        dataList.add(data);
    }

    /**
     * 追加一个集合数据
     *
     * @param list 要追加的数据集合
     */
    public void appendList(List<M> list) {
        dataList.addAll(list);
    }

    /**
     * 在最顶部前置数据
     *
     * @param data 要前置的数据
     */
    public void preposeItem(M data) {
        dataList.add(0, data);
    }

    /**
     * 在顶部前置数据集合
     *
     * @param list 要前置的数据集合
     */
    public void preposeList(List<M> list) {
        dataList.addAll(0, list);
    }
}

使用范例

使用范例为一种Item和多种Item这两种类型.

一种Item

运行结果如下图所示:

单个Item类型的ViewHolder如下:

/**
 * 一种View的Holder
 * Created by zyz on 2016/5/17.
 */
public class SingleHolder extends BaseHolder<Person> {

    TextView nameView;
    TextView ageView;

    public SingleHolder(ViewGroup parent, @LayoutRes int resId) {
        super(parent, resId);

        nameView = getView(R.id.name_tv);
        ageView = getView(R.id.age_tv);
    }

    @Override
    public void setData(Person data) {
        nameView.setText(data.getName());
        ageView.setText(String.valueOf(data.getAge()));
    }
}

与之对应的Adapter如下:

/**
 * 一种item的Adapter
 * Created by zyz on 2016/5/17.
 */
public class SingleAdapter extends BaseAdapter<Person, SingleHolder> {

    public SingleAdapter(SingleItemClickListener listener) {
        super(null, listener);
    }

    @Override
    public SingleHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new SingleHolder(parent, R.layout.item_single);
    }

    @Override
    public void onBindViewHolder(final SingleHolder holder, int position) {
        super.onBindViewHolder(holder, position);
        holder.nameView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ((SingleItemClickListener) listener).onNameClick(getItem(holder).getName());
            }
        });

        holder.ageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ((SingleItemClickListener) listener).onAgeClick(getItem(holder).getAge());
            }
        });
    }

    public interface SingleItemClickListener extends OnItemClickListener<SingleHolder> {

        void onNameClick(String name);

        void onAgeClick(int age);
    }
}

多种Item

运行结果如下图所示:

多个Item的ViewHolder的写法,可以根据Item的View重合度来写:

  1. 如果多个item完全没有相同的部分,则单独继承ViewHolder
  2. 如果Item之间有相同的部分,可以抽出来一个父类来继承ViewHolder

这里的范例Item是具有重合部分的.模型来自聊天界面.

Holder部分如下:
|-ChatHolder //聊天View的Holder,包含公共部分
    |-TextHolder //文字消息的Holder,包含文字特有的部分
    |-ImageHolder //图片消息的Holder,包含图片特有的部分.

数据部分如下:
|-ChatMsg //代表一条聊天消息
    |-TextMsg //代表一条文字消息
    |-ImageMsg //代表一条图片消息

ChatHolder代码如下,包含发送者的名称和时间:

/**
 * 聊天界面的ViewHolder
 * Created by zyz on 2016/5/18.
 */
public class ChatHolder extends BaseHolder<ChatMsg> {

    TextView senderNameTv;
    TextView createTimeTv;

    public ChatHolder(ViewGroup parent, @LayoutRes int resId) {
        super(parent, resId);

        senderNameTv = getView(R.id.name_tv);
        createTimeTv = getView(R.id.create_time_tv);
    }

    @Override
    public void setData(ChatMsg data) {
        senderNameTv.setText(data.getSenderName());
        createTimeTv.setText(data.getCreateTime());
    }
}

TextHolder的代码如下,包含文本显示的View

/**
 * 文本消息的Holder
 * Created by zyz on 2016/5/18.
 */
public class TextHolder extends ChatHolder {

    TextView contentTv;

    public TextHolder(ViewGroup parent, @LayoutRes int resId) {
        super(parent, resId);

        contentTv = getView(R.id.content_tv);
    }

    @Override
    public void setData(ChatMsg data) {
        super.setData(data);
        contentTv.setText(((TextMsg)data).getText());
    }
}

其中的setData()方法默认调用父类的方法,可以直接设置发送者的名称和时间.

ImageHolder的代码如下,包含显示图片的View

/**
 * 表情消息的Holder
 * Created by zyz on 2016/5/18.
 */
public class ImageHolder extends ChatHolder {

    ImageView contentIv;

    public ImageHolder(ViewGroup parent, @LayoutRes int resId) {
        super(parent, resId);
        contentIv = getView(R.id.content_iv);
    }

    @Override
    public void setData(ChatMsg data) {
        super.setData(data);
        contentIv.setImageResource(((ImageMsg)data).getResId());
    }
}

最后是我们的Adapter,代码不多.

```java
/**

  • 聊天界面的Adapter
  • Created by zyz on 2016/5/18.
    */
    public class ChatAdapter extends BaseAdapter<ChatMsg, ChatHolder> {

    private static final int VIEW_TEXT = 0;
    private static final int VIEW_IMAGE = 1;

    public ChatAdapter(OnItemClickListener listener) {
    super(null, listener);
    }

    @Override
    public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    ChatHolder holder;
    if (viewType == VIEW_IMAGE) {
    holder = new ImageHolder(parent, R.layout.item_msg_img_left);
    } else {
    holder = new TextHolder(parent, R.layout.item_msg_text_left);
    }
    return holder;