让偷懒更彻底——用Butterknife 来为recyclerview 打造通用适配器(上)
Posted 思忆(GeorgeQin)
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了让偷懒更彻底——用Butterknife 来为recyclerview 打造通用适配器(上)相关的知识,希望对你有一定的参考价值。
背景
随着recyclerview 的越来越普及,其高度的易用性,让我们越来越爱不释手,当然网上也出现了很多类似的通用适配器,让我们更加方便的使用它,今天我们这里介绍一种新的recyclerview的通用适配器的实现思路——把recyclerview和ButterKnife结合起来使用(ps:因为公司开发一直使用butterknife,才有了这种想法)。
首先贴上我的实现效果:
代码用法使用:
ModelRecyclerAdapter adapter = new ModelRecyclerAdapter(MyImageViewHolder.class, datas);
recyclerView.setAdapter(adapter);
其中datas就是我们的数据,当然为了通用是泛型的,这边是传入的一个String的list,最关键的是我的MyViewHolder.class类了,他就是我们的核心点了,在这个类里面封装了我们所有数据展示和点击事件。
首先是item布局代码,我这边为了简单,就用了一个imageview,
在其中我用Picasso去加载了一张图片,然后给每个位置设置了点击效果展示当前position。
R.layout.item_list:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="120dp"
android:layout_height="120dp"
android:background="@android:color/black"
android:paddingLeft="8dp"
android:paddingTop="8dp">
<ImageView
android:id="@+id/iv_item1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:padding="12dp"
android:scaleType="centerCrop" />
</LinearLayout>
我们绑定事件的viewholder类:
/**
* 我们的实际使用中的viewhoder
* 用注释item的布局文件,在这个类的实现中,我们可以绑定点击事件,更新数据
*/
@RecyclerItemViewId(R.layout.item_list)
public static class MyViewHolder extends ModelRecyclerAdapter.ModelViewHolder<String> {
@BindView(R.id.iv_item1)
ImageView imageView;
/**
* 可以对itemview的任何一个view绑定监听,这里只是以onclick为例,当然也可以绑定onTouch,onLongClick等
*/
@OnClick(R.id.iv_item1)
void onclick() {
Toast.makeText(imageView.getContext(), position + " 点击~", Toast.LENGTH_SHORT).show();
}
public int position;
public MyViewHolder(View itemView) {
super(itemView);
}
/**
* 绑定我们的数据
*
* @param item 这是数据
* @param adapter adapter 对象
* @param context context对象
* @param positon 当前位置
*/
@Override
public void convert(String item, ModelRecyclerAdapter adapter, Context context, int positon) {
this.position = positon;
Picasso.with(context).load(item).into(imageView);
}
}
代码很清晰有木有!?我们只需要在这个类里面完成布局的绑定,数据的绑定,监听的绑定,然后设置给recyclerview的时候只需2行代码即可!教练,这波我要偷懒!
设计思路分析:
我们在为recyclerview定制adpter的时候,必须继承自Recyclerview.Adapter<>而这个泛型中需要传入Recyclerview.viewholder的实现类,那么这个viewholder类是什么呢?其实这个类在构造的时候,需要传入一个itemview,这个view就相当于我们使用listview和gridview中的convertview,而在listview中,我们复用的单位是convertview,而在recyclerview中,我们复用的单位是这个viewholder,itemview就是其中一个成员变量,下面我们首先用原始的方式写出我们demo里面那个图片实例展示:
public class MyHolder extends RecyclerView.ViewHolder {
/**
* 构造方法,传入view即我们listview的convertview
*
* @param itemView
*/
public MyHolder(View itemView) {
super(itemView);
}
}
public class MyAdapter extends RecyclerView.Adapter<MyHolder> {
List<String> datas = new ArrayList<>();
public MyAdapter(List<String> data) {
this.datas = data;
}
@Override
public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
MyHolder holder = new MyHolder(itemView);//完成对holder的创建,中间互用逻辑RecyclerView帮我们完成了
return holder;
}
@Override
public void onBindViewHolder(MyHolder holder, final int position) {
//这里完成数据的绑定与事件的监听
ImageView imageView = (ImageView) holder.itemView.findViewById(R.id.iv_item1);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "点击 " + position, Toast.LENGTH_SHORT).show();
}
});
Picasso.with(holder.itemView.getContext()).load(datas.get(position)).into(imageView);
}
@Override
public int getItemCount() {
return datas.size();
}
}
由此可见,viewholder类才是我们的核心出发点,我只要在这个类里面完成我们的数据绑定,监听,即可,所以我们现在这么写:
ModelAdapter.ModelviewHolder:
/**
* hodler抽象类,支持任何数据类型
*
* @param <T>
*/
public static abstract class ModelViewHolder<T> extends RecyclerView.ViewHolder {
public ModelViewHolder(View itemView) {
super(itemView);
}
/**
* 这个是我们真正在实际使用的类中的绑定数据的方法
*
* @param item bean类型
* @param adapter adpter对象
* @param context context对象
* @param positon 位置
*/
public abstract void convert(T item, ModelRecyclerAdapter adapter, Context context, int positon);
}
下面是我们的modeladapter类的核心代码:
/**
* RecyclerView 通用适配器第一版
* Created by cd5160866 on 16/5/10.
*/
public class ModelRecyclerAdapter<T> extends RecyclerView.Adapter<ModelRecyclerAdapter.ModelViewHolder> {
protected Context mContext;
/**
* 通过注释的方式加入的布局item的layoutId
*/
protected int mLayoutId;
/**
* viewholder的实现类类名
*/
private Class<? extends ModelViewHolder> viewHolderClass;
/**
* 数据 即我们的任何类型的bean
*/
protected List<T> mDatas = new ArrayList<>();
public ModelRecyclerAdapter(Class<ModelViewHolder> viewHolderClass, List<T> Datas) {
this.viewHolderClass = viewHolderClass;
this.mLayoutId = viewHolderClass.getAnnotation(RecyclerItemViewId.class)//获取我们的layoutid,我们的类注释后面的部分
.value();
mDatas = Datas;
}
@Override
public ModelViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
ModelViewHolder viewHolder = null;
if (mContext == null)
mContext = parent.getContext();
try {
View converView = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false);
viewHolder = viewHolderClass.getConstructor(View.class).newInstance(converView);
ButterKnife.bind(viewHolder, converView);//将viewhodler于我们的view绑定起来
} catch (Exception e) {
e.printStackTrace();
}
return viewHolder;
}
@Override
public void onBindViewHolder(ModelViewHolder holder, int position) {
holder.convert(mDatas.get(position), this, mContext, position);//这里更新数据
}
@Override
public int getItemCount() {
return mDatas.size();
}
}
首先我们看这个ModelAdapter的第20行,也就是我们的构造的方法,这个方法接收两个参数,其中第二个参数不用多说,当然是我们的数据,第一个参数也就是我们的ModelViewhodler的实现类,adpter会根据这类来创建viewholder,再看第22行,这一行就是我们获取我们的itemId的核心代码,这个布局id不是通过构造方法传进来的,那是怎么获得的呢?没错!就是java注解(Annotation),至于为何用注解,分析完成后我们就知道了,下面我们新建一个Annotation:
@Retention(RUNTIME)
public @interface RecyclerItemViewId {
int value();//我们注解后面使用id值
}
关于注解的用法,可以参考:深入理解java注解
所以在最文章最开头我们自己实现的那个ModelViewHolder的实现类上面就是这么写:
@RecyclerItemViewId(R.layout.item_list)
public static class MyImageViewHolder extends ModelRecyclerAdapter.ModelViewHolder<String> {
//.....
}
然后我们再回过头来看ModelRecyclerAdapter的第22行,我们也就明白了是啥意思了,
this.mLayoutId = viewHolderClass.getAnnotation(RecyclerItemViewId.class).value();
我们通过传入的我们自己的实现类名去获取我们自定义的注解,再去获取注解的值,也就是我们的layoutId,拿到id以后,当然就是我们在onCreateViewHolder方法中去创建我们的itemview,也就是第34行,再往下看第35行,在这一行我们当然的思路就是要用itemview去创建我们的viewholder实例,因为我们自己的Modelviewhodler是个抽象类,无法直接new,我们当然只能通过它的子类来创建实例,即我们只能通过我们传进来的实现类的类名去获取他的构造方法,当然viewholder本身构造需要view作为参数,所以这里我们需要获取它带View.class的构造方法,所以综合起来,代码就是这么写:
View converView = LayoutInflater.from(context).inflate(mLayoutId, parent, false);
viewHolder = viewHolderClass.getConstructor(View.class).newInstance(converView);
说到最后,当然是我们的butterKnife了,只需一行代码,把我们的viewholder和convertview绑定起来:
ButterKnife.bind(viewHolder, converView);//将viewhodler于我们的view绑定起来
至此,我们就能向一开始一样,在viewholder这个类里面对itemview中的任何一个view进行点击,长按,触摸等各种监听,demo为了简单直观,只写了一个onclick事件:
/**
* 可以对itemview的任何一个view绑定监听,这里只是以onclick为例,当然也可以绑定onTouch,onLongClick等
*/
@OnClick(R.id.iv_item1)
void onclick() {
Toast.makeText(imageView.getContext(), position + " 点击~", Toast.LENGTH_SHORT).show();
}
再回过头来看我们的adapter的代码,去看看45行,也就是onBindViewHolder方法,当然,在这里我们的完成数据的绑定与更新:
@Override
public void onBindViewHolder(ModelViewHolder holder, int position) {
holder.convert(mDatas.get(position), this, mContext, position);//这里更新数据
}
这里调用了我们的viewholder这个抽象方法,我们因此在我们的自己实现的viewholder类里面去实现这个方法完成对我们数据的绑定,比如,我的demo里面依旧是用Picasso去加载一直图片~至此,我们的ModelRecyclerAdapter核心逻辑完成,此外我们再给他封装一些方法,方便他添加数据和删除数据~所以完整代码如下:
最终完成! ModelRecyclerAdapter:
/**
* RecyclerView 通用适配器第一版
* Created by cd5160866 on 16/5/10.
*/
public class ModelRecyclerAdapter<T> extends RecyclerView.Adapter<ModelRecyclerAdapter.ModelViewHolder> {
protected Context mContext;
/**
* 通过注释的方式加入的布局item的layoutId
*/
protected int mLayoutId;
/**
* viewholder的实现类类名
*/
private Class<? extends ModelViewHolder> viewHolderClass;
/**
* 数据 即我们的任何类型的bean
*/
protected List<T> mDatas = new ArrayList<>();
public ModelRecyclerAdapter(Class<ModelViewHolder> viewHolderClass) {
this.viewHolderClass = viewHolderClass;
this.mLayoutId = viewHolderClass.getAnnotation(RecyclerItemViewId.class)
.value();
}
public ModelRecyclerAdapter(Class<ModelViewHolder> viewHolderClass, List<T> Datas) {
this.viewHolderClass = viewHolderClass;
this.mLayoutId = viewHolderClass.getAnnotation(RecyclerItemViewId.class)//获取我们的layoutid,我们的类注释后面的部分
.value();
mDatas = Datas;
}
@Override
public ModelViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
ModelViewHolder viewHolder = null;
if (mContext == null)
mContext = parent.getContext();
try {
View converView = LayoutInflater.from(parent.getContext()).inflate(mLayoutId, parent, false);
viewHolder = viewHolderClass.getConstructor(View.class).newInstance(converView);
ButterKnife.bind(viewHolder, converView);//将viewhodler于我们的view绑定起来
} catch (Exception e) {
e.printStackTrace();
}
return viewHolder;
}
@Override
public void onBindViewHolder(ModelViewHolder holder, int position) {
holder.convert(mDatas.get(position), this, mContext, position);//这里更新数据
}
@Override
public int getItemCount() {
return mDatas.size();
}
public void add(int positon, T data) {
mDatas.add(positon, data);
notifyItemInserted(positon);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
}, 500);
}
public void add(T data) {
mDatas.add(data);
notifyDataSetChanged();
}
public void add(List<T> data) {
mDatas.clear();
mDatas.addAll(data);
notifyDataSetChanged();
}
public void remove(int position) {
mDatas.remove(position);
notifyItemRemoved(position);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
}, 500);
}
public void replace(int position, T data) {
mDatas.remove(position);
mDatas.add(position == 0 ? position : position - 1, data);
notifyDataSetChanged();
}
public void addAll(List<T> datas) {
mDatas.addAll(datas);
notifyDataSetChanged();
}
public void clear() {
mDatas.clear();
notifyDataSetChanged();
}
public List<T> getItems() {
return mDatas;
}
/**
* hodler抽象类,支持任何数据类型
*
* @param <T>
*/
public static abstract class ModelViewHolder<T> extends RecyclerView.ViewHolder {
public ModelViewHolder(View itemView) {
super(itemView);
}
/**
* 这个是我们真正在实际使用的类中的绑定数据的方法
*
* @param item bean类型
* @param adapter adpter对象
* @param context context对象
* @param positon 位置
*/
public abstract void convert(T item, ModelRecyclerAdapter adapter, Context context, int positon);
}
}
声明一下的是第59行的add方法和83行的remove方法是先调用notifyItemRemoved(position)是为了保留插入动画和删除动画效果,但是这个貌似会让position错位,因此再延时去调动notifyDataSetChanged()保证位置正常。
至此我们也就明白了,我们创建我们的recyclerAdapter的时候,只需要在构造方法中传入viewholder的实现类即可,我们在这个类里面完成了对item布局的绑定,事件的监听,数据的绑定,一个类解决所有问题,确实是剩下了不少事,代码也能精简不少。
具体使用方法,大家有兴趣的可以参考demo。
整篇文章跨度较大,包含了java注解,recyclerView,Butterknife,其是网上有很多通用适配器的实现方式,今天这里只是一种实现思路,至此分析完成,有问题请随时沟通,下篇,我将继续在此基础上,为我们的Recyclerview简洁的添加Header,footer和emptyview,祝贺大家寒假愉快,新年加油!↖(^ω^)↗
相关链接:
以上是关于让偷懒更彻底——用Butterknife 来为recyclerview 打造通用适配器(上)的主要内容,如果未能解决你的问题,请参考以下文章
Android Studio上方便使用butterknife注解框架的偷懒插件Android Butterknife Z
AndroidStudio使用偷懒插件Butterknife和GsonFormat