当前位置: 首页 > news >正文

GridLayoutManager 中的一些坑

前言

如果GridLayoutManager使用item的布局都是wrap_cotent 那么会在布局更改时会出现一些出人意料的情况。(本文完全不具备可读性和说教性,仅为博主方便查找问题)

布局item:

<!--layout_item.xml-->
<?xml version="1.0" encoding="utf-8"?>
<com.vb.rerdemo.MyConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="10dp"android:background="#f0f"><com.google.android.material.card.MaterialCardViewandroid:layout_width="match_parent"app:layout_constraintTop_toTopOf="parent"app:cardCornerRadius="10dp"app:cardBackgroundColor="#908000"android:layout_height="240dp"><TextViewandroid:id="@+id/tv"android:layout_gravity="center"android:layout_width="wrap_content"android:text="hello world"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></com.google.android.material.card.MaterialCardView>
</com.vb.rerdemo.MyConstraintLayout>

在这里插入图片描述

//LastGapDecoration.kt
//给最后一行的item添加一个高度
class LastGapDecoration : ItemDecoration() {override fun getItemOffsets(outRect: Rect,view: View,parent: RecyclerView,state: RecyclerView.State) {super.getItemOffsets(outRect, view, parent, state)super.getItemOffsets(outRect, view, parent, state)val itemPosition = parent.getChildAdapterPosition(view)val gridLayoutManager = parent.layoutManager as? GridLayoutManager ?: returnval spanCount = gridLayoutManager.spanCountval itemCount = gridLayoutManager.itemCountif (spanCount <= 0) {return}val lastRowItemCount = itemCount % spanCountval lastRow =isLastRow(itemPosition, itemCount, spanCount, lastRowItemCount)Log.d("fmy","lastRow ${lastRow} itemPosition ${itemPosition} lastRowItemCount ${lastRowItemCount} itemCount ${itemCount} viewid ${view.hashCode()}")if (lastRow) {outRect.bottom = ScreenUtil.dp2px(40f,App.myapp)} else {outRect.bottom = 0}}private fun isLastRow(itemPosition: Int,itemCount: Int,spanCount: Int,lastRowItemCount: Int): Boolean {// 如果最后一行的数量不足一整行,则直接判断位置if (lastRowItemCount != 0 && itemPosition >= itemCount - lastRowItemCount) {return true}// 如果最后一行的数量足够一整行,则需要计算val rowIndex = itemPosition / spanCountval totalRow = ceil(itemCount.toDouble() / spanCount).toInt()return rowIndex == totalRow - 1}
}

当我们填充6个布局后的效果:
在这里插入图片描述

红色区域和45之间的间距通过LastGapDecoration完成。

此时我们移除3后:
在这里插入图片描述
根本原因在于GridLayoutManager#layoutChunk函数中

 
public class GridLayoutManager extends LinearLayoutManager {View[] mSet;//layoutChunk 每次调用只拿取当前行view进行对比计算//比如GridLayoutManager一行两个那么每次会拿取每行的对应view进行计算void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {int count = 0;while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {//略... 经过一些的计算mSet放入本次要进行摆放的view//count 一般为GridLayoutManager的spanCount数量mSet[count] = view;count++;}//maxSize是指在本次layoutChunk中所有view里面最大的高度数据。(包含view自身和ItemDecorations得到的)int maxSize = 0;// we should assign spans before item decor offsets are calculatedfor (int i = 0; i < count; i++) {//计算ItemDecorationscalculateItemDecorationsForChild(view, mDecorInsets);//调用measure计算view宽高 核心!!!//核心代码点:注意这里调用子view的measure参数为layoutparameter高度//我们把这里称为操作AmeasureChild(view, otherDirSpecMode, false);//核心代码点:这里这里会得到这个view的宽高和ItemDecorations填充的高度和final int size = mOrientationHelper.getDecoratedMeasurement(view);//核心代码点: 记录最大数值if (size > maxSize) {maxSize = size;}}//我们把这里称为操作B//取出当前行中的所有view。保证行高度一致for (int i = 0; i < count; i++) {final View view = mSet[i];if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {final LayoutParams lp = (LayoutParams) view.getLayoutParams();final Rect decorInsets = lp.mDecorInsets;final int verticalInsets = decorInsets.top + decorInsets.bottom+ lp.topMargin + lp.bottomMargin;final int horizontalInsets = decorInsets.left + decorInsets.right+ lp.leftMargin + lp.rightMargin;final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);final int wSpec;final int hSpec;if (mOrientation == VERTICAL) {wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,horizontalInsets, lp.width, false);//核心代码点: 这里会强制当前行所有view的高度与最高的view保持一致。       hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets,View.MeasureSpec.EXACTLY);} else {//略}//执行测量measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true);}} }
}    

上面的代码可以总结为:

