快来封装RecyclerView通用适配器Adapter

写上一篇的RecyclerView实现侧滑删除的时候,还没考虑过封装RecyclerView.Adapter,因为google已经帮我们把ViewHolder给封装成了一种内部用的机制。后来发现现在做的一个小项目有大概6个列表,每个都是重复的事情,很麻烦。恰好想起之前看到的Hongyang大神的一篇ListView通用Adapter的封装,因此也就想封装一个RecyclerView的BaseAdapter了。

一、正常实现分析

首先来看一下正常的一个RecyclerView.Adapter如何实现:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements View.OnClickListener {

    private Context mContext;
    private List<Bean> mBeen;
    private OnItemClickListener mListener;

    public MyAdapter(Context context, List<Bean> been) {
        this.mContext = context;
        this.mBeen = been;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false);
        view.setOnClickListener(this);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.itemView.setTag(position);
        Bean bean = mBeen.get(position);
        holder.tv_item.setText(bean.getItem());
    }

    @Override
    public int getItemCount() {
        return mBeen.size();
    }

    @Override
    public void onClick(View v) {
        if (mListener != null) {
            mListener.onItemClick(this, v, (Integer) v.getTag());
        }
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        TextView tv_item;
        public ViewHolder(View itemView) {
            super(itemView);
            tv_item = (TextView) itemView.findViewById(R.id.tv_item);
        }
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.mListener = onItemClickListener;
    }

    public interface OnItemClickListener {
        void onItemClick(RecyclerView.Adapter adapter, View v, int position);
    }
}

基本上就是这样了。

先继承RecyclerView.ViewHolder实现自己的ViewHolder,用于存储复用的子View中的控件引用,包括要对控件进行初始化的findViewById操作。然后在MyAdapter的构造函数中初始化上下文Context以及数据集合,之后重点是重写onCreateViewHolder(ViewGroup parent, int viewType)用于创建ViewHolder,重写onBindViewHolder(ViewHolder holder, int position)方法实现数据的绑定,以及getItemCount()实现获取数据总量。

另外,由于RecyclerView不支持setOnItemClickListener的操作,因此我们要自己添加点击事件。所以,我定义了一个OnItemClickListener接口,然后在onCreateViewHolder中对ItemView进行了点击事件的绑定操作,而点击事件的回调就是OnItemClickListener中的onItemClick(RecyclerView.Adapter adapter, View v, int position)方法。由于view是复用的,因此关键的地方就在于在onBindViewHolder(ViewHolder holder, int position)中给ItemView设置当前position的tag,然后发生点击事件时,则从View中取出对应的position确定位置,然后回调onItemClick方法即可。


二、抽象

以上面的代码为例,联想其他的列表,发现会变化的地方有4处:

  • 数据Bean;
  • onCreateViewHolder(ViewGroup parent, int viewType)里的布局文件;
  • onBindViewHolder(ViewHolder holder, int position)里的数据绑定部分;
  • 以及自定义的ViewHolder;

不变的地方有:

  • 数据集合的集合以及上下文Context的赋值是可以不变的;
  • onCreateViewHolder中除了布局文件不一致,也不会变化,当然ViewHolder我们下面再讨论;
  • onBindViewHolder(ViewHolder holder, int position)中的setTag以及从集合中取出数据的操作是不变的;
  • getItemCount就不用说了,如果集合不变,这里的写法就是死的;
  • 当然还有OnItemClickListener的点击事件以及对应的点击回调处理也是不变的;

上述的数据集合我们可以默认是List集合,那么真正变化的其实是数据类型。
再来说一下ViewHolder,ViewHolder中看似我们每次都要针对不同布局来进行初始化操作,但是自定义ViewHolder中其实真正变化的是具体的子控件的引用持有和获取。那么,唯一的变化就在于控件不同,如果我们将控件放入到map中,以其id为key,实例为value,那么这里就相当简单了,也可以看做是不变的内容。那么,上面提到的onCreateViewHolder中VIewHolder的创建便也是固定的。

好,一个个来。

先抽象出ViewHolder:

public class RecyclerViewHolder extends RecyclerView.ViewHolder {

