Android 解析RecyclerView(1)——带点击事件监听的通用Adapter

在之前我已经写过一篇关于设计通用Adapter的文章了:Android RecyclerView设计通用Adapter

按照最原始的做法,构建一个RecyclerView Adapter需要写挺多代码的,而通过对Java泛型的使用,可以使代码量变得只需十几行即可。此外,由于RecyclerView不像ListView那样支持直接添加顶部View,也不支持直接添加点击事件监听,需要开发者自己通过改造RecyclerView或者RecyclerView Adapter来完成需求。 我打算通过写三篇文章来对设计“带点击事件监听的通用Adapter”和“带顶部View和底部View的RecyclerView”进行介绍。

本文是对设计通用Adapter的介绍

一、ViewHolder

在使用RecyclerView的过程中,需要用到ViewHolder,在当中对控件进行初始化并赋予数据 为了避免每次加RecyclerView的子项时都要对View进行查找,可以使用SparseArray来存储View,key值是View ID,Value值则是View

    //用来存放View以减少findViewById的次数
    private SparseArray<View> viewSparseArray;

则每次查找View时,先从缓存中查找,如果缓存中存在则直接返回View,否则还是进行findViewById,找到了后再把View存入缓存 利用泛型也可以使返回的View自动转型

  /**
     * 根据 ID 来获取 View
     *
     * @param viewId viewID
     * @param <T>    泛型
     * @return 将结果强转为 View 或 View 的子类型
     */
    private  <T extends View> T getView(int viewId) {
        // 先从缓存中找,找到的话则直接返回
        // 如果找不到则findViewById,再把结果存入缓存中
        View view = viewSparseArray.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            viewSparseArray.put(viewId, view);
        }
        return (T) view;
    }

由于我们使用到的控件一般都是TextView和ImageView,所以默认提供的操作View的方法也是针对这两个控件,可以根据实际情况再来增加

 public CommonRecyclerHolder setText(int viewId, CharSequence text) {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }

    public CommonRecyclerHolder setImageResource(int viewId, int resourceId) {
        ImageView imageView = getView(viewId);
        imageView.setImageResource(resourceId);
        return this;
    }

    public CommonRecyclerHolder setImageResource(int viewId, Bitmap bitmap) {
        ImageView imageView = getView(viewId);
        imageView.setImageBitmap(bitmap);
        return this;
    }

    public CommonRecyclerHolder setViewVisibility(int viewId, int visibility) {
        getView(viewId).setVisibility(visibility);
        return this;
    }

为了使RecyclerView支持点击事件监听,可以在ViewHolder的构造函数中对其进行事件监听设置。首先需要定义一个通用的点击事件监听接口,支持短按点击和长按点击

    public interface onClickCommonListener {

        void onClickListener(int position);

        void onLongClickListener(int position);

    }

在View的真实点击事件监听函数中回调自定义的监听接口

private onClickCommonListener clickCommonListener;

 public CommonRecyclerHolder(View itemView) {
        super(itemView);
        viewSparseArray = new SparseArray<>();
        itemView.setOnClickListener(this);
        itemView.setOnLongClickListener(this);
    }

    public void setClickCommonListener(onClickCommonListener clickCommonListener) {
        this.clickCommonListener = clickCommonListener;
    }

    @Override
    public void onClick(View view) {
        if (clickCommonListener != null) {
            clickCommonListener.onClickListener(getAdapterPosition());
        }
    }

    @Override
    public boolean onLongClick(View view) {
        if (clickCommonListener != null) {
            clickCommonListener.onLongClickListener(getAdapterPosition());
        }
        return true;
    }

总的代码如下所示:

/**
 * 通用ViewHolder
 * Created by ZY on 2017/6/3.
 */
