自定义View中的ListView和ScrollView嵌套的问题

当我们在使用到ScrollView和ListView的时候可能会出现显示不全的问题。那我们可以进行以下分析
ScrollView在测量子布局的时候会用UNSPECIFIED。通过源码观察,
在ScrollView的onMeasure方法中

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);if (!mFillViewport) {return;}final int heightMode = MeasureSpec.getMode(heightMeasureSpec);if (heightMode == MeasureSpec.UNSPECIFIED) {return;}if (getChildCount() > 0) {final View child = getChildAt(0);final int widthPadding;final int heightPadding;final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();if (targetSdkVersion >= VERSION_CODES.M) {widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;} else {widthPadding = mPaddingLeft + mPaddingRight;heightPadding = mPaddingTop + mPaddingBottom;}final int desiredHeight = getMeasuredHeight() - heightPadding;if (child.getMeasuredHeight() < desiredHeight) {final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, widthPadding, lp.width);final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(desiredHeight, MeasureSpec.EXACTLY);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}}}

点击super.onMeasure()方法可以观察到进入了FrameLayout中

  @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int count = getChildCount();final boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;mMatchParentChildren.clear();int maxHeight = 0;int maxWidth = 0;int childState = 0;for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (mMeasureAllChildren || child.getVisibility() != GONE) {measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);final LayoutParams lp = (LayoutParams) child.getLayoutParams();maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);childState = combineMeasuredStates(childState, child.getMeasuredState());if (measureMatchParentChildren) {if (lp.width == LayoutParams.MATCH_PARENT ||lp.height == LayoutParams.MATCH_PARENT) {mMatchParentChildren.add(child);}}}}// Account for padding toomaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// Check against our minimum height and widthmaxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// Check against our foreground's minimum height and widthfinal Drawable drawable = getForeground();if (drawable != null) {maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());}setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));count = mMatchParentChildren.size();if (count > 1) {for (int i = 0; i < count; i++) {final View child = mMatchParentChildren.get(i);final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec;if (lp.width == LayoutParams.MATCH_PARENT) {final int width = Math.max(0, getMeasuredWidth()- getPaddingLeftWithForeground() - getPaddingRightWithForeground()- lp.leftMargin - lp.rightMargin);childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);} else {childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,getPaddingLeftWithForeground() + getPaddingRightWithForeground() +lp.leftMargin + lp.rightMargin,lp.width);}final int childHeightMeasureSpec;if (lp.height == LayoutParams.MATCH_PARENT) {final int height = Math.max(0, getMeasuredHeight()- getPaddingTopWithForeground() - getPaddingBottomWithForeground()- lp.topMargin - lp.bottomMargin);childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);} else {childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,getPaddingTopWithForeground() + getPaddingBottomWithForeground() +lp.topMargin + lp.bottomMargin,lp.height);}child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}}}

里面有个measureChildWithMargins方法

    protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

这个时候发现ScrollView有重写这个方法

    @Overrideprotected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +heightUsed;final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),MeasureSpec.UNSPECIFIED);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

其中childHeightMeasureSpec的模式是MeasureSpec.UNSPECIFIED。这个表示尽可能的大,很少能用到。
而我们的ListView也会调用onMeaure方法

 @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// Sets up mListPaddingsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);final int widthMode = MeasureSpec.getMode(widthMeasureSpec);final int heightMode = MeasureSpec.getMode(heightMeasureSpec);int widthSize = MeasureSpec.getSize(widthMeasureSpec);int heightSize = MeasureSpec.getSize(heightMeasureSpec);int childWidth = 0;int childHeight = 0;int childState = 0;mItemCount = mAdapter == null ? 0 : mAdapter.getCount();if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED|| heightMode == MeasureSpec.UNSPECIFIED)) {final View child = obtainView(0, mIsScrap);// Lay out child directly against the parent measure spec so that// we can obtain exected minimum width and height.measureScrapChild(child, 0, widthMeasureSpec, heightSize);childWidth = child.getMeasuredWidth();childHeight = child.getMeasuredHeight();childState = combineMeasuredStates(childState, child.getMeasuredState());if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(((LayoutParams) child.getLayoutParams()).viewType)) {mRecycler.addScrapView(child, 0);}}if (widthMode == MeasureSpec.UNSPECIFIED) {widthSize = mListPadding.left + mListPadding.right + childWidth +getVerticalScrollbarWidth();} else {widthSize |= (childState & MEASURED_STATE_MASK);}if (heightMode == MeasureSpec.UNSPECIFIED) {heightSize = mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2;}if (heightMode == MeasureSpec.AT_MOST) {// TODO: after first layout we should maybe start at the first visible position, not 0heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);}setMeasuredDimension(widthSize, heightSize);mWidthMeasureSpec = widthMeasureSpec;}