    private SparseArray<View> mViews;

    RecyclerViewHolder(View itemView) {
        super(itemView);
        this.mViews = new SparseArray<>();
    }

    View getView(int viewId) {
        View view = mViews.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            mViews.put(viewId, view);
        }
        return view;
    }
}

很简单,我们采用Android中的SpareArray来缓存view的引用,该类避免了自动包装并且数据结构不依赖于别的实体对象。只需要在需要view的时候使用getView(int id)即可,该方法首先会从缓存中查找是否存在该view,如果存在则直接返回,不存在才会去findViewById并进行缓存。这样,就可以应对所有的子View的引用变化。

再来看一下RecyclerView.Adapter的抽象:

package com.example.davidchen.blogdemo.adapter;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

/**
 * RecyclerView adapter基类
 * <p>
 * 封装了数据集合以及ItemView的点击事件回调,同时暴露 {@link #onBindData(RecyclerViewHolder, Object, int)}
 * 用于数据与view绑定
 *
 * @param <T> A data bean class that will be used by the adapter.
 *            <p>
 *            Created by DavidChen on 2018/5/30.
 */

abstract class BaseRecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerViewHolder> implements View.OnClickListener {

    private Context mContext;
    private List<T> mData;
    private int mLayoutId;
    private OnItemClickListener mListener;

    BaseRecyclerViewAdapter(Context context, List<T> data, int layoutId) {
        this.mContext = context;
        this.mData = data;
        this.mLayoutId = layoutId;
    }

    @Override
    public RecyclerViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(mLayoutId, parent, false);
        view.setOnClickListener(this);
        return new RecyclerViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerViewHolder holder, int position) {
        holder.itemView.setTag(position);
        T bean = mData.get(position);
        onBindData(holder, bean, position);
    }

    @Override
    public int getItemCount() {
        return mData.size();
    }

    @Override
    public void onClick(View v) {
        if (mListener != null) {
            mListener.onItemClick(this, v, (Integer) v.getTag());
        }
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        this.mListener = onItemClickListener;
    }

    protected abstract void onBindData(RecyclerViewHolder holder, T bean, int position);

    public interface OnItemClickListener {
        void onItemClick(RecyclerView.Adapter adapter, View v, int position);
    }
}

如上所示,
我们先用泛型来解决数据类型不一致的问题,并且在构造方法中就要求初始化布局文件的参数。
在onCreateViewHolder中直接通过构造函数中获得的布局来创建view,并且添加OnClickListener,最后再返回上面抽象出来的RecyclerViewHolder类。
在onBindViewHolder中设置Tag,并且获取到数据bean,并且通过抽象方法onBindData(RecyclerViewHolder, T, int)给以后扩展绑定数据提供便捷。
getItemCount中直接给出集合的大小。
同时在ItemView的OnClick事件中调用暴露给用户的OnItemClickListener接口,以便用户可以轻松的实现item的点击事件。

三、总结

综上我们可以得到一个扩展性良好的RecyclerView的通用Adapter,以后便不再需要繁琐的干着重复的操作了。
给出一个我写的具体的扩展类,看起来还是相当简洁的:

/**
 * 清单列表adapter
 * <p>
 * Created by DavidChen on 2018/5/30.
 */

public class InventoryAdapter extends BaseRecyclerViewAdapter<Inventory> {

    public InventoryAdapter(Context context, List<Inventory> data) {
        super(context, data, R.layout.item_inventroy);
    }

    @Override
    protected void onBindData(RecyclerViewHolder holder, Inventory bean, int position) {
        ((TextView) holder.getView(R.id.tv_item_desc)).setText(bean.getItemDesc());
        String quantity = bean.getQuantity() + "箱";
        ((TextView) holder.getView(R.id.tv_quantity)).setText(quantity);
        String detail = bean.getItemCode() + "/" + bean.getDate();
        ((TextView) holder.getView(R.id.tv_detail)).setText(detail);
        String volume = bean.getVolume() + "方";
        ((TextView) holder.getView(R.id.tv_volume)).setText(volume);
    }
}



好了,感兴趣可以到GitHub上查看下载 源码



  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值