RecyclerView的封装和使用(一)

概述

最近学习了一下RecyclerView的封装与使用,感觉确实颇为神奇,真的节省了很多时间与代码量,特记录一下,以免自己的遗忘,也想帮助跟我一样的菜鸟,度过难关。


下面是取自偶像鸿洋大神的博客中的一段话,重点是介绍RecyclerView的基本用法和其方法的含义。

张鸿洋的博客

RecyclerView出现已经有一段时间了,想i性能大家肯定不陌生了,大家可以通过导入support-v7对其进行使用。
据官方的介绍,该控件用于在有限的窗口中展示大量数据集,其实这样功能的空间我们并不陌生,例如:ListView、GirdView。
那么有了ListView、GirdView。
那么有了ListView、GirdView为什么还需要RecyclerView这样的控件呢?整体上看RecyclerView架构,提供了一种插拔式的体验,高度的解耦,异常的灵活,通过设置它提供的不同LayoutManager,ItemDecoration,ItemAnimator实现令人瞠目的效果。

  • 你想要控制其显示的方式,请通过布局管理器LayoutManager
  • 你想要控制Item间的间隔(可绘制),请通过ItemDecoration
  • 你想要控制Item增删的动画,请通过ItemAnimator
  • 你想要控制点击、长按事件,请自己写。

基本使用

鉴于我们对于ListView的使用特别的熟悉,对比下Recycler的使用代码:

mRecyclerView = findView(R.id.id_recyclerview);
//设置布局管理器
mRecyclerView.setLayoutManager(layout);
//设置adapter
mRecyclerView.setAdapter(adapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
                getActivity(), DividerItemDecoration.HORIZONTAL_LIST));

ok,相比较于ListView的代码,ListView可能只需要去设置一个adapter就能正常使用了,而RecyclerView基本需要上面一系列的步骤,那么为什么会添加这么多的步骤呢?

那么就必须解释下RecyclerView的这个名字了,从它类名上看,RecyclerView代表的意义是,我只管Recycler View ,也就是说RecyclerView只管回收与复用View,其他的你可以自己去设置。可以看出其高度的解耦,给与你充分的定制自由
(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。

核心解密

我们的RecyclerView最重要的还是RecyclerAdapter方法,我们书写时是继承RecyclerView.Adapter的,并且需要传递一个ViewHolder。并且强制需要复写三个方法

创建ViewHolder

public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType)

将数据绑定至ViewHolder

 public void onBindViewHolder(MyViewHolder holder, int position)

获取总的条目数

  public int getItemCount()

可见,RecyclerView对ViewHolder也进行了一定的封装,但是如果你仔细观察,你会发出一个疑问,ListView里面有个getView返回View为Item的布局,那么这个Item的样子在哪控制呢?其实是这样的,我们创建的ViewHolder必须继承RecyclerView.ViewHolder,这个RecyclerView.ViewHolder的构造时必须传入一个View,这个View相当与我们ListVIew getView中的convertView(即: 我们需要inflate的item布局需要传入)。
还有一点,ListView中convertView是复用的,在Recycler中,是把ViewHolder作为缓存的单位了,然后convertView作为ViewHolde的成员变量保持在ViewHolder中,也就是说,假设屏幕显示10条数据,则会创建10个ViewHolder缓存起来,每次复用的是ViewHolder,所以他把getView这个方法变为了onCreateViewHolder。

开始封装

通常我们的项目中有许许多多的列表,如果未经封装的话很有可能要重复写很多很多的Adapter,里面又大部分是重复的代码,而且这些Adapter和ViewHolder所做的事情又非常的像:视图绑定,数据绑定,点击事件分发等等,我们的目的是可以尽量复用我们的Adapter,所以我选择把Adapter设置成如下样式。这样做到我们基本可以复用一个Adapter,并且可以根据传入的泛型去改变Adapter最终显示的样子。

public abstract class RecyclerAdapter<Data>
        extends RecyclerView.Adapter