  1. 取出当前的所有view
  2. 对所有view执行一次高度测量,并记录当前最高的view数据
  3. 在此执行一次测量,保证当前行的所有view高度一致

我们重点再看一眼measureChild函数

 private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) {final LayoutParams lp = (LayoutParams) view.getLayoutParams();final Rect decorInsets = lp.mDecorInsets;final int verticalInsets = decorInsets.top + decorInsets.bottom+ lp.topMargin + lp.bottomMargin;final int horizontalInsets = decorInsets.left + decorInsets.right+ lp.leftMargin + lp.rightMargin;final int availableSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);final int wSpec;final int hSpec;if (mOrientation == VERTICAL) {wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode,horizontalInsets, lp.width, false);//mOrientationHelper.getTotalSpace()可以先忽略//verticalInsets 就是decorate中的高度和一些margin等数值//lp.height如果是wrapcontent那么一返回高度为0的MeasureSpec.UNSPECIFIED//lp.height如果不是wrapcontent那么一返回高度为父亲高度减去verticalInsets的MeasureSpec.EXACTLY//lp.height如果是一个明确数值那么一返回高度为设置的高度的MeasureSpec.EXACTLY//总结getChildMeasureSpec传入布局参数高度和decorate高度hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(),verticalInsets, lp.height, true);} else {//略}//透传给子view测量measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured);}

measureChildWithDecorationsAndMargin函数会根据必要性确定是否要执行子view的测量操作。

private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec,boolean alreadyMeasured) {RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();final boolean measure;if (alreadyMeasured) {measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp);} else {measure = shouldMeasureChild(child, widthSpec, heightSpec, lp);}//根据情况是否执行if (measure) {child.measure(widthSpec, heightSpec);}}boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {return !mMeasurementCacheEnabled|| !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width)|| !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height);}boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {return child.isLayoutRequested()|| !mMeasurementCacheEnabled|| !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width)|| !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);}       

shouldReMeasureChild可以总结为:

  1. 如果没有开启缓存那么一定执行测绘
  2. 如果开启了缓存那么判断之前是否执行过相同参数测量

在了解上面的信息我们可以总结一下流程发现问题:
我们假设假设wrapcotent计算的高度为50
decorate插入的高度为10

插入0时:0执行操作A,不执行操作B

插入1时:

  • 0和1同时执行操作A,不执行操作B。0由于之前测绘过不会触发onmeasure。 1触发onmeasure

插入2时:

  • 0和1同时执行操作A,不执行操作B , 0和1不会触发onmeasure。 2执行操作A并触发onmeasure

插入3时:

  • 0和1同时执行操作A,不执行操作B , 0和1不会触发onmeasure。 3和2执行操作A ,2不会触发onmeasure,3触发onmeasure。

移除1时:

  • 0 和 1 同时执行操作A (0 和1不会触发onmeasure)操作B不会执行(虽然1被移除 但是由于预布局存在还需要进行一次比较)
  • 2 和 3 同时执行操作A (2 和3不会触发onmeasure). 由于2移动第一行不会有decorate高度,因此2执行操作B并触发onmeasure。2 高度为60(移除后2和3虽然不在一行但需要执行预布局)
  • 0和2进行同时执行操作A (0 和2不会触发onmeasure),同时0会被执行操作B把高度填充到60. (虽然2没有decorate的高度 但是上一次预布局引起了2高度错误)
  • 3 同时执行操作A (不会触发onmeasure) 不会触发操作B

