【RecyclerView】同时刷新和滚动导致,滚动位置异常(一)

news2024/10/6 6:46:54

前置:

被选中item高度与非选中item高度不一致,且硬件有点卡,运行会有一定卡顿。

可视界面的item为三个,总数据为十个。

期望效果:

=》

 实际上效果:

 

 代码:

        mListAdapter.setSelectedPosition(position);
        mListView.post(() -> {
            mListView.smoothScrollToPosition(position);
        });
    public void setSelectedPosition(int position) {
        if (mSelectedPosition == position) {
            return;
        }
        mSelectedPosition = position;
        mSelectedSubPoiPosition = -1;
        if (0 > mSelectedPosition) {
            return;
        }
        if (mSelectedPosition >= getItemCount()) {
            return;
        }
        notifyDataSetChanged();
    }

代码很简单,先notify在进行scroll,看代码是不会出现这个问题,后来进行初步调试,推测为view未渲染完毕,进行滚动,导致滚动的位置与实际偏差。(选中和未选中的item高度不一致)

一.解决方案

    public void setSelectedPosition(int position) {
        if (mSelectedPosition == position) {
            return;
        }
        int mLastSelectPos=mSelectedPosition;
        mSelectedPosition = position;
        mSelectedSubPoiPosition = -1;
        if (0 > mSelectedPosition) {
            return;
        }
        if (mSelectedPosition >= getItemCount()) {
            return;
        }
      //修改为:范围刷新
      notifyItemRangeChanged(Math.min(mLastSelectPos, mSelectedPosition), Math.abs(mLastSelectPos - mSelectedPosition) + 1);//受影响的itemd都刷新下

    }
   mListView.post(() -> {
            //TODO 添加代码 把刷新动画end,设置为null也会失败,但是end有用
            RecyclerView.ItemAnimator itemAnimator = mListView.getItemAnimator();
            if (itemAnimator != null) {
                itemAnimator.endAnimations();
            }
            mListView.smoothScrollToPosition(scrollPosition);
        });

二.recyclerView的刷新

我的思路是,首先对控件的加载进行优化,现针对notify刷新,其次为滚动动画。

1. 刷新全部的item,notifyDataSetChanged()   
2. 刷新指定的item,notifyItemChanged(int)   
3. 从指定的位置开始刷新指定个item,notifyItemRangeChanged(int,int) 这个刷新onBindViewHolder方法,position才能保持一直
4. 局部刷新指定的数据,notifyItemChanged(int, Object)

根据浏览器和源码的结果,主要分为以上四种刷新方式。

1.notifyItemChanged

1.1 notifyItemChanged(position)

想先使用notifyItemChanged,指定刷新本次选中和上次选中的item,精准打击,这样刷新是最节约资源的。

        notifyItemChanged(mLastSelectPos);
        notifyItemChanged(mSelectedPosition);

onBindViewHolder也只会调用俩次。

但是加上滚动,且滚动的目标位置距离过远,导致中间加载的view数量多,滚动的位置也会出现偏差。并未解决到BUG。

1.2 notifyItemChanged(position,payloads)

payloads我理解为一个标识符,在onBindViewHolder的方法中进行判断,针对item的控件单独刷新,其余并无区别。

    @Override
    public final void onBindViewHolder(VH holder, int position, List<Object> payloads) {
      
    }

用法举个栗子:

        //"a"和"b"都是标识符,可以换成其他的
        notifyItemChanged(mLastSelectPos,"a");
        notifyItemChanged(mSelectedPosition,"b");
    @Override
    public final void onBindViewHolder(VH holder, int position, List<Object> payloads) {
      
     if (payloads.get(0).equals("a")){
            ((Item) holder).getmNameTv().setText("我是A");
        }else if (payloads.get(0).equals("b")){
            ((Item) holder).getmNameTv().setText("我是B");
        }
    }

1.3 它们终归指向何处?

 

查看源码都指向了 notifyItemRangeChanged

 2.notifyItemRangeChanged

 先看参数源码解释:

positionStart :刷新开始的位置/下标

itemCount:从positionStart开始数itemCount个数需要刷新。

用法:

notifyItemRangeChanged(Math.min(mLastSelectPos, mSelectedPosition), Math.abs(mLastSelectPos - mSelectedPosition) + 1)

 举个栗子:

notifyItemRangeChanged(0,3),刷新下标:0\1\2

notifyItemRangeChanged(3,5),刷新下标:2\3\4\5\6

3.刷新流程=》Observable

我们现在知道是由Observable促成刷新,来看下如何进行注册。

首先进行注册

RecyclerView 会一开始new一个RecyclerViewDataObserver进行注册。

private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

    ....
public void setAdapter(@Nullable Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }

    ....

private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
    }
    ...

我们来看下RecyclerViewDataObserver里面的方法对应RecyclerView的哪些调用。

notifyDataSetChanged() => 
RecyclerViewDataObserver.onChanged()
notifyItemChange()/notifyItemRangeChanged()  =>
RecyclerViewDataObserver.onItemRangeChanged();

