android菜鸟低调修行纪念册安卓UI

recycleview的刷新机制

2018-11-07  本文已影响243人  NoBugException

这篇文章是下面文章的补充
线性recyclerview常规用法

1.如果大量的数据被修改或者被修改数据的位置不确定,则使用

adapter.notifyDataSetChanged();

这个方法很消耗性能,不到万不得已不要使用,请尽量使用下面的刷新方法。

2.刷新某一项(常用)

//刷新某Item中的所有组件
adapter.notifyItemChanged(position);
//刷新某Item中的部分组件
adapter.notifyItemChanged(position, payloads);
//插入Item
adapter.notifyItemInserted(position);       
//删除Item
adapter.notifyItemRemoved(position);  
//移动Item
adapter.notifyItemMoved(position, position + 1);
图片.png

需求1:点击按钮1,使图片和文字改变。

switch (view.getId()){
    case R.id.image:
        Toast.makeText(MainActivity.this, "点击了图片", Toast.LENGTH_SHORT).show();
        break;
    case R.id.button_1:
        Toast.makeText(MainActivity.this, "点击了按钮1", Toast.LENGTH_SHORT).show();
        PhotoBean photoBean = list.get(position);
        photoBean.setName("修改!");
        photoBean.setUrl("http://pic29.nipic.com/20130511/9252150_174018365301_2.jpg");
        adapter.notifyItemChanged(position);
        break;
     case R.id.button_2:
        Toast.makeText(MainActivity.this, "点击了按钮2", Toast.LENGTH_SHORT).show();
         break;
 }
10.gif

需求2:点击按钮2,使按钮2高亮。

首先添加一个clickButton状态

public class PhotoBean {

    private String name;

    private String url;

    private byte clickButton;//0:点击了图片,1:点击了按钮1,2:点击了按钮2

    public PhotoBean(){}

    public PhotoBean(String url, String name, byte clickButton){
        this.url = url;
        this.name = name;
        this.clickButton = clickButton;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public byte getClickButton() {
        return clickButton;
    }

    public void setClickButton(byte clickButton) {
        this.clickButton = clickButton;
    }
}

修改onBindViewHolder方法,将按钮变黄

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        PhotoBean dataListBean = mData.get(position);

        if(dataListBean != null){

            Glide.with(mContext).load(dataListBean.getUrl()).into(((ViewHolder)holder).imgView);
            ((ViewHolder)holder).tvName.setText(dataListBean.getName());

            if(dataListBean.getClickButton() == 2){
                ((ViewHolder)holder).button_2.setBackgroundColor(Color.YELLOW);
            }
        }

        ((ViewHolder)holder).imgView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickLitener != null) {
                    if(mData != null && mData.size() > 0){
                        mOnItemClickLitener.onItemClick(((ViewHolder)holder).imgView, position);
                    }
                }
            }
        });

        ((ViewHolder)holder).button_1.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickLitener != null) {
                    if(mData != null && mData.size() > 0){
                        mOnItemClickLitener.onItemClick(((ViewHolder)holder).button_1, position);
                    }
                }
            }
        });

        ((ViewHolder)holder).button_2.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mOnItemClickLitener != null) {
                    if(mData != null && mData.size() > 0){
                        mOnItemClickLitener.onItemClick(((ViewHolder)holder).button_2, position);
                    }
                }
            }
        });
    }

效果如下:


11.gif

这样貌似已经实现了功能,但是存在两个严重的弊端:
1.我只要将按钮2改变颜色就行了,为什么图片还会刷新?
2.我每次点击按钮2,图片、文字、按钮都要重新排版一次,严重消耗性能,也许现在手机性能比较好,很多人不在乎,但是我在乎。

解决方案:使用payloads实现刷新Item中的部分控件。

修改按钮2点击事件实现

case R.id.button_2:
     Toast.makeText(MainActivity.this, "点击了按钮2", Toast.LENGTH_SHORT).show();
     PhotoBean photoBean2 = new PhotoBean();
     photoBean2.setClickButton((byte) 2);
     adapter.notifyItemChanged(position, photoBean2);
      break;

添加回调方法

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
        if(payloads != null && !payloads.isEmpty()){
            if(payloads.get(0) instanceof PhotoBean){
                PhotoBean photoBean = (PhotoBean) payloads.get(0);

                if(photoBean.getClickButton() == 2){
                    ((ViewHolder)holder).button_2.setBackgroundColor(Color.YELLOW);
                }
            }

        }else{
            onBindViewHolder(holder, position);
        }
    }

展示效果

12.gif