其中的heightMeasureSpec也就是我们在ScrollView中child.measure(childWidthMeasureSpec, childHeightMeasureSpec);在传递过来的。所以也是模式为MeasureSpec.UNSPECIFIED。而当heightMode == MeasureSpec.UNSPECIFIED时会调用

 if (heightMode == MeasureSpec.UNSPECIFIED) {heightSize = mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2;}

这样在heightSize = mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2只是加了一个childHeight 所以展示的只有一个item的大小。
所以要解决这个问题应该让listView进入heightMode == MeasureSpec.AT_MOST里。

public class MyListView extends ListView
{public MyListView(Context context) {super(context);}public MyListView(Context context, AttributeSet attrs) {super(context, attrs);}public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//解决显示不全的问题heightMeasureSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}
}

网上有的博客换成MyListView这么做就实现了,那么为什么呢?
首先当我们的模式为AT_MOST

      if (heightMode == MeasureSpec.AT_MOST) {// TODO: after first layout we should maybe start at the first visible position, not 0heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);}

调用ListView的measureHeightOfChildren

   @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,int maxHeight, int disallowPartialChildPosition) {final ListAdapter adapter = mAdapter;if (adapter == null) {return mListPadding.top + mListPadding.bottom;}// Include the padding of the listint returnedHeight = mListPadding.top + mListPadding.bottom;final int dividerHeight = mDividerHeight;// The previous height value that was less than maxHeight and contained// no partial childrenint prevHeightWithoutPartialChild = 0;int i;View child;// mItemCount - 1 since endPosition parameter is inclusiveendPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;final AbsListView.RecycleBin recycleBin = mRecycler;final boolean recyle = recycleOnMeasure();final boolean[] isScrap = mIsScrap;for (i = startPosition; i <= endPosition; ++i) {child = obtainView(i, isScrap);measureScrapChild(child, i, widthMeasureSpec, maxHeight);if (i > 0) {// Count the divider for all but one childreturnedHeight += dividerHeight;}// Recycle the view before we possibly return from the methodif (recyle && recycleBin.shouldRecycleViewType(((LayoutParams) child.getLayoutParams()).viewType)) {recycleBin.addScrapView(child, -1);}returnedHeight += child.getMeasuredHeight();if (returnedHeight >= maxHeight) {// We went over, figure out which height to return.  If returnedHeight > maxHeight,// then the i'th position did not fit completely.return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)&& (i > disallowPartialChildPosition) // We've past the min pos&& (prevHeightWithoutPartialChild > 0) // We have a prev height&& (returnedHeight != maxHeight) // i'th child did not fit completely? prevHeightWithoutPartialChild: maxHeight;}if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {prevHeightWithoutPartialChild = returnedHeight;}}// At this point, we went through the range of children, and they each// completely fit, so return the returnedHeightreturn returnedHeight;}

因为returnedHeight一直在累加,这样我们让maxHeight为最大值,这样他就不会进入if (returnedHeight >= maxHeight) ,而是返回returnedHeight。
onMeasure方法中的widthMeasureSpec和heightMeasureSpec会包含两个信息,第一个信息是模式:2位,第二个信息是值:30位。一共32位
在这里插入图片描述
在这里插入图片描述
而这里为什么Integer.MAX_VALUE 要右移两位

        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,@MeasureSpecMode int mode) {if (sUseBrokenMakeMeasureSpec) {return size + mode;} else {return (size & ~MODE_MASK) | (mode & MODE_MASK);}}

