前情提要
最近项目我在项目中使用了 RecyclerView
代替了 ListView
.由于项目中有多出列表项使用 RecyclerView
,这就导致需要写多个 Adapter
和 ViewHolder
.
其实,怎么说呢?就是懒,想少写代码,所以想研究一下能否简化一下.
具体实现
封装分为 Adapter
和 ViewHolder
两部分,如下所示.
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
,并绑定了两个泛型:
- M : 用于该 Adapter 的列表的数据类型,即
List<M>
. - 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重合度来写:
- 如果多个item完全没有相同的部分,则单独继承
ViewHolder
- 如果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,代码不多.
/**
* 聊天界面的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;