理解RecyclerView(四)—自定义点击事件、万能ViewHolder和简单封装

前言:人不需要活太多样子,你认真做一件事,就会解释所有的事。        ——《不必交谈时刻》

一、概述

  上一篇文章RecyclerView(三)中实现了如何添加分割线、动画效果、拖拽和侧滑删除效果,但是在使用的Adapter是没有封装过的,每次都要重写相关方法,下面我们会对Adapter进行简单的封装。RecyclerView的item的相关点击事件也需要自己高度自定义,不像ListView那样item点击事件能快速响应。

二、自定义点击事件

  RecyclerView并没有像ListView那样暴露出item的点击事件和长按事件处理得API,也就是说在使用RecyclerView的时候需要我们自己来实现item的点击事件和长按事件的处理。我们通常都是在绑定ViewHolder的时候设置监听,然后通过Adapter回调。

2.1 自定义点击事件接口

我们需要自定义点击事件监听回调的接口,通过接口将点击事件传递出去:

 	//1.定义变量接收接口
    private OnItemClickListener mOnItemClickListener;
    //2.定义接口:点击事件
    public interface OnItemClickListener {
        void onItemClick(View view, int position);//单击
        void onItemLongClick(View view, int position);//长按
    }
    //3.设置接口接收的方法
    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

首先,定义接口Interface OnItemClickListener,并定义接口需要实现的方法;接着定义接收接口的变量mOnItemClickListener,当接口实例存在时,能直接实用该接口; 最后定义接收接口的方法setOnItemClickListener(),给定义的接口赋值。

2.2 onBindViewHolder设置点击事件监听

onBindViewHolder()中方法中对item设置单击和长按点击事件,这里需要判断一下mOnItemClickListener是否为null。

  	@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
        ViewHolder viewHolder = (ViewHolder) holder;
        //点击事件
        viewHolder.mCl_root.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickListener != null) mOnItemClickListener.onItemClick(v, position);
            }
        });
        //长按事件
        viewHolder.mCl_root.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (mOnItemClickListener != null) mOnItemClickListener.onItemLongClick(v, position);
                //true表示此事件已消费,不会触发单击事件,false表示两个回调都会触发
                return false;
            }
        });
    }

2.3 Adapter设置点击事件回调

在Activity中,adapter实例设置事件点击回调,回调点击事件和长按事件,在回调方法中实现相关逻辑:

 //设置点击事件
adapter.setOnItemClickListener(new ItemClickAdapter.OnItemClickListener() {
    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(ItemClickActivity.this, adapter.getData().get(position) + "点击事件", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemLongClick(View view, int position) {
        Toast.makeText(ItemClickActivity.this, adapter.getData().get(position) + "长按事件", Toast.LENGTH_SHORT).show();
    }
});

效果如下:

其实自定义点击事件主要是需要自己定义点击事件的接口,然后设置到相关控件中使用,最后实现点击事件的方法就可以了。(源码在文章最后给出)

三、万能ViewHolder

  ViewHolder的作用就是用来保存控件常量和查找控件(findViewById()),因为RecyclerView的item也是复用的,在item复用的时候也要每次都去查找控件,这样比较浪费性能,项目中一般都是使用SparseArray来保存已经查找的控件,如果有则直接取出来使用,没有再去findViewById()查找,保存在SparseArray中,方便下次使用。(源码在文章最后给出)

public class BaseViewHolder extends RecyclerView.ViewHolder {
    // SparseArray 比 HashMap 更省内存,在某些条件下性能更好,只能存储 key 为 int 类型的数据,
    // 用来存放 View 以减少 findViewById 的次数
    private SparseArray<View> viewSparseArray;
    
    //这个是item的对象
    private View mItemView;

    public BaseViewHolder(View itemView) {
        super(itemView);
        this.mItemView = itemView;
        viewSparseArray = new SparseArray<>();
    }
    
    /**
     * 根据 ID 来获取 View
     * @param viewId viewID
     * @param <T>    泛型
     * @return 将结果强转为 View 或 View 的子类型
     */
    public <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;
    }
    
   //获取item的对象
    public View getItemView() {
        return mItemView;
    }
}

将adapter中的Holder替换为BaseViewHolder,就不需要每次实现adapter的时候都来实例一个holder,使用万能Holder能节省时间同时能让代码更简洁,具体使用下面会给出代码。通过BaseViewHolder获取实例holder后就可以getView()方法获取item里面的任何View。(源码在文章最后给出)

 TextView tv_name = holder.getView(R.id.tv_name);

四、万能适配器

  因为每次创建Adapter继承RecyclerView.Adapter的话比较都要实现onCreateViewHolder(),onBindViewHolder(),getItemCount()等必要的相关方法,这里面有部分代码每次都要重复写,我们可以写一个基类,将重复的代码写在里面,不重复和不一样的部分通过抽象方法来实现,定义万能适配器需要继承RecyclerView.Adapter,并实现相关方法,再通过抽象方法getLayoutId()获取布局文件,onBindItemHolder()绑定item数据,以后Adapter继承这个类并实现这两个方法即可。

public abstract class BaseListAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {
    protected Context mContext;
    protected LayoutInflater mInflater;
    protected int mLastPosition = -1;
    protected List<T> mDataList = new ArrayList<>();

