前情提要
最近项目我在项目中使用了RecyclerView代替了ListView.由于项目中有多出列表项使用RecyclerView,这就导致需要写多个Adapter和ViewHolder.
其实,怎么说呢?就是懒,想少写代码,所以想研究一下能否简化一下.
具体实现
封装分为Adapter和ViewHolder两部分,如下所示.
ViewHolder
抽象类BaseHolder继承RecyclerView.ViewHolder,并依赖注入的数据类型M,即和ViewHolder绑定的数据类型为M.
该抽象类包含一个构造方法,用于获取item对应的布局.一个抽象函数用于将数据设置到item上面.
[代码]java代码:
01 02 03 04 05 06 07 08 09 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); }</t></t></m> |
Adapter
Adapter类也为抽象类,继承于RecyclerView.Adapter,并绑定了两个泛型:
1. M : 用于该 Adapter 的列表的数据类型,即List<M>.
2. H : 即和 Adapter 绑定的 Holder 的类型.
并且,该 Adapter 自带 List 数据集合,声明时可以不用传递数据集合.也包含了 List 的相关操作.同时还给该 Adapter 绑定了一个 item 的点击事件,且为可选操作,不需要点击操作,直接传null即可.
[代码]java代码:
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 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); } }</m></m></m></h></m></h></m></h></m,> |
使用范例
使用范例为一种Item和多种Item这两种类型.
一种Item
运行结果如下图所示:
单个Item类型的ViewHolder如下:
[代码]java代码:
01 02 03 04 05 06 07 08 09 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())); } }</person> |
与之对应的Adapter如下:
[代码]java代码:
01 02 03 04 05 06 07 08 09 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); } }</singleholder></person,> |
多种Item
运行结果如下图所示:
多个Item的ViewHolder的写法,可以根据Item的View重合度来写:
1. 如果多个item完全没有相同的部分,则单独继承ViewHolder
2. 如果Item之间有相同的部分,可以抽出来一个父类来继承ViewHolder
这里的范例Item是具有重合部分的.模型来自聊天界面.
[代码]java代码:
1 2 3 4 5 6 7 8 9 | Holder部分如下: |-ChatHolder //聊天View的Holder,包含公共部分 |-TextHolder //文字消息的Holder,包含文字特有的部分 |-ImageHolder //图片消息的Holder,包含图片特有的部分.
数据部分如下: |-ChatMsg //代表一条聊天消息 |-TextMsg //代表一条文字消息 |-ImageMsg //代表一条图片消息 |
ChatHolder代码如下,包含发送者的名称和时间:
[代码]java代码:
01 02 03 04 05 06 07 08 09 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()); } }</chatmsg> |
TextHolder的代码如下,包含文本显示的View
[代码]java代码:
01 02 03 04 05 06 07 08 09 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
[代码]java代码:
01 02 03 04 05 06 07 08 09 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,代码不多.
```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;