需求3:实现添加删除和移动功能

case R.id.button_3:
     Toast.makeText(MainActivity.this, "添加Item", Toast.LENGTH_SHORT).show();
     list.add(position, new PhotoBean(list.get(position).getUrl(), list.get(position).getName(), list.get(position).getClickButton()));
     adapter.notifyItemInserted(position);
     break;
case R.id.button_4:
     Toast.makeText(MainActivity.this, "删除Item", Toast.LENGTH_SHORT).show();
     list.remove(position);
     adapter.notifyItemRemoved(position);
     break;
case R.id.button_5:
     Toast.makeText(MainActivity.this, "移动Item", Toast.LENGTH_SHORT).show();
     Collections.swap(list,position,position+1);// 元素互换
     adapter.notifyItemMoved(position, position + 1);
     break;

添加效果:


13.gif

删除效果:


14.gif

移动效果:


15.gif

另外,以上讲解的都是刷新一个Item, 下面四个方法是更新多个Item

public final void notifyItemRangeChanged(int positionStart, int itemCount);
public final void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload);
public final void notifyItemRangeInserted(int positionStart, int itemCount);
public final void notifyItemRangeRemoved(int positionStart, int itemCount) ;

其实,更新一个Item的方法,底层都会调用以上四个方法的其中一个,只是此时itemCount=1。

可能会出现的紊乱问题:

Item紊乱是一个极其容易出现的问题,想要彻底解决这个问题请往下看:

方法一:
    @Override
    public int getItemViewType(int position) {
        //防止滑动时混乱的问题
        return position;
    }

以上代码是为了防止Item滑动紊乱的问题,加上这句话可以基本解决Item紊乱的问题,原理是:

方法二:

紊乱现象只是数据紊乱的假象,我们可以不要重写getItemViewType也可以解决紊乱的问题。
getItemViewType默认返回值是0, 也就是说默认情况下Item类型是一致的。
场景1:加载本地图片(或网络图片)
在onBindViewHolder中的写法如下

if(图片存在){
      //加载已存在的图片
 }

场景2:姓名展示
在onBindViewHolder中的写法如下

if(存在姓名){
      //加载已存在的姓名
 }

如果getItemViewType返回的是固定值时,这两种场景的代码会发生100%紊乱,解决方案如下:
场景1:加载本地图片(或网络图片)
在onBindViewHolder中的写法如下

if(图片存在){
      //加载已存在的图片
 }else{
      //加载默认的图片
 }

场景2:姓名展示
在onBindViewHolder中的写法如下

if(存在姓名){
      //加载已存在的姓名
 }else{
      //加载默认的姓名
 }

也就是说,如果getItemViewType返回的是固定值时,解决紊乱的问题必须从数据处理着手。

另外,上面所提到的插入:notifyItemInserted、删除:notifyItemRemoved、移动:notifyItemMoved,不使用方法一的话会出现紊乱,解决方案如下:

case R.id.button_3:
    Toast.makeText(MainActivity.this, "添加Item", Toast.LENGTH_SHORT).show();
    list.add(position, new PhotoBean(photoBean.getUrl(), photoBean.getName(), photoBean.getClickButton()));
    adapter.notifyItemInserted(position);
    if(position != list.size()){
        adapter.notifyItemRangeChanged(position, list.size() - position);
    }
    break;
case R.id.button_4:
    Toast.makeText(MainActivity.this, "删除Item", Toast.LENGTH_SHORT).show();
    list.remove(position);
    adapter.notifyItemRemoved(position);
    if(position != list.size()){
        adapter.notifyItemRangeChanged(position, list.size() - position);
    }
    break;
case R.id.button_5:
    Toast.makeText(MainActivity.this, "移动Item", Toast.LENGTH_SHORT).show();
    Collections.swap(list,position,position+1);// 元素互换
    adapter.notifyItemMoved(position, position + 1);
    if(position != list.size()){
        adapter.notifyItemRangeChanged(position, list.size() - position);
    }

也就是说,必须将可能会发生重新布局的地方刷新,这里考虑的性能问题就没有全部刷新,notifyItemRangeChanged这个方法是局部刷新的实现。

推荐:如果Item的类型单一,那么为了以防紊乱,推荐使用方法一

但是有人问了, 如果在某个场景下需要支持多类型的recycleview,在列表中每一种类型都有很多个Item,比如聊天消息,聊天消息可能会分为:文本、图片、语音、视频,如果列表中存在多个文本消息(相同类型的Item),那么只能采用方法二了。

上一篇下一篇

猜你喜欢

热点阅读