先看下最开始调用的RecyclerViewDataObserver.onChanged()里做了什么事。


    private class RecyclerViewDataObserver extends AdapterDataObserver {
       

        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
       
            mState.mStructureChanged = true;
         // 清空mCachedViews
            processDataSetCompletelyChanged(true);
        // 刷新UI
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }
        }    
    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        mDataSetHasChangedAfterLayout = true;
    //添加标识符
        markKnownViewsInvalid();
    }

调用markKnownViewsInvalid()使用for循环给mCachedViews 的每个viewHolder添加标识符FLAG_UPDATE或者FLAG_INVALID。

 // RecyclerView.java/Recycler
 void markKnownViewsInvalid() {
 	// 遍历缓存的view设置标志
     int cachedCount = this.mCachedViews.size();
     for(int i = 0; i < cachedCount; ++i) {
         RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)this.mCachedViews.get(i);
         if (holder != null) {
             holder.addFlags(6);
             holder.addChangePayload((Object)null);
         }
     }
	 // 决定回收和清除view的关键是 是否设置的stableIds
     if (RecyclerView.this.mAdapter == null || !RecyclerView.this.mAdapter.hasStableIds()) {
     	 // 将mCacheViews中缓存的viewHolder全部移入mRecyclerPool中
         this.recycleAndClearCachedViews();
     }
 }

下一步查看了网上的相关博客,我们可以倒回去看requestLayout()刷新布局,会执行onMeasure()
onLayout(),那么势必就涉及到viewHolder的复用问题,而在填充fill之前,会在之前
执行 detachAndScrapAttachedViews 方法进行缓存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/456002.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学成在线笔记+踩坑(6)——【媒资模块】视频处理。FFmpeg+XXL-JOB

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线牛客面试题 目录 1 视频转码需求 1.1 视频编码格式和文件格式 1.2 windows使用编码工具FFmpeg 1.3 视频处理工具类 1.3.1 拼装FFmpeg命令的各工具类 1…

复旦MOSS大模型开源了「中国版ChatGPT」,Github和Hugging Face同时上线

最近&#xff0c;ChatGPT非常火&#xff0c;从ChatGPT3到ChatGPT4&#xff0c;都非常火。无论是否为互联网行业的&#xff0c;多少都听到过关于ChatGPT的消息。虽然百度、阿里等互联网巨头都已经宣布将会推出相关的类ChatGPT产品。但目前还未有成型的产品上线。 而昨日&#x…

94. 二叉树的中序遍历【119】

难度等级&#xff1a;容易 上一篇算法&#xff1a; 102. 二叉树的层序遍历【206】 力扣此题地址&#xff1a; 94. 二叉树的中序遍历 - 力扣&#xff08;Leetcode&#xff09; 1.题目&#xff1a;94. 二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序…

AlgoC++第四课:逻辑回归

目录 逻辑回归前言1. 鸢尾花分类问题1.1 基于线性回归的思考1.2 损失函数 2. 伯努利分布3. 示例代码3.1 数据可视化3.2 日志信息打印3.3 数据读取和处理3.4 逻辑回归模型3.5 完整示例代码3.6 python实现 4. 思考总结 逻辑回归 前言 手写AI推出的全新面向AI算法的C课程 Algo C&…

InnoDB中必须要了解的几个关键特性

InnoDB引擎在设计中使用了很多技术思想。下面我们主要介绍一些InnoDB的关键特性&#xff0c;帮助你去更好了解 InnoDB。 关键特性 1.预读&#xff08;1&#xff09;预读的两种算法&#xff08;2&#xff09;在InnoDB中相关配置 2.插入缓冲2.1 Insert Buffer2.2 Change Buffer2…

跨域的五种最常见解决方案

这是一篇笔记 什么是跨域&#xff1f; 跨域不是问题&#xff0c;是一种安全机制。浏览器有一种策略名为同源策略&#xff0c;同源策略规定了部分请求不能被浏览器所接受。 值得一提的是&#xff1a;同源策略导致的跨域是浏览器单方面拒绝响应数据&#xff0c;服务器端是处理…

C#基于asp.net的企业人事管理系统的研究与实现

&#xff08;一&#xff09;任务 1.本课题的任务是对人事管理系统的管理过程进行分析&#xff0c;列出逻辑实现过程&#xff0c;对系统进行逻辑设计和数据库设计&#xff1b; 2.主要实现系统管理、档案管理、考勤管理、薪水管理等功能&#xff1b; 3.实现企业员工的相关信息&am…

一篇文章教你解决node-sass 4.12.0 安装失败,一劳永逸

已知&#xff1a; 使用mac电脑使用的node版本是v14.20.0 问题&#xff1a;在安装node-sass 4.12.0的时候报错如下 看到这一堆错误&#xff0c;千万不要立马复制粘贴到浏览器去搜&#xff0c;感觉像无头苍蝇乱撞&#xff0c;好歹稍微看一下什么意思。 显而易见是有一个文档40…

浅析低代码开发的典型应用构建场景v