因为Integer.MAX_VALUE右移两位等于一个30位的值,这样执行makeMeasureSpec就可以构建成一个32位的值。这样去替换ListView的heightMeasureSpec即可。

    /*** A constant holding the maximum value an {@code int} can* have, 2<sup>31</sup>-1.*/@Native public static final int   MAX_VALUE = 0x7fffffff;

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI对话系统app开源

支持对接gpt&#xff0c;阿里云&#xff0c;腾讯云 具体看截图 后端环境&#xff1a;PHP7.4MySQL5.6 软件&#xff1a;uniapp 废话不多说直接上抗揍云链接&#xff1a; https://mny.lanzout.com/iKFRY1o1zusf 部署教程请看源码内的【使用教程】文档 欢迎各位转载该帖/源码

webrtc

stun服务 阿里云服务器安全组添加端口开放 webrtc-streamer视屏流服务器搭建 - 简书

Dockerfile(1) - FROM 指令详解

FROM 指明当前的镜像基于哪个镜像构建dockerfile 必须以 FROM 开头&#xff0c;除了 ARG 命令可以在 FROM 前面 FROM [--platform<platform>] <image> [AS <name>]FROM [--platform<platform>] <image>[:<tag>] [AS <name>]FROM […

naive-ui-admin 表格去掉工具栏toolbar

使用naive-ui-admin的时候&#xff0c;有时候不需要显示工具栏&#xff0c;工具栏太占地方了。 1.在src/components/Table/src/props.ts 里面添加属性 showToolbar 默认显示&#xff0c;在不需要的地方传false。也可以默认不显示 &#xff0c;这个根据需求来。 2.在src/compo…

微服务-微服务链路追踪组件Skywalking实战

自动化监控系统Prometheus&Grafana实战: 4 trem APM-性能监控项目: 4 trem 1. skywalking是什么 1.1 Skywalking主要功能特性 1.2 Skywalking整体架构 1.3 SkyWalking 环境搭建部署 2. SkyWalking快速开始 2.1 SkyWalking Agent追踪微服务 2.1.1 通过jar包方式接…

时序数据库TimescaleDB,实战部署全攻略

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

P沟道与N沟道MOSFET的基本概念

N沟道与P沟道MOSFET基本原理与区别 学习MOSFET时的简单笔记作为个人总结&#xff0c;仅供学习参考&#xff0c;实际电路设计请直接略过&#xff01;&#xff01;&#xff01; 文章目录 N沟道与P沟道MOSFET基本原理与区别前言一、MOSFET &#xff1f;二、N沟道MOS管原理三、P沟…

怎么调用文心一言的api接口生成一个简单的聊天机器人(python代码)

寒假在学习大模型&#xff0c;但也没弄出多少眉目&#xff0c;电脑性能还有点小问题&#xff0c;大模型总跑不起来&#xff0c;只会简单调用一下现有的大模型的接口&#xff0c;例如&#xff1a;文心一言&#xff0c;下面展示一下代码&#xff1a; import tkinter as tk impor…

【小沐学QT】QT学习之信号槽使用

文章目录 1、简介2、代码实现2.1 界面菜单“转到槽”方法2.2 界面信号槽编辑器方法2.3 QT4.0的绑定方法2.4 QT5.0之后的绑定方法2.5 C11的方法2.6 lamda表达式方法2.7 QSignalMapper方法 结语 1、简介 在GUI编程中&#xff0c;当我们更改一个小部件时&#xff0c;我们通常希望…

【vmware安装群晖】

