实现RecyclerView的可复用Adapter

已经有好几天么有更新了,为考试准备了几天时间,现在考完了可以重操旧业了哈哈!

虽然之前已经写过这篇文章了,但是刚刚翻看了一下自己写过的旧文章,发现这篇写的非常的烂头-。=,而且里面有一个问题没有说的明白,所以决定重新写一下,也为了自己能够再熟悉一下。


下面进入正题:

可复用适配器基本原理

相信大家都是用过RecyclerView,作为一个很常用的数据展示类控件,大家都知道其最为核心的地方就在于适配器的编写了。(之后我会在博客中补充一篇RecyclerView的详细使用的博客),编写适配器的步骤主要分为以下几个步骤:

  1. 创建View布局
  2. 绑定View布局中的控件
  3. 添加数据

而在必要的时候,我们会通过ViewHolder去实现以上步骤。

其中1对应的是RecyclerView中的onCreateViewHolder方法,2对应的是RecyclerView的onBindViewHolder方法。当然RecyclerView还有一个getItemCount方法需要重写,但是不在本篇重点考虑中。

在以往我们每次写一个RecyclerView,都需要写一个适配器,其实就是因为我们每次的RecyclerView条目(也就是需要在RecyclerView中创建的布局)不同,因为是已经写死了的,所以无法更改。

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.rv_layout, parent, false);
        return new MyViewHolder(view);
    }
    class static MyViewHolder extends RecyclerView.ViewHolder {

        TextView mTv;

        public MyViewHolder(View itemView) {
            super(itemView);
            mTv = itemView.findViewById(R.id.name);
        }
    }

例如上面,就是一个典型的RecyclerView适配器的写法。在适配器中,我们固定了布局id和控件类型,所以只能给一种适配。

假如说一个适配器类没有写死布局,那么他就可以对各种布局中的控件进行绑定(findViewById)了。这就是可复用适配器的基本原理。

现在我们知道了基本原理,接下试一下如何实现吧。

实现步骤

在以前,我们都是在适配器中传入源数据引用(List等)、上下文(Context),而现在实现可复用,所以需要传入一个很重要的参数:布局ID。

    private List<T> mList;
    private int mResId;

    public MyBaseAdapter(List<T> mList, int mResId) {
        this.mList = mList;
        this.mResId = mResId;
    }

1.修改ViewHolder

    public static class MyViewHolder extends RecyclerView.ViewHolder {

        public static MyViewHolder getHolder(){
            
            return null;
        }
        private MyViewHolder(View itemView) {
            super(itemView);
        }
        ......
    }

与之前一同,现在我们把MyViewHolder创建实例的方法改成了静态工厂创建。具体里面的逻辑和添加参数和其他方法的操作我们等会儿再说。现在只需要知道,创建MyViewHolder实例只需要通过这个这个getHolder方法就好。

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return MyViewHolder.getHolder();
    }

RecyclerView中的onCreateViewHolder方法需要一个MyViewHolder对象,所以在这里调用MyViewHolder.getHolder方法(当然肯定是需要参数的,等会儿再添加啦)。

2.看一看onBindViewHolder方法

我们之前在onBindViewHolder方法中处理控件赋值的事务,虽然现在我们不知道是哪个布局,是什么控件,但是我们只需要知道,在onBindViewHolder方法中,就是处理这一个事务,我们假定其他的都处理好了。

但是不同的布局也有不同的控件,我们还是不知道该对什么控件进行操作,但是注意到在Adapter构造器中就传入了布局ID。所以我们需要一个抽象方法,然后把这些乱七八糟的事情扔给调用者(一般都是在Activity或者Fragment中)。

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        bindView(holder, position);
    }

    public abstract void bindView(MyViewHolder holder,int position);

当然,这个适配器也需要变成抽象类了。

public abstract class MyBaseAdapter<T> extends RecyclerView.Adapter<MyBaseAdapter.MyViewHolder> ;

3.补充

大体的框架就是这个样子,接下来我们需要对之前的方法参数、方法之类的进行一下补充。

首先添加给getHolder方法完善一下参数吧:

