有关RecyclerView.Adapter 的 notifyItemChanged(int position, @Nullable Object payload) 方法

今天在看某个项目的源码的时候,无意间看到了 RecyclerView.Adapter 的 notifyItemChanged(int position, @Nullable Object payload) 方法,以前没有见过,后来自己查来之后,才知道这个是与 RV 的 Item 局部刷新有关的。

平时一般是用 notifyItemChanged(int position) 方法来进行局部刷新,但是这个局部刷新是通过回调 onBindViewHolder(@NonNull VH holder, int position) 该方法刷新指定的 item 的整个视图,如果该视图里面包含图片什么的,如果逻辑处理不当会导致图片也被重新刷新而使视图在效果闪一下。

而如果使用 notifyItemChanged(int position, @Nullable Object payload) 方法的话,则会回调 onBindViewHolder(@NonNull VH holder, int position,@NonNull List<Object> payloads) 方法,然后再该方法中就可以使用 payloads 参数来进行布局的刷新。


先用一个实例来演示一下:
假设 RV 中的 Item 包括一个 ImageView、两个 TextView,然后整个 Item 设置了一个点击事件,当 Item 被点击之后,就要为内部对应的两个 TextView 更新一下文本信息,如下图所示:
这里写图片描述

然后是省略之后的主要代码:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private List<ItemBean> data;

    ......

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        ItemBean itemBean=data.get(position);
        holder.tv1.setText(itemBean.str1);
        holder.tv2.setText(itemBean.str2);
        holder.iv.setImageResource(R.mipmap.ic_launcher);
    }

    //要注意,当上下滑动 RV 导致 Item 因复用更新视图时,也会走该方法的,但是此时 payloads 为空
    //所以应该注意在该方法中刷新的状态应该保存到 data 对应的 bean 中,使更新时能够读取到正确的状态而展现出正确的效果
    //否则在上下滑动后,原位置的 Item 效果会退回到局部更新之前
    @Override
    public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            //payloads 为 空,说明是更新整个 ViewHolder 
            onBindViewHolder(holder, position);
        } else {
            ItemBean itemBean=data.get(position);
            //将需要刷新的状态保存到 ItemBean 对应的成员变量中
            itemBean.str1 += (String) payloads.get(0);
            itemBean.str2 += (String) payloads.get(1);
            holder.tv1.setText(itemBean.str1);
            holder.tv2.setText(itemBean.str2);
        }
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        final TextView tv1,tv2;
        final ImageView iv;

        public ViewHolder(View itemView) {
            super(itemView);
            tv1 = itemView.findViewById(R.id.tv1);
            tv2 = itemView.findViewById(R.id.tv2);
            iv = itemView.findViewById(R.id.iv);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    notifyItemChanged(getAdapterPosition()," update for 1");
                    notifyItemChanged(getAdapterPosition()," update for 2");
                }
            });
        }
    }
}

这里需要注意的是,在 onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) 中布局刷新时,同时需要将刷新的状态保存到对应的 bean 中,否则在上下滑动后,原 position 位置的 item 会退回到未局部刷新之前的状态。就像下图的效果:
这里写图片描述

@Override
    public void onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            ItemBean itemBean=data.get(position);
//            itemBean.str1 += (String) payloads.get(0);
//            itemBean.str2 += (String) payloads.get(1);
//            holder.tv1.setText(itemBean.str1);
//            holder.tv2.setText(itemBean.str2);
            //直接刷新该局部视图,而不保存相应的状态到 bean 中
            holder.tv1.setText(itemBean.str1 + payloads.get(0));
            holder.tv2.setText(itemBean.str2 + payloads.get(1));
        }
    }

之后,需要补充几点:

1、如果重写了 onBindViewHolder(ViewHolder holder, int position, List<Object> payloads) 方法,那么不管是 RV 在初始化时,还是在因为上下滑动而刷新时,都会走该方法(此时 payloads 为空),而不走 onBindViewHolder(ViewHolder holder, int position) 方法,因此,需要完善逻辑,在 payloads 为空时手动调用 onBindViewHolder(holder, position); 或者将原本 onBindViewHolder(holder, position); 的逻辑写到这里。

2、每一个具体的 position 都会在 onBindViewHolder(holder, position, payloads) 方法对应一个单独的 payloads
这一点测试一下即可知道,假设为 ItemView 的点击事件补充如下代码:

itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        notifyItemChanged(getAdapterPosition()," update for 1 ->"+getAdapterPosition());
        notifyItemChanged(getAdapterPosition()," update for 2 ->"+getAdapterPosition());
        //补充的代码
        notifyItemChanged(getAdapterPosition()+1," update for 1 ->"+(getAdapterPosition()+1));
        notifyItemChanged(getAdapterPosition()+1," update for 2 ->"+(getAdapterPosition()+1));
    }
});

则可以看到如下的效果:
这里写图片描述
position 为 1 和 2 分别刷新了对应的视图,且两者没有交叉。

3、假设在 notifyItemChanged(position, payload) 方法中传入的 payload 为 null,则会把 position 对应的 List<Object> payloads 清空,而在调用 onBindViewHolder(ViewHolder,int,List) 之前 notifyItemChanged(position, payload) 将不会起作用。
这个可以在源码的注释中了解到。
这里写图片描述
其中标记处的大致意思是:
notifyItemRangeChanged()中 payload == null 时将清除该项目上的所有现有的 payloads,并阻止将来
payloads 增加元素,直到调用 onBindViewHolder(ViewHolder,int,List)

然后通过实测也可以验证:

itemView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        notifyItemChanged(getAdapterPosition()," update for 1 ->"+getAdapterPosition());
        notifyItemChanged(getAdapterPosition()," update for 2 ->"+getAdapterPosition());
        notifyItemChanged(getAdapterPosition(),null);
        notifyItemChanged(getAdapterPosition()," update for 3 ->"+getAdapterPosition());
        notifyItemChanged(getAdapterPosition()," update for 4 ->"+getAdapterPosition());
    }

将 ItemView 的点击事件改成上述,然后运行,点击 Item,此时虽然会调用 onBindViewHolder(ViewHolder,int,List) ,但是 payloads.size() == 0

4、连续多次的调用 notifyItemChanged(position, payload),但是只会回调一次
onBindViewHolder(ViewHolder,int,List)。具体原因需要分析源码,目前暂不深究。

  • 11
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值