vmware安装群晖 vmware安装群辉&#xff1a; vmware版本&#xff1a;17pro 下载链接&#xff0c; https://customerconnect.vmware.com/cn/downloads/details?downloadGroupWKST-1751-WIN&productId1376&rPId116859 激活码可自行搜索 教程&#xff1a; https://b…

STM32串口通信(发送与接收数据)

文章目录 前言一、介绍部分通信接口术语解释 串口通信简介硬件电路电平标准串口参数串口时序USART简介USART框图USRAT基本结构数据帧起始位检测波特率发生器CH340G 二、实例部分使用串口发送数据接线图代码实现重定向printf需要勾上Use MicroLIB中文不乱码方法 串口的发送与接收…

Go语言必知必会100问题-10 小心类型嵌入导致的问题

小心类型嵌入导致的问题 在定义结构体时&#xff0c;Go语言支持通过类型嵌入的形式定义结构体字段。但是&#xff0c;如果我们没有真正理解类型嵌入的意义&#xff0c;有时可能会导致意想不到的行为。本文将主要分析如何嵌入类型&#xff0c;类型嵌入的作用以及可能出现的问题…

Leetcode1035. 不相交的线 -代码随想录

题目&#xff1a; 代码(首刷看解析 2024年2月29日&#xff09;&#xff1a; class Solution { public:// 动态规划 思路同最长公共子序列int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {int m nums1.size();int n nums2.size();vecto…

vue中组合式API和选项式API的区别

组合式api&#xff08;Composition API&#xff09;是vue3对我们开发者来说变化非常大的更新&#xff0c;我们先不关注具体语法&#xff0c;先对它有一个大的感知。 通过vue2, vue3两种形式实现同一个需求&#xff0c;理解vue3的compition api 带来的好处 两个独立的功能&…

MySQL 如何从 Binlog 找出变更记录并回滚

文章目录 前言1. 案例模拟1.1 确认信息1.2 下载 Binlog1.3 准备环境1.4 注册 Binlog1.5 准备结构信息1.6 Python 订阅1.7 输出结果展示 2. 原理解析2.1 程序设计2.2 模块版本 总结 前言 最近有研发同学问我&#xff1a;有一个问题&#xff0c;想查一个 ID 为 xxxx 的 sku 什么…

Android Gradle开发与应用 (二) : Groovy基础语法

1. Groovy是什么 Groovy是基于JVM虚拟机的一种动态语言&#xff0c;语法和Java非常相似&#xff0c;并能够无缝地与Java代码集成和互操作&#xff0c;增加了很多动态类型和灵活的特性。(闭包、DSL) 语法和Java非常相似这个特点&#xff0c;意味着&#xff0c;如果我们完全不懂…

推荐几款优秀免费开源的导航网站

&#x1f9a9;van-nav 项目地址&#xff1a;van-nav项目介绍&#xff1a;一个轻量导航站&#xff0c;汇总你的所有服务。项目亮点&#xff1a;全平台支持&#xff0c;单文件部署&#xff0c;有配套浏览器插件。效果预览 &#x1f9a9;发现导航 项目地址&#xff1a;nav项目…

算法 -【从前序与中序遍历序列构造二叉树】

从前序与中序遍历序列构造二叉树 题目示例1示例2 分析代码 题目 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例1 输入: preorder [3,9,20,1…

RabbitMQ实战学习

RabbitMQ实战学习 文章目录 RabbitMQ实战学习RabbitMQ常用资料1、安装教程2、使用安装包3、常用命令4、验证访问5、代码示例 一、RabbitMQ基本概念1.1. MQ概述1.2 MQ 的优势和劣势1.3 MQ 的优势1. 应用解耦2. 异步提速3. 削峰填谷 1.4 MQ 的劣势1.5 RabbitMQ 基础架构1.6 JMS 二…

怎样才能考上南京大学的计算机研究生?

附上南大与同层次学校近四年的分数线对比&#xff0c;整体很难 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 我本人是双非一本的计算机专业&#xff0c;23考研一战上岸的&#xf…