    public BaseListAdapter(Context context) {
        mContext = context;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    
    @NonNull
    @Override
    public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new BaseViewHolder(mInflater.inflate(getLayoutId(), parent, false));
    }
    
    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder holder, final int position) {
        onBindItemHolder(holder, position);
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onItemClickListener != null)
                    onItemClickListener.onItemClick(v, position);
            }
        });
    }
    //局部刷新关键:带payload的这个onBindViewHolder方法必须实现
    @Override
    public void onBindViewHolder(@NonNull BaseViewHolder holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            onBindItemHolder(holder, position, payloads);
        }
    }
    //item布局文件
    public abstract int getLayoutId();
    //绑定item数据
    public abstract void onBindItemHolder(BaseViewHolder holder, int position);
    public void onBindItemHolder(BaseViewHolder holder, int position, List<Object> payloads) {}
    
    @Override
    public int getItemCount() {
        return mDataList.size();
    }
    /**
     * 获取List列表数据
     * @return
     */
    public List<T> getDataList() {
        return mDataList;
    }
    /**
     * 设置数据
     * @param list
     */
    public void setDataList(Collection<T> list) {
        mLastPosition = -1;
        clear();
        mDataList.addAll(list);
        notifyDataSetChanged();
    }
    /**
     * 添加数据
     * @param list
     */
    public void addAll(Collection<T> list) {
        int lastIndex = this.mDataList.size();
        if (this.mDataList.addAll(list)) {
            notifyItemRangeInserted(lastIndex, list.size());
        }
    }
    /**
     * 移除某条数据
     * @param position
     */
    public void remove(int position) {
        this.mDataList.remove(position);
        notifyItemRemoved(position);

        if (position != (getDataList().size())) { // 如果移除的是最后一个,忽略
            notifyItemRangeChanged(position, this.mDataList.size() - position);
        }
    }
    /**
     * 清空列表
     */
    public void clear() {
        mDataList.clear();
        notifyDataSetChanged();
    }
    public OnItemClickListener onItemClickListener;
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.onItemClickListener = listener;
    }
}

BaseListAdapter<T>使用泛型来定义数据的实体,因为每个adapter的数据类型都是不一样的,使用泛型能适配所有的数据,并且通过泛型的形式将BaseViewHolder传递给RecyclerView.Adapter那么在adapter中就能直接使用BaseViewHolder实例。
通过BaseAdapter的封装,能减少很多重复而必要的代码,实现方法共用,只需要将不同的部分通过抽象方法抽离出来实现即可,大大提高开发效率。(源码在文章最后给出)

五、综合使用

创建Adapter时继承上面的BaseAdapter基类,只需要实现getLayoutId()onBindItemHolder()方法即可,将ViewHolder替换为BaseViewHolder;
通过泛型传入Goods数据实体。

public class ItemClickAdapter extends BaseListAdapter<Goods> {
    //item子类点击事件
    private OnItemChildClickListener mOnItemChildClickListener;
    public void setOnItemChildClickListener(OnItemChildClickListener onItemChildClickListener) {
        mOnItemChildClickListener = onItemChildClickListener;
    }
    
    public ItemClickAdapter(Context context) {
        super(context);
    }

    @Override
    public int getLayoutId() {
        return R.layout.item_normal;
    }

    @Override
    public void onBindItemHolder(BaseViewHolder holder, final int position) {
        List<Goods> dataList = getDataList();
        Goods goods = dataList.get(position);

        TextView tv_name = holder.getView(R.id.tv_name);
        tv_name.setText(goods.getName());

        holder.setText(R.id.tv_price, "$:" + goods.getPrice())
                .setText(R.id.tv_des, goods.getDes())
                .setOnClickListener(R.id.iv_head, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mOnItemChildClickListener != null)
                            mOnItemChildClickListener.onItemChildClick(v, position);
                    }
                });
    }
}

Activity中设置数据,代码如下:

public class ItemClickActivity extends AppCompatActivity implements OnItemClickListener, OnItemChildClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);
        RecyclerView recyclerView = findViewById(R.id.recyclerView);

        List<Goods> goodsList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Goods goods = new Goods("第 " + i + " 个item", i * 100, "商品描述");
            goodsList.add(goods);
        }

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        ItemClickAdapter adapter = new ItemClickAdapter(this);
        recyclerView.setAdapter(adapter);
        adapter.setDataList(goodsList);

        //设置点击事件
        adapter.setOnItemClickListener(this);
        adapter.setOnItemChildClickListener(this);
    }

    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(this, "item点击事件", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onItemChildClick(View view, int position) {
        switch (view.getId()) {
            case R.id.iv_head:
                Toast.makeText(this, "item子类点击事件: 头像", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

点击事件接口已经分别抽取为一个单独的类,方便复用:

//item点击事件
public interface OnItemClickListener {
    void onItemClick(View view, int position);
}
//item子类点击事件
public interface OnItemChildClickListener {
    void onItemChildClick(View view, int position);
}

效果如下:
在这里插入图片描述
源码地址

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才

我是suming,感谢各位的支持和认可,您的点赞、评论、收藏【一键三连】就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !

要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!

相关文章:

理解RecyclerView(一)

 ● RecyclerView的基础使用、网格布局、瀑布流布局

理解RecyclerView(二)

 ● RecyclerView的ItemType(不同条目类型)

理解RecyclerView(三)

 ● RecyclerView的ItemDecoration分割线、增删item动画效果、拖拽和侧滑删除功能

理解RecyclerView(四)

 ● RecyclerView的自定义点击事件、万能ViewHolder和Adapter简单封装

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值