让偷懒更彻底——用Butterknife 来为recyclerview 打造通用适配器(上)

来源:互联网 发布:淘宝网机器人 编辑:程序博客网 时间:2024/05/14 03:48

背景

随着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,祝贺大家寒假愉快,新年加油!↖(^ω^)↗

相关链接:

demo下载

关于butterknife

0 0
原创粉丝点击