public class CommonRecyclerHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

    public interface onClickCommonListener {

        void onClickListener(int position);

        void onLongClickListener(int position);

    }

    private onClickCommonListener clickCommonListener;

    //用来存放View以减少findViewById的次数
    private SparseArray<View> viewSparseArray;

    public CommonRecyclerHolder(View itemView) {
        super(itemView);
        viewSparseArray = new SparseArray<>();
        itemView.setOnClickListener(this);
        itemView.setOnLongClickListener(this);
    }

    public void setClickCommonListener(onClickCommonListener clickCommonListener) {
        this.clickCommonListener = clickCommonListener;
    }

    @Override
    public void onClick(View view) {
        if (clickCommonListener != null) {
            clickCommonListener.onClickListener(getAdapterPosition());
        }
    }

    @Override
    public boolean onLongClick(View view) {
        if (clickCommonListener != null) {
            clickCommonListener.onLongClickListener(getAdapterPosition());
        }
        return true;
    }

    /**
     * 根据 ID 来获取 View
     *
     * @param viewId viewID
     * @param <T>    泛型
     * @return 将结果强转为 View 或 View 的子类型
     */
    private  <T extends View> T getView(int viewId) {
        // 先从缓存中找,找到的话则直接返回
        // 如果找不到则findViewById,再把结果存入缓存中
        View view = viewSparseArray.get(viewId);
        if (view == null) {
            view = itemView.findViewById(viewId);
            viewSparseArray.put(viewId, view);
        }
        return (T) view;
    }

    public CommonRecyclerHolder setText(int viewId, CharSequence text) {
        TextView tv = getView(viewId);
        tv.setText(text);
        return this;
    }

    public CommonRecyclerHolder setImageResource(int viewId, int resourceId) {
        ImageView imageView = getView(viewId);
        imageView.setImageResource(resourceId);
        return this;
    }

    public CommonRecyclerHolder setImageResource(int viewId, Bitmap bitmap) {
        ImageView imageView = getView(viewId);
        imageView.setImageBitmap(bitmap);
        return this;
    }

    public CommonRecyclerHolder setViewVisibility(int viewId, int visibility) {
        getView(viewId).setVisibility(visibility);
        return this;
    }

}

二、RecyclerView Adapter

接下来需要是来改造 RecyclerView Adapter了

在有些时候,有让RecyclerView的子项呈现不同的布局的需求,这就需要我们在以下方法中使不同子项返回不同的viewType值了

  public int getItemViewType(int position)

然后,根据不同子项的viewType在以下方法中加载不同的布局文件

  public CommonRecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType)

为了使我们自定义的RecyclerView Adapter支持多布局,需要使用到一个接口来返回不同的布局文件

    //多布局支持
    public interface MultiTypeSupport<T> {

        int getLayoutId(T item, int position);

    }

使用泛型来对应不同情况下需要RecyclerView展示的数据集合

    private List<T> dataList;

则 RecyclerView Adapter几个需要重写的方法可以如下定义

@Override
    public int getItemViewType(int position) {
        if (multiTypeSupport != null) {
            return multiTypeSupport.getLayoutId(dataList.get(position), position);
        }
        return super.getItemViewType(position);
    }

    @Override
    public CommonRecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (multiTypeSupport != null) {
            layoutId = viewType;
        }
        View view = layoutInflater.inflate(layoutId, parent, false);
        return new CommonRecyclerHolder(view);
    }

    @Override
    public void onBindViewHolder(CommonRecyclerHolder holder, int position) {
        bindData(holder, dataList.get(position));
        holder.setClickCommonListener(clickCommonListener);
    }

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

声明一个抽象方法,使子类根据实际情况来进行数据绑定

  protected abstract void bindData(CommonRecyclerHolder holder, T data);

