前情提要
最近项目我在项目中使用了RecyclerView
代替了ListView
.由于项目中有多出列表项使用RecyclerView
,这就导致需要写多个Adapter
和ViewHolder
.
其实,怎么说呢?就是懒,想少写代码,所以想研究一下能否简化一下.
具体实现
封装分为Adapter
和ViewHolder
两部分,如下所示.
ViewHolder
抽象类BaseHolder
继承RecyclerView.ViewHolder
,并依赖注入的数据类型M
,即和ViewHolder
绑定的数据类型为M.
该抽象类包含一个构造方法,用于获取item对应的布局.一个抽象函数用于将数据设置到item上面.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | /** * 基础的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
,并绑定了两个泛型:
- M : 用于该 Adapter 的列表的数据类型,即
List<M>
. - H : 即和 Adapter 绑定的 Holder 的类型.
并且,该 Adapter 自带 List 数据集合,声明时可以不用传递数据集合.也包含了 List 的相关操作.同时还给该 Adapter 绑定了一个 item 的点击事件,且为可选操作,不需要点击操作,直接传null
即可.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | /** * 基础的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如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /** * 一种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如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | /** * 一种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重合度来写:
- 如果多个item完全没有相同的部分,则单独继承
ViewHolder
- 如果Item之间有相同的部分,可以抽出来一个父类来继承
ViewHolder
这里的范例Item是具有重合部分的.模型来自聊天界面.
1
| Holder部分如下:
|-ChatHolder //聊天View的Holder,包含公共部分
|-TextHolder //文字消息的Holder,包含文字特有的部分
|-ImageHolder //图片消息的Holder,包含图片特有的部分.
数据部分如下:
|-ChatMsg //代表一条聊天消息
|-TextMsg //代表一条文字消息
|-ImageMsg //代表一条图片消息
|
ChatHolder
代码如下,包含发送者的名称和时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /** * 聊天界面的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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /** * 文本消息的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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * 表情消息的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,代码不多.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /** * 聊天界面的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<ChatHolder> 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; } @Override public int getItemViewType(int position) { if (getItem(position).getMsgType() == ChatMsg.TYPE_TEXT) { return VIEW_TEXT; } else { return VIEW_IMAGE; } } } |
上述设置的有时间监听,则对应的事件处理在Activity中完成
1 2 3 4 5 6 | chatAdapter = new ChatAdapter(new OnItemClickListener<ChatHolder>() { @Override public void onItemClick(ChatHolder holder) { //处理事件 } }); |
以上就是对ViewHolder和Adapter的简易封装,以后会根据需要继续封装简化.