在数字经济蓬勃发展的大势之下&#xff0c;企业软件开发人员供给不足、开发速度慢、开发成本高、数字化和智能化成效不明显等问题日益凸出&#xff0c;阻碍了企业的数字化转型。 而近年来&#xff0c;低代码的出现推动了经济社会的全面提效&#xff0c;也成为人才供求矛盾的润…

【Docker】限制已运行容器的Cpu和内存

docker限制已运行容器的Cpu和内存 本文首发于 慕雪的寒舍 1.问题描述 最近云服务器的内存经常不够用&#xff0c;而且是莫名其妙的增多&#xff0c;在腾讯云的控制台里面看&#xff0c;4g的内存占用了3.2g&#xff0c;就卡到连ssh都连不上了 PS: 已换过网络和设备&#xff0c…

只要10分钟,零代码基础搞定炫酷大屏设计(内附详细教程)

近几年可视化大屏再次被推上热搜&#xff0c;无论是已经结束的疫情时代指挥中心大屏&#xff0c;还是每年购物节的大屏数据成交额&#xff0c;或者是日常会议中的大屏分析&#xff0c;到处都是可视化大屏的身影。 有人会说&#xff0c;这种大屏一定需要大量的财力、技术顶端的…

开源构建系统Buck2发布

看来最近 Meta 的工程师是一点都没有闲着&#xff0c;前两天刚开源 AI 图像分割模型&#xff0c;这不就又发布了名为 Buck2 的开源构建系统。 Buck2 是一个已经在 Meta 内部使用了一段时间的大型构建系统&#xff0c;目前 Meta 有数千名开发人员正在使用该构建系统&#xff0c;…

IP地址配置

1.vi /etc/sysconfig/network-scripts/ifcfg-ens33 &#xff08;在配置文件中修改&#xff09; 配置文件内容为&#xff1a; IPADDR:配置IP地址 NETMASK:配置子网掩码 GATEWAY:配置网关 DNS:配置dns地址 BOOTPROTO&#xff1a;设置获取ip的方式\DHCP为动态获取\ static为…

C++(GCC)生成和使用静态库

C&#xff08;GCC&#xff09;生成和使用静态库 文章目录 C&#xff08;GCC&#xff09;生成和使用静态库1、前言1.1 什么是静态库1.2 静态库优缺点1.3 C使用静态库的方法1.4 注意事项 2、linux下C生成静态库.a3、链接使用静态库 更多精彩内容&#x1f449;个人内容分类汇总 &a…

可算是熬出头了,测试4年,费时8个月,入职阿里,涨薪14K

前言 你的努力&#xff0c;终将成就无可替代的自己。 本科毕业后就一直从事测试的工作&#xff0c;和多数人一样&#xff0c;最开始从事点点点的工作&#xff0c;看着自己的同学一步一步往上走&#xff0c;自己还是在原地踏步&#xff0c;说实话这不是自己想要的状态。 一年半…

蓝牙耳机接打电话哪个比较好?接打电话最好的蓝牙耳机

技术已经发展到如此程度&#xff0c;耳机可以淹没嘈杂环境中不断出现的杂音&#xff0c;同时还能让我们在通话、音乐和娱乐方面保持清晰&#xff0c;既然如此&#xff0c;我们就来整理一下2023年适合通话和娱乐的无线耳机清单。 一、南卡小音舱Lite2蓝牙耳机 参考价格&#x…

Ubuntu系统下Python的虚拟环境搭建方法

文章目录 参考链接&#xff1a;一、Python虚拟环境的作用及创建方法简介1. 创建虚拟环境的必要性2. venv、virtualenv、pipenv三种创建虚拟环境方法比较2.1 搭建Python虚拟环境的方法2.2 venv、virtualenv方法2.3 pipenv方法 二、 virtualenv搭建虚拟环境1. 环境准备1.1 安装vi…

AI数字人产品“世优BOTA”发布会落幕,世优科技纪智辉演讲回顾

4月20日&#xff0c;世优科技式发布了新一代AI数字人产品——“世优BOTA”。在会上&#xff0c;世优科技创始人纪智辉介绍了数字人技术、驱动形式、数字人应用、“世优BOTA”的前世今生及未来。 以下是纪智辉的演讲内容概要&#xff1a; 各位嘉宾、各位朋友&#xff1a;大家下…

Storm proxies动态SEO监测优化为什么要大量用到http代理ip?

SEO监测和优化通常需要大量使用HTTP代理IP来实现以下几个方面的需求&#xff1a; 模拟不同地区的搜索结果&#xff1a;搜索引擎通常会根据用户所在地区提供不同的搜索结果&#xff0c;因此需要使用HTTP代理IP模拟不同地区的搜索结果&#xff0c;以便监测和优化针对不同地区的SE…

Matlab 相机标定

详细的原理可以看这篇 计算机视觉-相机标定&#xff0c;写的很赞 Step1. 准备 首先准备打印好的黑白棋盘格图片&#xff0c;并且保证表面的平整&#xff0c;例如35*35的棋盘格&#xff0c;贴在亚克力板上。 Step2. 拍摄 用相机拍摄棋盘格分别在取景框左上角、右上角、左下…