ViewHolder的显示样式也是采用泛型的策略。因为最终的显示是由ViewHolder所绑定的那个View所决定的,所以真正的ViewHoldr需要外面的使用者传递给我们。

/**
     * 自定义的ViewHolder
     *
     * @param <Data> 范型类型
     */
    public static abstract class ViewHolder<Data> extends RecyclerView.ViewHolder {
        private Unbinder unbinder;
        private AdapterCallback<Data> callback;
        protected Data mData;

        public ViewHolder(View itemView) {
            super(itemView);
        }

        /**
         * 用于绑定数据的触发
         *
         * @param data 绑定的数据
         */
        void bind(Data data) {
            this.mData = data;
            onBind(data);
        }

        /**
         * 当触发绑定数据的时候的回掉;必须复写
         *
         * @param data 绑定的数据
         */
        protected abstract void onBind(Data data);

        /**
         * Holder自己对自己对应的Data进行更新操作
         *
         * @param data Data数据
         */
        public void updateData(Data data) {
            if (this.callback != null) {
                this.callback.update(data, this);
            }
        }
    }

接口是发生数据改变时的回调,定义为如下样式

public interface AdapterCallback<Data> {
    void update(Data data, RecyclerAdapter.ViewHolder<Data> holder);
}

剩下的就是我们一些简单的点击事件的回调,以及List增加数据的操作

整体的封装

package net.qiujuer.italker.common.widget.recycler;

import android.support.annotation.LayoutRes;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import net.qiujuer.italker.common.R;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * @author HFRX hfrx1314@qq.com
 * @version 1.0.0
 */
