首页 > 代码库 > 让偷懒更彻底——用Butterknife 来为recyclerview 打造通用适配器(上)

让偷懒更彻底——用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 update(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

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

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