美文网首页Android知识Android开发Android技术知识
Android 解析RecyclerView(1)——带点击事件

Android 解析RecyclerView(1)——带点击事件

作者: 业志陈 | 来源:发表于2017-06-07 08:23 被阅读182次

    在之前我已经写过一篇关于设计通用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

    相关文章

      网友评论

        本文标题:Android 解析RecyclerView(1)——带点击事件

        本文链接:https://www.haomeiwen.com/subject/whlofxtx.html