@SuppressWarnings({"unchecked", "unused"})
public abstract class RecyclerAdapter<Data>
        extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder<Data>>
        implements View.OnClickListener, View.OnLongClickListener,
        AdapterCallback<Data> {
    private final List<Data> mDataList;
    private AdapterListener<Data> mListener;

    /**
     * 构造函数模块
     */
    public RecyclerAdapter() {
        this(null);
    }

    public RecyclerAdapter(AdapterListener<Data> listener) {
        this(new ArrayList<Data>(), listener);
    }

    public RecyclerAdapter(List<Data> dataList, AdapterListener<Data> listener) {
        this.mDataList = dataList;
        this.mListener = listener;
    }

    /**
     * 复写默认的布局类型返回
     *
     * @param position 坐标
     * @return 类型,其实复写后返回的都是XML文件的ID
     */
    @Override
    public int getItemViewType(int position) {
        return getItemViewType(position, mDataList.get(position));
    }

    /**
     * 得到布局的类型
     *
     * @param position 坐标
     * @param data     当前的数据
     * @return XML文件的ID,用于创建ViewHolder
     */
    @LayoutRes
    protected abstract int getItemViewType(int position, Data data);

    /**
     * 创建一个ViewHolder
     *
     * @param parent   RecyclerView
     * @param viewType 界面的类型,约定为XML布局的Id
     * @return ViewHolder
     */
    @Override
    public ViewHolder<Data> onCreateViewHolder(ViewGroup parent, int viewType) {
        // 得到LayoutInflater用于把XML初始化为View
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        // 把XML id为viewType的文件初始化为一个root View
        View root = inflater.inflate(viewType, parent, false);
        // 通过子类必须实现的方法,得到一个ViewHolder
        ViewHolder<Data> holder = onCreateViewHolder(root, viewType);


        // 设置View的Tag为ViewHolder,进行双向绑定
        root.setTag(R.id.tag_recycler_holder, holder);
        // 设置事件点击
        root.setOnClickListener(this);
        root.setOnLongClickListener(this);

        // 进行界面注解绑定
        holder.unbinder = ButterKnife.bind(holder, root);
        // 绑定callback
        holder.callback = this;

        return holder;
    }

    /**
     * 得到一个新的ViewHolder
     *
     * @param root     根布局
     * @param viewType 布局类型,其实就是XML的ID
     * @return ViewHolder
     */
    protected abstract ViewHolder<Data> onCreateViewHolder(View root, int viewType);

    /**
     * 绑定数据到一个Holder上
     *
     * @param holder   ViewHolder
     * @param position 坐标
     */
    @Override
    public void onBindViewHolder(ViewHolder<Data> holder, int position) {
        // 得到需要绑定的数据
        Data data = mDataList.get(position);
        // 触发Holder的绑定方法
        holder.bind(data);
    }

    /**
     * 得到当前集合的数据量
     */
    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    /**
     * 返回整个集合
     * @return List<Data>
     */
    public List<Data> getItems(){
        return mDataList;
    }
    /**
     * 插入一条数据并通知插入
     *
     * @param data Data
     */
    public void add(Data data) {
        mDataList.add(data);
        notifyItemInserted(mDataList.size() - 1);
    }

    /**
     * 插入一堆数据,并通知这段集合更新
     *
     * @param dataList Data
     */
    public void add(Data... dataList) {
        if (dataList != null && dataList.length > 0) {
            int startPos = mDataList.size();
            Collections.addAll(mDataList, dataList);
            notifyItemRangeInserted(startPos, dataList.length);
        }
    }

    /**
     * 插入一堆数据,并通知这段集合更新
     *
     * @param dataList Data
     */
    public void add(Collection<Data> dataList) {
        if (dataList != null && dataList.size() > 0) {
            int startPos = mDataList.size();
            mDataList.addAll(dataList);
            notifyItemRangeInserted(startPos, dataList.size());
        }
    }

    /**
     * 删除操作
     */
    public void clear() {
        mDataList.clear();
        notifyDataSetChanged();
    }

    /**
     * 替换为一个新的集合,其中包括了清空
     *
     * @param dataList 一个新的集合
     */
    public void replace(Collection<Data> dataList) {
        mDataList.clear();
        if (dataList == null || dataList.size() == 0)
            return;
        mDataList.addAll(dataList);
        notifyDataSetChanged();
    }

    @Override
    public void update(Data data, ViewHolder<Data> holder) {
        int pos = holder.getAdapterPosition();
        if(pos>=0){
            mDataList.remove(pos);
            mDataList.add(pos,data);
            notifyItemChanged(pos);
        }
    }

    @Override
    public void onClick(View v) {
        ViewHolder viewHolder = (ViewHolder) v.getTag(R.id.tag_recycler_holder);
        if (this.mListener != null) {
            // 得到ViewHolder当前对应的适配器中的坐标
            int pos = viewHolder.getAdapterPosition();
            // 回掉方法
            this.mListener.onItemClick(viewHolder, mDataList.get(pos));
        }

    }

    @Override
    public boolean onLongClick(View v) {
        ViewHolder viewHolder = (ViewHolder) v.getTag(R.id.tag_recycler_holder);
        if (this.mListener != null) {
            // 得到ViewHolder当前对应的适配器中的坐标
            int pos = viewHolder.getAdapterPosition();
            // 回掉方法
            this.mListener.onItemLongClick(viewHolder, mDataList.get(pos));
            return true;
        }
        return false;
    }

    /**
     * 设置适配器的监听
     *
     * @param adapterListener AdapterListener
     */
    public void setListener(AdapterListener<Data> adapterListener) {
        this.mListener = adapterListener;
    }

    /**
     * 我们的自定义监听器
     *
     * @param <Data> 范型
     */
    public interface AdapterListener<Data> {
        // 当Cell点击的时候触发
        void onItemClick(RecyclerAdapter.ViewHolder holder, Data data);

        // 当Cell长按时触发
        void onItemLongClick(RecyclerAdapter.ViewHolder holder, Data data);
    }

    /**
     * 自定义的ViewHolder
     *
     * @param <Data> 范型类型
     */
    public static abstract class ViewHolder<Data> extends RecyclerView.ViewHolder {
        private Unbinder unbinder;
        private AdapterCallback<Data> callback;
        protected Data mData;

        public ViewHolder(View itemView) {
            super(itemView);
        }

        /**
         * 用于绑定数据的触发
         *
         * @param data 绑定的数据
         */
        void bind(Data data) {
            this.mData = data;
            onBind(data);
        }

        /**
         * 当触发绑定数据的时候,的回掉;必须复写
         *
         * @param data 绑定的数据
         */
        protected abstract void onBind(Data data);

        /**
         * Holder自己对自己对应的Data进行更新操作
         *
         * @param data Data数据
         */
        public void updateData(Data data) {
            if (this.callback != null) {
                this.callback.update(data, this);
            }
        }
    }

    /**
     * 对回调接口做一次实现
     * @param <Data>
     */
    public static abstract class AdapterListenerImpl<Data> implements  AdapterListener<Data>{

        @Override
        public void onItemClick(ViewHolder holder, Data data) {

        }

        @Override
        public void onItemLongClick(ViewHolder holder, Data data) {

        }
    }
}