为了对应多种情况,比如有时候需要使用到多个布局文件,有时候又需要设置点击事件监听,所以可以声明多个构造函数来对应所有的情况

 /**
     * 私有构造函数
     *
     * @param context  上下文
     * @param dataList 数据集合
     */
    private CommonRecyclerAdapter(Context context, List<T> dataList) {
        this.layoutInflater = LayoutInflater.from(context);
        this.dataList = dataList;
    }

    /**
     * 适用于:列表所有的子项都使用相同的布局文件,且不需要监听点击事件
     *
     * @param context  上下文
     * @param dataList 数据集合
     * @param layoutId 布局文件ID
     */
    protected CommonRecyclerAdapter(Context context, List<T> dataList, int layoutId) {
        this(context, dataList);
        this.layoutId = layoutId;
    }

    /**
     * 适用于:列表的子项使用不同的布局文件,且不需要监听点击事件
     *
     * @param context          上下文
     * @param dataList         数据集合
     * @param multiTypeSupport 支持多个布局文件
     */
    protected CommonRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport) {
        this(context, dataList);
        this.multiTypeSupport = multiTypeSupport;
    }

    /**
     * 适用于:列表所有的子项都使用相同的布局文件,且需要监听点击事件
     *
     * @param context             上下文
     * @param dataList            数据集合
     * @param layoutId            布局文件ID
     * @param clickCommonListener 点击事件监听
     */
    protected CommonRecyclerAdapter(Context context, List<T> dataList, int layoutId,
                                    CommonRecyclerHolder.onClickCommonListener clickCommonListener) {
        this(context, dataList, layoutId);
        this.clickCommonListener = clickCommonListener;
    }

    /**
     * 适用于:列表的子项使用不同的布局文件,且需要监听点击事件
     *
     * @param context             上下文
     * @param dataList            数据集合
     * @param multiTypeSupport    支持多个布局文件
     * @param clickCommonListener 点击事件监听
     */
    protected CommonRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport,
                                    CommonRecyclerHolder.onClickCommonListener clickCommonListener) {
        this(context, dataList, multiTypeSupport);
        this.clickCommonListener = clickCommonListener;
    }

总的代码如下所示:

/**
 * 通用RecyclerView Adapter
 * Created by ZY on 2017/6/3.
 */
public abstract class CommonRecyclerAdapter<T> extends RecyclerView.Adapter<CommonRecyclerHolder> {

    //多布局支持
    public interface MultiTypeSupport<T> {

        int getLayoutId(T item, int position);

    }

    private MultiTypeSupport<T> multiTypeSupport;

    private LayoutInflater layoutInflater;

    private List<T> dataList;

    private int layoutId;

    private CommonRecyclerHolder.onClickCommonListener clickCommonListener;

    /**
     * 私有构造函数
     *
     * @param context  上下文
     * @param dataList 数据集合
     */
    private CommonRecyclerAdapter(Context context, List<T> dataList) {
        this.layoutInflater = LayoutInflater.from(context);
        this.dataList = dataList;
    }

    /**
     * 适用于:列表所有的子项都使用相同的布局文件,且不需要监听点击事件
     *
     * @param context  上下文
     * @param dataList 数据集合
     * @param layoutId 布局文件ID
     */
    protected CommonRecyclerAdapter(Context context, List<T> dataList, int layoutId) {
        this(context, dataList);
        this.layoutId = layoutId;
    }

    /**
     * 适用于:列表的子项使用不同的布局文件,且不需要监听点击事件
     *
     * @param context          上下文
     * @param dataList         数据集合
     * @param multiTypeSupport 支持多个布局文件
     */
    protected CommonRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport) {
        this(context, dataList);
        this.multiTypeSupport = multiTypeSupport;
    }

    /**
     * 适用于:列表所有的子项都使用相同的布局文件,且需要监听点击事件
     *
     * @param context             上下文
     * @param dataList            数据集合
     * @param layoutId            布局文件ID
     * @param clickCommonListener 点击事件监听
     */
    protected CommonRecyclerAdapter(Context context, List<T> dataList, int layoutId,
                                    CommonRecyclerHolder.onClickCommonListener clickCommonListener) {
        this(context, dataList, layoutId);
        this.clickCommonListener = clickCommonListener;
    }

    /**
     * 适用于:列表的子项使用不同的布局文件,且需要监听点击事件
     *
     * @param context             上下文
     * @param dataList            数据集合
     * @param multiTypeSupport    支持多个布局文件
     * @param clickCommonListener 点击事件监听
     */
    protected CommonRecyclerAdapter(Context context, List<T> dataList, MultiTypeSupport<T> multiTypeSupport,
                                    CommonRecyclerHolder.onClickCommonListener clickCommonListener) {
        this(context, dataList, multiTypeSupport);
        this.clickCommonListener = clickCommonListener;
    }

    @Override
    public int getItemViewType(int position) {
        if (multiTypeSupport != null) {
            return multiTypeSupport.getLayoutId(dataList.get(position), position);
        }
        return super.getItemViewType(position);
    }

    @Override
    public CommonRecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (multiTypeSupport != null) {
            layoutId = viewType;
        }
        View view = layoutInflater.inflate(layoutId, parent, false);
        return new CommonRecyclerHolder(view);
    }

    @Override
    public void onBindViewHolder(CommonRecyclerHolder holder, int position) {
        bindData(holder, dataList.get(position));
        holder.setClickCommonListener(clickCommonListener);
    }

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

    protected abstract void bindData(CommonRecyclerHolder holder, T data);

}