解决方案:

val manager = GridLayoutManager(this, 2)
manager.isMeasurementCacheEnabled = false

相关文章:

GridLayoutManager 中的一些坑

前言 如果GridLayoutManager使用item的布局都是wrap_cotent 那么会在布局更改时会出现一些出人意料的情况。&#xff08;本文完全不具备可读性和说教性&#xff0c;仅为博主方便查找问题&#xff09; 布局item: <!--layout_item.xml--> <?xml version"1.0&qu…...

算法实验二 矩阵最小路径和 LIS

算法实验课二 矩阵最小路径和 leetcode裸题 最小路径和 给定一个包含非负整数的 *m* x *n* 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 示例 1&#xff1a; 输入&…...

Apache Paimon实时数据糊介绍

Apache Paimon 是一种湖格式,可以使用 Flink 和 Spark 构建实时 数据糊 架构,用于流式和批处理操作。Paimon 创新地将湖格式和 LSM(日志结构合并树)结构相结合,将实时流式更新引入湖架构中。 Paimon 提供以下核心功能: 实时更新: 主键表支持大规模更新的写入,具有非常…...

计算机网络:数据链路层 - 可靠传输协议

计算机网络&#xff1a;数据链路层 - 可靠传输协议 可靠传输概念停止-等待协议 SW回退N帧协议 GBN选择重传协议 SR 可靠传输概念 如下所示&#xff0c;帧在传输过程中受到干扰&#xff0c;产生了误码。接收方的数据链路层&#xff0c;通过真伪中的真检验序列 FCS 字段的值&…...

苍穹外卖07(缓存菜品,SpringCache,缓存套餐,添加购物车菜品和套餐多下单,查看购物车,清除购物车,删除购物车中一个商品)

目录 一、缓存菜品 1 问题说明 2 实现思路 3 代码开发&#xff1a;修改DishServiceImpl 4 功能测试 二、SpringCache 1. 介绍 2. 使用语法 1 起步依赖 2 使用要求 3 常用注解 4 SpEL表达式(了解备用) 5 步骤小结 3.入门案例 1 准备环境 2 使用入门 1 引导类上加…...

C语言第三十八弹---编译和链接

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 编译和链接 1、翻译环境和运行环境 2、翻译环境 2.1、预处理&#xff08;预编译&#xff09; 2.2、编译 2.2.1、词法分析 2.2.2、语法分析 2.2.3、语义分…...

无人售货奶柜:开启便捷生活的新篇章

无人售货奶柜&#xff1a;开启便捷生活的新篇章 在这个快节奏的现代生活中&#xff0c;科技的革新不仅为我们带来了前所未有的便利&#xff0c;更在不经意间改变着我们的日常。其中&#xff0c;无人售货技术的出现&#xff0c;尤其是无人售货奶柜&#xff0c;已经成为我们生活…...

STM32为什么不能跑Linux?

STM32是一系列基于ARM Cortex-M微控制器的产品&#xff0c;它们主要用于嵌入式系统中。而Linux则是一个开源的类Unix操作系统&#xff0c;主要面向的是桌面电脑、服务器等资源丰富的计算机。虽然理论上可以将Linux移植到STM32上运行&#xff0c;但是由于两者之间存在着很多技术…...

Dubbo 3.x源码(18)—Dubbo服务引用源码(1)

基于Dubbo 3.1&#xff0c;详细介绍了Dubbo服务的发布与引用的源码。 此前我们学习了Dubbo的服务导出的源码&#xff0c;在DubboBootstrapApplicationListener#startSync方法中&#xff0c;在调用了exportServices方法进行服务导出之后&#xff0c;立即调用了referServices方法…...

设计模式:工厂模式和抽象工厂模式的区别