我们知道getHolder方法为了获取获取到一个Holder对象,所以,

    public static MyViewHolder getHolder(int mResId, ViewGroup parent, int viewType) {

        MyViewHolder holder;
        View view = LayoutInflater.from(parent.getContext()).inflate(mResId, parent, false);
        holder = new MyViewHolder(view);

        return holder;
    }

在onCreateViewHolder中调用:

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Log.e(TAG, "onCreateViewHolder: ");
        return MyViewHolder.getHolder(mResId, parent, viewType);
    }

然后我们需要完善一下MyViewHolder的属性:

    private SparseArray<View> views = new SparseArray<>();

最简单的就是在只需要在MyViewHolder中添加一个SparseArray属性,制定他的泛型为View,这个属性用来存储布局中的控件,通过id来获取。


接下来需要在MyViewHolder中写几个辅助方法:

    private <T extends View> T getView(int id) {
        T t = (T) views.get(id);
        if (t == null) {
            t = this.itemView.findViewById(id);
            views.put(id, t);
        }
        return t;
    }

这个方法就是通过id来从SparseArray实例中获取控件,如果已经存储过,那么就直接获取,如果为空,就调用itemView来获取。

这里要说一下itemView这个属性:我们并没有在MyViewHolder中创建这样一个属性,它是在RecyclerView.ViewHolder中的一个final属性,

    public abstract static class ViewHolder {
        public final View itemView;
    }

这个itemView在ViewHolder的构造器中被赋值,我们点击super(itemView)看一下:

    public ViewHolder(View itemView) {
        if (itemView == null) {
            throw new IllegalArgumentException("itemView may not be null");
        }
        this.itemView = itemView;
    }

好了,如果想继续深入研究的朋友可以下去自己研究,我们继续完善MyViewHolder。

其实到现在为止我们的部署任务已经完成了,就差一点——给控件赋值,由于Android中有各式各样的控件,我们不可能做到给每个控件都赋值,所以最好是用到什么的地方然后再去添加,不过这样也比重写一个适配器要好很多了吧。

我们先写一个给TextView添加数据的方法吧:

    public MyViewHolder setText(int id, String text) {
        TextView textView = getView(id);
        textView.setText(text);
        return this;
    }

然后再来一个CheckBox数据添加的方法:

    public MyViewHolder setChecked(int id, boolean isChecked) {
        CheckBox checkBox = getView(id);
        checkBox.setChecked(isChecked);
        return this;
    }

这里都是用到了刚刚写的getView方法,先获取到对应id的控件,然后进行赋值。

由于给item的控件进行赋值,所以可能会有很多的控件,为了调用方便我们把这一系列函数都写成了链式编程。

然后这些方法使用是在什么地方呢?还记得我们之前写的抽象方法bindView吗?这个抽象函数实在调用者的地方实现的:

    public abstract void bindView(MyViewHolder holder, int position);

在这个方法中有一个holder对象,这个holder就是我们之前进行一系列加工的holder,所以刚才写的一系列方法都可以使用。

    recyclerView.setAdapter(new MyBaseAdapter<Bean>(list, R.layout.rv_layout) {
        @Override
        public void bindView(MyViewHolder holder, int position) {
            holder.setText(R.id.name, list.get(position).getName());
        }
    });

这个就是在调用者中实现的bindView方法了,可以看到我们直接调用holder的setText方法,然后将需要添加数据的控件ID传入,然后再将数据传入就好了。当然如果有很多控件需要添加数据,这时候链式编程的优势就展现出来啦。

    holder.setText(R.id.name, list.get(position).getName())
            .setPicture(R.id.head, R.drawable.ic_launcher_background)
            .setChecked(R.id.isOver, false);

下面一起来看一下效果吧:



源码如下:https://github.com/gfzy9876/RecyclerView-.git


今天的文章就到这里了,喜欢的朋友希望点赞或关注一下,有不同想法的朋友们欢迎欢迎下方留言一起探讨,感谢大家的支持!!





猜你喜欢

转载自blog.csdn.net/zy_jibai/article/details/80908851