三、实际使用

首先定义一个Java Bean,方便进行数据传递

/**
 * Created by ZY on 2017/6/3.
 */
public class Data {

    private int imageResource;

    private String hintText;

    public Data(int imageResource, String hintText) {
        this.imageResource = imageResource;
        this.hintText = hintText;
    }

    public int getImageResource() {
        return imageResource;
    }

    public String getHintText() {
        return hintText;
    }

}

然后定义一个 item1.xml 和 item2.xml 布局文件,来作为RecyclerView的子项要显示的布局文件

item1.xml 的布局代码如下所示, item2.xml 只是更改了背景颜色而已,就不贴出来了

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/iv_head"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:layout_margin="5dp"
            android:src="@mipmap/ic_launcher" />

        <TextView
            android:id="@+id/tv_hintText"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textSize="17sp" />

    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#abc" />

</LinearLayout>

现在我们定义Adapter子类需要写的代码量就很少了,根据实际情况声明需要的构造函数即可,不需要每个都写出来

/**
 * Created by ZY on 2017/6/3.
 */
public class MyCommonRecyclerAdapter extends CommonRecyclerAdapter<Data> {

    public MyCommonRecyclerAdapter(Context context, List<Data> dataList, int layoutId) {
        super(context, dataList, layoutId);
    }

    public MyCommonRecyclerAdapter(Context context, List<Data> dataList, MultiTypeSupport<Data> multiTypeSupport) {
        super(context, dataList, multiTypeSupport);
    }

    public MyCommonRecyclerAdapter(Context context, List<Data> dataList, int layoutId, CommonRecyclerHolder.onClickCommonListener clickCommonListener) {
        super(context, dataList, layoutId, clickCommonListener);
    }

    public MyCommonRecyclerAdapter(Context context, List<Data> dataList, MultiTypeSupport<Data> multiTypeSupport, CommonRecyclerHolder.onClickCommonListener clickCommonListener) {
        super(context, dataList, multiTypeSupport, clickCommonListener);
    }

    @Override
    protected void bindData(CommonRecyclerHolder holder, Data data) {
        holder.setImageResource(R.id.iv_head, data.getImageResource())
                .setText(R.id.tv_hintText, data.getHintText());
    }

}

然后,在Activity中声明使用

Activity的布局文件如下所示,两个按钮分别用来增添和删除数据

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.czy.demo.MainActivity">

    <Button
        android:id="@+id/btn_addData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="增添数据" />

    <Button
        android:id="@+id/btn_deleteData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/btn_addData"
        android:text="删除数据" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_singleDataList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/btn_deleteData" />

</RelativeLayout>

Activity的所有代码如下所示:

public class SingleLayoutActivity extends AppCompatActivity implements CommonRecyclerHolder.onClickCommonListener, View.OnClickListener {

    private List<Data> dataList;