定义 工厂模式(Factory Pattern)通常指的是工厂方法模式(Factory Method Pattern),它定义了一个创建对象的方法,由子类决定要实例化的类。工厂方法让类的实例化推迟到子类。 抽象工厂模式(Abstract Factory Pattern)提供了一个接口,用于创建相关或依赖对象的家族,而…...

python面试题(36~50)

36、如何取一个整数的绝对值? 这可以通过abs函数来实现。 abs(2) #> 2 abs(-2) #> 2 37、如何将两个列表组合成一个元组列表? 可以使用zip函数将列表组合成一个元组列表。这不仅仅限于使用两个列表。也适合3个或更多列表的情况。 a [a,b,c] b [1,2,3] [(k,v) fo…...

Vue 样式技巧总结与整理[中级局]

SFC&#xff08;单文件组件&#xff09;由 3 个不同的实体组成&#xff1a;模板、脚本和样式。三者都很重要&#xff0c;但后者往往被忽视&#xff0c;即使它可能变得复杂&#xff0c;且经常导致挫折和 bug。 更好的理解可以改善代码审查并减少调试时间。 这里有 7 个奇技淫巧…...

cesium加载.tif格式文件

最近项目中有需要直接加载三方给的后缀名tif格式的文件 <script src"https://cdn.jsdelivr.net/npm/geotiff"></script> 或者 yarn add geotiff npm install geotiff 新建tifs.js import GeoTIFF, { fromBlob, fromUrl, fromArrayBuffer } from geotif…...

分布式全闪占比剧增 152%,2023 年企业存储市场报告发布

近日&#xff0c;IDC 发布了 2023 年度的中国存储市场报告。根据该报告&#xff0c;在 2023 年软件定义存储的市场占比进一步扩大&#xff0c;分布式全闪的增长尤其亮眼&#xff0c;其市场份额从 2022 年的 7% 剧增到 2023 年的 17.7%&#xff0c;增长了 152%。 01 中国企业存…...

LeetCode 707. 设计链表(单链表、(非循环)双链表 模板)

你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则还需要属性 prev 以指示链表中的上一个节点…...

深入了解Flutter中Overlay的介绍以及使用

Flutter Overlay 介绍 在 Flutter 中&#xff0c;Overlay 是一种特殊的 Widget&#xff0c;它可以用来在应用程序的其他部分之上显示内容。Overlay 非常适合用于显示模态对话框、弹出菜单、工具提示等。 Overlay 的工作原理 Overlay 位于 Flutter 的渲染树之外&#xff0c;这…...

文本直接生成2分钟视频,即将开源模型StreamingT2V

Picsart人工智能研究所、德克萨斯大学和SHI实验室的研究人员联合推出了StreamingT2V视频模型。通过文本就能直接生成2分钟、1分钟等不同时间&#xff0c;动作一致、连贯、没有卡顿的高质量视频。 虽然StreamingT2V在视频质量、多元化等还无法与Sora媲美&#xff0c;但在高速运…...

时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测

时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测 目录 时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测&#xff08;完整源码…...

FPGA高端图像处理开发板-->鲲叔4EV:12G-SDI、4K HDMI2.0、MIPI等接口谁敢与我争锋?

目录 前言鲲叔4EV----高端FPGA图像处理开发板核心板描述底板描述配套例程源码描述配套服务描述开发板测试视频演示开发板获取 前言 在CSDN写博客传播FPGA开发经验已经一年多了&#xff0c;帮助了不少人&#xff0c;也得罪了不少人&#xff0c;有的人用我的代码赢得了某些比赛、…...

linux练习-交互式传参

在shell脚本中&#xff0c;read 向用户显示一行文本并接受用户输入 #!/bin/bash read -p 依次输入你的姓名、年龄、家乡 name age home echo 我是$name,年龄$age,我来自$home...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

【2025年】解决Burpsuite抓不到https包的问题

环境&#xff1a;windows11 burpsuite:2025.5 在抓取https网站时&#xff0c;burpsuite抓取不到https数据包&#xff0c;只显示&#xff1a; 解决该问题只需如下三个步骤&#xff1a; 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

若依登录用户名和密码加密

/*** 获取公钥&#xff1a;前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...