拆解

其实大家在处理ListView的时候就会碰到很多图片显示错乱的问题,或者说是点击事件错乱的问题。大都可以通过一个setTag去解决。但是我个人就一直有个疑问,我知道要用setTag,我知道setTag可以解决图片错乱的问题,但是为啥呢,怎么解决的呢。

简单的说,Recycler就是把数据显示在View上的一个控件,他关注View的Recycler,具有复用View的功能;而对于对应关系,我们希望能够做到的是:当知道数据集合的一个Item的时候能够访问到对应的View,并更新View的信息,比如我们更新了数据的name字段,name我们希望更新View对应的界面;这个过程recycler已经给我们搞定了,name还有一种情况,什么呢?就是当我们操作界面的时候比如我们在界面的某个View上点击了一下,此时我们希望能够一下就知道我们点击的是哪一个数据;好的此时因为View的复用性,他可能对应任意数据;那么当前对应的是什么呢?接口上来说并不提供,所以我们利用View的tag机制,当进行数据绑定的时候把数据绑定到View的tag上,那么当我们操作View的时候就可以从View的tag中拿出对应的数据出来,从而实现快速的操作,减少不必要的循环查询,以及错位的情况,这就是类似于双向绑定的开发模式。

/**
     * Sets a tag associated with this view and a key. A tag can be used
     * to mark a view in its hierarchy and does not have to be unique within
     * the hierarchy. Tags can also be used to store data within a view
     * without resorting to another data structure.
     *
     * The specified key should be an id declared in the resources of the
     * application to ensure it is unique (see the <a
     * href="{@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>).
     * Keys identified as belonging to
     * the Android framework or not associated with any package will cause
     * an {@link IllegalArgumentException} to be thrown.
     *
     * @param key The key identifying the tag
     * @param tag An Object to tag the view with
     *
     * @throws IllegalArgumentException If they specified key is not valid
     *
     * @see #setTag(Object)
     * @see #getTag(int)
     */
public void setTag(int key, final Object tag) {
        // If the package id is 0x00 or 0x01, it's either an undefined package
        // or a framework id
        if ((key >>> 24) < 2) {
            throw new IllegalArgumentException("The key must be an application-specific "
                    + "resource id.");
        }

        setKeyedTag(key, tag);
    }

setTag有一长串的解释,大致意思就是说我们可以设置一个Tag,将View与Key关联起来,这个tag可以被使用去标记一个View在他的数据结构中。最简单的方法就是创建一个id在Resource中。
在往里就是这个方法了,去更新对应的Tag,这样就把View和tag绑定并存储在了一个数据结构中,我们通过getTag的时候,就会去拿跟这个tag绑定的View。所以就一般不会出现错乱的问题。

 private void setKeyedTag(int key, Object tag) {
        if (mKeyedTags == null) {
            mKeyedTags = new SparseArray<Object>(2);
        }

        mKeyedTags.put(key, tag);
    }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值