    private MyCommonRecyclerAdapter myCommonRecyclerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_single_layout);
        dataList = new ArrayList<>();
        initData();
        RecyclerView rv_singleDataList = (RecyclerView) findViewById(R.id.rv_singleDataList);
        rv_singleDataList.setLayoutManager(new LinearLayoutManager(this));
        myCommonRecyclerAdapter = new MyCommonRecyclerAdapter(this, dataList, R.layout.item, this);
        rv_singleDataList.setAdapter(myCommonRecyclerAdapter);
        findViewById(R.id.btn_addData).setOnClickListener(this);
        findViewById(R.id.btn_deleteData).setOnClickListener(this);
    }

    private void initData() {
        for (int i = 0; i < 50; i++) {
            Data data = new Data(R.mipmap.ic_launcher_round, "Hi:" + i);
            dataList.add(data);
        }
    }

    @Override
    public void onClickListener(int position) {
        Toast.makeText(this, "点击:" + position, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onLongClickListener(int position) {
        Toast.makeText(this, "长按:" + position, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_addData:
                Data data = new Data(R.mipmap.ic_launcher, "Hi");
                dataList.add(data);
                myCommonRecyclerAdapter.notifyDataSetChanged();
                break;
            case R.id.btn_deleteData:
                if (dataList.size() > 0) {
                    dataList.remove(0);
                }
                myCommonRecyclerAdapter.notifyDataSetChanged();
                break;
        }
    }
}

运行效果如下所示:

这里写图片描述

在上面RecyclerView使用的是相同的布局文件,可以在声明myCommonRecyclerAdapter变量时使用另一个构造函数即可支持显示多布局

        MyCommonRecyclerAdapter myCommonRecyclerAdapter = new MyCommonRecyclerAdapter(this, dataList, new CommonRecyclerAdapter.MultiTypeSupport<Data>() {
            @Override
            public int getLayoutId(Data item, int position) {
                if (position % 2 == 0) {
                    return R.layout.item;
                }
                return R.layout.item2;
            }
        }, this);

运行效果如下所示:

这里写图片描述

这里提供代码下载:解析RecyclerView(1)——带点击事件监听的通用Adapter

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

仿淘宝收货地址,本地数据库

谁说咱们攻城狮不能写出既幽默又能懂的博客呢,本人想推出一系列博文,可以给刚接触Android开发的做一个参考,也可以与接触Android已久的各路大神比较一下,...

1.2K7
来自专栏Android中高级开发

Android开发之漫漫长途 XV——RecyclerView

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

1432
来自专栏何俊林

Android支付实践(二)之微信支付详解与Demo

前言:集成支持宝和微信支付,是公司获取收益的最主要的部分,这两大巨头几乎在支付业务上不可或缺,今天看下Simon_Crystin独家授权本公众号发步的Andro...

2146
来自专栏移动端开发

Android学习--RecyclerView

       前面一篇总结了ListView,在这篇我们总结一些这个RecyclerView,我们就从最基本的开始,安卓团队是将RecyclerView定义在s...

21410
来自专栏mukekeheart的iOS之旅

Andriod基础——Adapter类

Android是完全遵循MVC模式设计的框架,Activity是Controller,layout是View,因为layout五花八门,很多数据都不能直接绑定上...

3385
来自专栏Android开发指南

2.抽取代码(BaseActivity)

3548
来自专栏androidBlog

Android 自定义 MarqueeView 实现跑马灯 —— 原理篇

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

1201
来自专栏酷玩时刻

Retrofit2+Rxjava+MVP 实践

此博文根据前面两篇文章 Android MVP 架构初试 Android MVP 架构封装 再结合主流框架Retrofit2+Rxjava来个实践(实现聚合网...

2274
来自专栏水击三千

Android应用开发SharedPreferences存储数据的使用方法

SharedPreferences是Android中最容易理解的数据存储技术,实际上SharedPreferences处理的就是一个key-value(键值对)...

3086
来自专栏上善若水

002android初级篇之ViewPager及PagerSlidingTabStrip listview的使用

listView是一个可以用来显示视图列表的控件。 它使用适配器来为之提供数据和资源。

1183

扫码关注云+社区