优雅封装RecyclerView通用Adapter
一、 概述
看了鸿洋大神的 为RecyclerView打造通用Adapter ,感觉随着 item 类型的增多以及 item 功能复杂度的增加,用该 Adapter 的实现会增加不少麻烦,所以想自己封装一个更加解耦的通用 Adapter。实现思想是用一个实体类来承载 item 的功能和数据。
功能特点:
- 支持多 item
- 为每个不同类型的 item 分别进行一个实体类包装,更解耦,维护起来方便
- 实现 item 状态保存及恢复
- item 点击事件实现
- 添加分类头
二、 使用及效果
(1) 单 item 情况
新建一个实体类继承自 RecyclerItem
public class SingleItem implements RecyclerItem {
int num;
public SingleItem(int num){
this.num = num;
}
@Override
public int getType() {
return 0;
}
@Override
public View getView(Context context, ViewGroup parent) {
return LayoutInflater.from(context).inflate(R.layout.item1,parent,false);
}
@Override
public void convert(final RecyclerViewHolder holder) {
TextView text = holder.getViewById(R.id.text);
text.setText(String.valueOf(num));
text.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(holder.getView().getContext(),"被点击",Toast.LENGTH_SHORT).show();
}
});
}
在convert
方法中调用该对象的数据,不需要额外的类作为数据载体。接着用此 item 作为数据源传给 RecyclerAdapter
//MainActivity#onCreate()
for (int i=0;i<10;i++){
RecyclerItem item = new SingleItem(i);
dataList.add(item);
}
recyclerView.setAdapter(new RecyclerAdapter(this,dataList));
贴个运行结果
图1
(2)多 item 类型
多 item 跟单 item 类似,为每一个 item 都创建一个实体类实现 RecyclerItem
接口,然后根据具体情况进行相关逻辑操作。接着将 item 的实例列表传到 RecyclerAdapter
中。如:
for (int i = 0;i < 10;i++){
RecyclerItem item;
if(i % 2 == 0){
item = new MutiItem1();
}else{
item = new MutiItem2();
}
dataList.add(item);
}
但需要注意的是,多 item 情况必须保证每个 RecyclerItem
的实现类的 int getType()
方法返回不同的整形数据,简单的方法是返回当前类名的 hashCode()
。
(3)保存及恢复 item 状态
平时开发中,经常需要在 RecyclerView 中进行 item 状态的保存及恢复。例如,多选功能中 item 的选中状态保存及恢复。下面我们来用此 Adapter 来实现。
先上个效果:
图2
实现过程:
跟上面的过程一样,首先新建 SendMsgItem
和 ReceiveMsgItem
两个实体类,并实现 RecyclerItem
接口。下面是 SendMsgItem 类的部分代码:
public class SendMsgItem implements RecyclerItem, CheckObservable.Observer {
boolean isChecked = false;
boolean isVisible = false;
private String mMsg;
public SendMsgItem(String msg){
mMsg =msg;
CheckObservable.getInstance().registerObserver(this);
}
@Override
public void convert(RecyclerViewHolder holder) {
TextView textView = holder.getViewById(R.id.msg_send);
textView.setText(mMsg);
final CheckBox checkBox = holder.getViewById(R.id.check_box);
View view = holder.getView();
if (isVisible) {
checkBox.setVisibility(View.VISIBLE);
checkBox.setChecked(isChecked);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
isChecked = !isChecked;
checkBox.setChecked(isChecked);
}
});
} else {
checkBox.setVisibility(View.GONE);
view.setOnClickListener(null);
isChecked = false;
}
}
@Override
public void changed() {
isVisible = !isVisible;
}
}
/**省略部分代码**/
在 convert()
方法中进行了状态的判断显示以及保存。基本所有的逻辑和数据都在当前类中实现,有良好的扩展性,便于日后更改维护。CheckObservable
类与 Observer
接口是为了后面显示选择框用的,用了观察者模式,代码如下:
public class CheckObservable extends Observable<CheckObservable.Observer> {
private static CheckObservable mInstance;
private CheckObservable() {
}
public static CheckObservable getInstance() {
if (mInstance == null) {
synchronized (CheckObservable.class) {
if (mInstance == null) {
mInstance = new CheckObservable();
}
}
}
return mInstance;
}
public void notifyChanged() {
for (int i = 0; i < mObservers.size(); i++) {
mObservers.get(i).changed();
}
}
public interface Observer {
void changed();
}
}
接着是 MainActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
/**省略部分代码**/
final RecyclerAdapter adapter = new RecyclerAdapter(this, dataList);
initData();
adapter.setOnItemLongClickListener(new RecyclerAdapter.OnItemLongClickListener() {
@Override
public boolean onLongClick(View v , int position) {
CheckObservable.getInstance().notifyChanged();
adapter.notifyDataSetChanged();
return false;
}
});
recyclerView.setAdapter(adapter);
}
private void initData() {
RecyclerItem item;
for (int i = 0; i < 20; i++) {
if (i % 2 == 0) {
item = new SendMsgItem("Nice to meet you. "+i);
} else {
item = new ReceiveMsgItem("Nice to meet you too. "+i);
}
dataList.add(item);
}
}
Adapter 中也封装了 setOnItemClickListener
与 setOnItemLongClickListener
,方便在 Activity 和 Fragment 中使用。如果需要例子 Demo 源码的话请拉至文末。
三、 源码实现
经过上面的例子和说明,相信你已大概了解该封装的用法了,应该也知道此 Adapter 需要配套使用。即 RecyclerAdapter
+RecyclerItem
+RecyclerViewHolder
,缺一不可。下面我们就来一一分析一下这三个类具体的实现。
(1) RecyclerItem
接口:
public interface RecyclerItem {
/**
*@return 返回item的type,当使用多item时必须保证每个item返回不同的type
*/
int getType();
/**
* 此方法在{@link RecyclerAdapter#onCreateViewHolder(ViewGroup, int)}
* 内调用,你应该在此方法的实现中返回item的view对象
* @return item view
*/
View getView(Context context, ViewGroup parent);
/**
* 此方法在{@link RecyclerAdapter#onBindViewHolder(RecyclerViewHolder, int)}
* 内调用,你应该在此方法的实现中进行item的view与数据的绑定
* @param holder 当前item的{@link RecyclerViewHolder}
*/
void convert(RecyclerViewHolder holder);
}
各个方法的说明在注释中已经解释的很清楚了。
在使用过程中,应该在每个 RecyclerItem
实现类里面进行数据的存取以及数据和视图的绑定。
(2)RecyclerViewHolder
类
public class RecyclerViewHolder extends RecyclerView.ViewHolder {
private View mView;
private SparseArray<View> mViewMap = new SparseArray<>();
public <T extends RecyclerItem> RecyclerViewHolder(Context context, T item, ViewGroup parent) {
super(item.getView(context, parent));
mView = itemView;
mView.requestFocusFromTouch();
}
//返回根View
public View getView() {
return mView;
}
/**
* 根据View的id来返回view实例
*/
public <T extends View> T getViewById(int ResId) {
View view = mViewMap.get(ResId);
if (view == null) {
view = mView.findViewById(ResId);
mViewMap.put(ResId, view);
}
return (T) view;
}
}
该类的实现参考了鸿洋大神的通用 ViewHolder 中的部分代码,用了 SparseArray 来保存 View 对象,避免每次都要 findViewById。
(3)RecyclerAdapter
类
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerViewHolder> {
private List<RecyclerItem> mItemList;
private Context mContext;
//保存当前 ViewHolder 在 Adapter 中的位置
private int mPosition;
private OnItemClickListener mClickListener;
private OnItemLongClickListener mLongClickListener;
public RecyclerAdapter(Context context, List<RecyclerItem> itemList) {
mContext = context;
mItemList = itemList;
}
@Override
public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new RecyclerViewHolder(mContext, mItemList.get(mPosition), parent);
}
@Override
public void onBindViewHolder(RecyclerViewHolder holder, int position) {
//保存view的position
holder.getView().setTag(position);
setListener(holder);
mItemList.get(position).convert(holder);
}
@Override
public int getItemViewType(int position) {
mPosition = position;
return mItemList.get(position).getType();
}
@Override
public int getItemCount() {
return mItemList.size();
}
public Context getContext() {
return mContext;
}
protected void setListener(final RecyclerViewHolder holder) {
if (mClickListener != null && !holder.getView().hasOnClickListeners()) {
holder.getView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//从tag中取出position
mClickListener.onClick(v,(int)v.getTag());
}
});
}
if (mLongClickListener != null && !holder.getView().hasOnClickListeners()) {
holder.getView().setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return mLongClickListener.onLongClick(v,(int)v.getTag());
}
});
}
}
public void setOnItemClickListener(OnItemClickListener l) {
mClickListener = l;
}
public void setOnItemLongClickListener(OnItemLongClickListener l) {
mLongClickListener = l;
}
public interface OnItemClickListener {
void onClick(View view,int position);
}
public interface OnItemLongClickListener {
boolean onLongClick(View view,int position);
}
}
此类继承了 RecyclerVIew.Adapter
抽象类,然后实现了四个抽象方法
onCreateViewHolder()
onBindViewHolder()
getItemViewType()
-
getItemCount()
在前三个方法中每个方法与RecyclerItem
进行了关联,所以我们在RecyclerItem
实现类中的三个接口方法将分别在这三个方法中进行调用,这也是整个 Adapter 的封装核心。在此 Adapter 中还封装了setOnItemClickListener()
与setOnItemLongClickListener
方法,方便在 Activity 或 Fragment 中需要进行 item 的监听。
总结
为了学习 RecyclerView 与设计模式的使用,本猿封装了此 Adapter 进行日常偷懒。
该 Adapter 需要与 RecyclerItem
以及RecylerViewHoler
配合使用。在RecylerItem
实例中的convert
方法内调用对象的数据,不需要额外的类作为数据载体,每一个 RecyclerItem
都对应一个 Adapter 内的数据源。
为了使各个 Item 的功能逻辑分离开来,应该尽量在具体的 RecyclerItem
类中进行数据和功能的实现和整合。后面维护过程中需要更改哪个 item 只需要打开与修改该 item 的实现类即可。
下一篇文章将会带来用此封装扩展的 RecyclerView
,支持下拉刷新,上拉加载,HeaderView,FooterView...
Demo地址: https://github.com/gminibird/RecyclerViewTest