自定义View 布局过程(Layout)

news/2024/5/17 17:56:41/文章来源:https://blog.csdn.net/qq_48435252/article/details/127080355

目录

  • 一、作用
  • 二、layout过程详解
    • 2.1单一View的layout过程
      • 具体使用
      • 具体流程
      • 源码分析
      • 总结
    • 2.2ViewGroup的layout过程
      • 具体使用
      • 具体流程
      • 源码分析
      • 总结
  • 三、细节问题:getWidth() ( getHeight())与 getMeasuredWidth() (getMeasuredHeight())获取的宽 (高)有什么区别?
      • 结论

一、作用

计算视图(View)的位置

即计算View的四个顶点位置:Left、Top、Right 和 Bottom

二、layout过程详解

类似measure过程,layout过程根据View的类型分为2种情况:
在这里插入图片描述
接下来,我将详细分析这2种情况下的layout过程

2.1单一View的layout过程

具体使用

继承自View、SurfaceView 或 其他View;不包含子View

具体流程

在这里插入图片描述

源码分析

layout过程的入口 = layout(),具体如下:

/*** 源码分析起始点:layout()* 作用:确定View本身的位置,即设置View本身的四个顶点位置*/ public void layout(int l, int t, int r, int b) {  // 当前视图的四个顶点int oldL = mLeft;  int oldT = mTop;  int oldB = mBottom;  int oldR = mRight;  // 1. 确定View的位置:setFrame() / setOpticalFrame()// 即初始化四个顶点的值、判断当前View大小和位置是否发生了变化 & 返回 // setFrame() ->分析1// setOpticalFrame() ->分析2boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);// 2. 若视图的大小 & 位置发生变化// 会重新确定该View所有的子View在父容器的位置:onLayout()if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  onLayout(changed, l, t, r, b);  // 对于单一View的laytou过程:由于单一View是没有子View的,故onLayout()是一个空实现 ->分析3// 对于ViewGroup的laytou过程:由于确定位置与具体布局有关,所以onLayout()在ViewGroup为1个抽象方法,需自定义重写实现(下面的章节会详细说明)
}  /*** 分析1:setFrame()* 作用:根据传入的4个位置值,设置View本身的四个顶点位置* 即:最终确定View本身的位置*/ protected boolean setFrame(int left, int top, int right, int bottom) {// 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点// 从而确定了视图的位置mLeft = left;mTop = top;mRight = right;mBottom = bottom;mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);}/*** 分析2:setOpticalFrame()* 作用:根据传入的4个位置值,设置View本身的四个顶点位置* 即:最终确定View本身的位置*/ private boolean setOpticalFrame(int left, int top, int right, int bottom) {Insets parentInsets = mParent instanceof View ?((View) mParent).getOpticalInsets() : Insets.NONE;Insets childInsets = getOpticalInsets();// 内部实际上是调用setFrame()return setFrame(left   + parentInsets.left - childInsets.left,top    + parentInsets.top  - childInsets.top,right  + parentInsets.left + childInsets.right,bottom + parentInsets.top  + childInsets.bottom);}// 回到调用原处/*** 分析3:onLayout()* 注:对于单一View的laytou过程*    1. 由于单一View是没有子View的,故onLayout()是一个空实现*    2. 由于在layout()中已经对自身View进行了位置计算:setFrame() / setOpticalFrame()*    3. 所以单一View的layout过程在layout()后就已完成了*/ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {// 参数说明// changed 当前View的大小和位置改变了 // left 左部位置// top 顶部位置// right 右部位置// bottom 底部位置
}  

总结

单一View的layout过程解析如下:
在这里插入图片描述

2.2ViewGroup的layout过程

具体使用

继承自ViewGroup 或 各种Layout;含有子 View

原理
从ViewGroup至子View、自上而下遍历进行(即树形递归),通过计算整个ViewGroup中各个View的属性,从而最终确定整个ViewGroup的属性。即:

  • 计算自身ViewGroup的位置:layout()
  • 遍历包含的所有子View,确定所有子View在ViewGroup的位置:onLayout()

a. 步骤2 类似于 单一View的layout过程
b. 自上而下、一层层地传递下去,直到完成整个View树的layout()过程

在这里插入图片描述

具体流程

在这里插入图片描述

这里需要特别注意的是:
ViewGroup 和 View 同样拥有方法:layout()、onLayout(),但二者应用场景是不一样的:

  • 一开始计算ViewGroup位置时,调用的是ViewGroup的layout()和onLayout();
  • 当开始遍历子View及计算子View位置时,调用的是子View的layout()和onLayout(),类似于单一View的layout过程。

源码分析

/*** 源码分析:layout()* 作用:确定View本身的位置,即设置View本身的四个顶点位置* 注:与单一View的layout()源码一致*/ public void layout(int l, int t, int r, int b) {  // 当前视图的四个顶点int oldL = mLeft;  int oldT = mTop;  int oldB = mBottom;  int oldR = mRight;  // 1. 确定View的位置:setFrame() / setOpticalFrame()// 即初始化四个顶点的值、判断当前View大小和位置是否发生了变化 & 返回 // setFrame() ->分析1// setOpticalFrame() ->分析2boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);// 2. 若视图的大小 & 位置发生变化// 会重新确定该View所有的子View在父容器的位置:onLayout()if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  onLayout(changed, l, t, r, b);  // 对于单一View的laytou过程:由于单一View是没有子View的,故onLayout()是一个空实现(上面已分析完毕)// 对于ViewGroup的laytou过程:由于确定位置与具体布局有关,所以onLayout()在ViewGroup为1个抽象方法,需重写实现 ->分析3...}  /*** 分析1:setFrame()* 作用:确定View本身的位置,即设置View本身的四个顶点位置*/ protected boolean setFrame(int left, int top, int right, int bottom) {...// 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点// 从而确定了视图的位置mLeft = left;mTop = top;mRight = right;mBottom = bottom;mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);}/*** 分析2:setOpticalFrame()* 作用:确定View本身的位置,即设置View本身的四个顶点位置*/ private boolean setOpticalFrame(int left, int top, int right, int bottom) {Insets parentInsets = mParent instanceof View ?((View) mParent).getOpticalInsets() : Insets.NONE;Insets childInsets = getOpticalInsets();// 内部实际上是调用setFrame()return setFrame(left   + parentInsets.left - childInsets.left,top    + parentInsets.top  - childInsets.top,right  + parentInsets.left + childInsets.right,bottom + parentInsets.top  + childInsets.bottom);}// 回到调用原处/*** 分析3:onLayout()* 作用:计算该ViewGroup包含所有的子View在父容器的位置()* 注: *    a. 定义为抽象方法,需重写,因:子View的确定位置与具体布局有关,所以onLayout()在ViewGroup没有实现*    b. 在自定义ViewGroup时必须复写onLayout()!!!!!*    c. 复写原理:遍历子View 、计算当前子View的四个位置值 & 确定自身子View的位置(调用子View layout())*/ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {// 参数说明// changed 当前View的大小和位置改变了 // left 左部位置// top 顶部位置// right 右部位置// bottom 底部位置// 1. 遍历子View:循环所有子Viewfor (int i=0; i<getChildCount(); i++) {View child = getChildAt(i);   // 2. 计算当前子View的四个位置值// 2.1 位置的计算逻辑...// 需自己实现,也是自定义View的关键// 2.2 对计算后的位置值进行赋值int mLeft  = Leftint mTop  = Topint mRight = Rightint mBottom = Bottom// 3. 根据上述4个位置的计算值,设置子View的4个顶点:调用子view的layout() & 传递计算过的参数// 即确定了子View在父容器的位置child.layout(mLeft, mTop, mRight, mBottom);// 该过程类似于单一View的layout过程中的layout()和onLayout(),此处不作过多描述}}
} 

总结

对于视图组ViewGroup的布局流程(Layout)流程及各个方法说明总结如下:
在这里插入图片描述
这里需要特别注意的是:
ViewGroup 和 View 同样拥有方法:layout()、onLayout(),但二者应用场景是不一样的:

  • 一开始计算ViewGroup位置时,调用的是ViewGroup的layout()和onLayout();
  • 当开始遍历子View及计算子View位置时,调用的是子View的layout()和onLayout(),类似于单一View的layout过程。

至此,ViewGroup的 layout过程已讲解完毕。

三、细节问题:getWidth() ( getHeight())与 getMeasuredWidth() (getMeasuredHeight())获取的宽 (高)有什么区别?

首先明确定义:

getWidth() / getHeight():获得View最终的宽 / 高
getMeasuredWidth() / getMeasuredHeight():获得 View测量的宽 / 高

先看下各自的源码:

// 获得View测量的宽 / 高public final int getMeasuredWidth() {  return mMeasuredWidth & MEASURED_SIZE_MASK;  // measure过程中返回的mMeasuredWidth}  public final int getMeasuredHeight() {  return mMeasuredHeight & MEASURED_SIZE_MASK;  // measure过程中返回的mMeasuredHeight}  // 获得View最终的宽 / 高public final int getWidth() {  return mRight - mLeft;  // View最终的宽 = 子View的右边界 - 子view的左边界。}  public final int getHeight() {  return mBottom - mTop;  // View最终的高 = 子View的下边界 - 子view的上边界。}  

二者的区别:

在这里插入图片描述
**上面标红:一般情况下,二者获取的宽 / 高是相等的。**那么,“非一般”情况是什么?

答:人为设置:通过重写View的 layout()强行设置


@Override
public void layout( int l , int t, int r , int b){// 改变传入的顶点位置参数super.layout(l,t,r+100,b+100);// 如此一来,在任何情况下,getWidth() / getHeight()获得的宽/高 总比 getMeasuredWidth() / getMeasuredHeight()获取的宽/高大100px// 即:View的最终宽/高 总比 测量宽/高 大100px}

虽然这样的人为设置无实际意义,但证明了View的最终宽 / 高 与 测量宽 / 高是可以不一样

结论

在非人为设置的情况下,View的最终宽/高(getWidth() / getHeight())
与 View的测量宽/高 (getMeasuredWidth() / getMeasuredHeight())永远是相等

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

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

相关文章

Java高级——编译JDK

编译JDKJDK是什么&#xff1f;编译环境搭建JDK下载编译参数编译在IDE中调试源码JDK是什么&#xff1f; Java Development Kit&#xff0c;用于执行和开发java程序 编译环境搭建 本文采用Ubantu 18.04 执行以下命令安装依赖 apt-get install build-essential apt-get instal…

Transformer

参考 https://www.ylkz.life/deeplearning/p12158901/ https://zhuanlan.zhihu.com/p/396221959 模型结构 Input Embedding 将文本中词汇的数字表示转变为向量表示, 希望得到其在高维空间中的特征表示向量。 # 导入必备的工具包 import torch import torch.nn as nn import …

Qt5开发从入门到精通——第九篇一节( Qt5 文件及磁盘处理—— 读写文本文件)

CSDN话题挑战赛第2期 参赛话题&#xff1a;学习笔记 欢迎小伙伴的点评✨✨&#xff0c;相互学习c/c应用开发。&#x1f373;&#x1f373;&#x1f373; 博主&#x1f9d1;&#x1f9d1; 本着开源的精神交流Qt开发的经验、将持续更新续章&#xff0c;为社区贡献博主自身的开源精…

esp32-C3 CAN接口使用

esp32-C3 CAN接口使用功能概述CAN协议关注点接收过滤器单过滤器模式双过滤器模式关键函数说明配置和安装驱动获取TWAI状态信息发送/接收消息使用示例CAN控制器自回环测试CAN收发带过滤测试功能概述 ESP32-C3具有1个CAN控制器支持以下特性&#xff1a; 兼容ISO 11898-1协议(CA…

伟大的micropython smartconfig 配网它来了!!!

我这其实只是实验和搬运&#xff0c;还是感谢伟大的walkline群主&#xff0c;他弄好的&#xff0c;我只是负责搬运发布给新手看。 之前一大堆人问我配网的事儿&#xff0c;输入下wifi名称密码这么麻烦吗&#xff0c;好吧&#xff0c;有求必应&#xff0c;之前的配网是通过ap模式…

PICO高管专访:关于PICO 4硬件、内容、定价、海外布局的一切解答

PICO 4昨天正式在国内发布&#xff0c;简单来说这是一款相对均衡的VR一体机&#xff0c;在硬件素质、内容生态建设上都可圈可点&#xff0c;对于国内还未入手VR的朋友们来说是非常好的选择。相关阅读&#xff1a;《PICO 4评测&#xff1a;Pancake光学新标杆&#xff0c;VR娱乐V…

20【访问者设计模式】

文章目录二十、访问者设计模式20.1 访问者设计模式简介20.1.1 访问者设计模式概述20.1.2 访问者设计模式的UML类图20.2 访问者设计模式的实现20.3 访问者设计模式的优缺点二十、访问者设计模式 20.1 访问者设计模式简介 20.1.1 访问者设计模式概述 访问者设计模式&#xff0…

计算机网络基础 VLSM----可变长子网掩码;CIDR技术----无类域间路由;

VLSM----可变长子网掩码&#xff1a; 概述&#xff1a; 通过网络位向主机位借位的方式&#xff0c;延长子网掩码&#xff0c;从而达到将一个大网络划分为多个小网络&#xff1b;借出的位数称之为子网位&#xff0c;决定了能划分网络的个数。 优点&#xff1a; 更高效的利用…

记一次导入下载好的源码工程到本地工程异常解决方案

今天在学习okhttp相关视频时&#xff0c;安装视频的操作在自己的工程中引入三方的模块&#xff0c;但是发现引入后和预期的不一致。不一致指的是&#xff0c;视频中以module方式引入sample-okhttp并解决冲突后&#xff0c;sample-okhttp能够被android stuidio识别为applicayion…

Style样式设置器

构成Style最重要的两种元素&#xff1a; Setter类帮助我们设置控件的静态外观风格 Trigger类则帮助我们设置控件的行为风格。 Setter,设置器&#xff0c;我们给属性赋值的时候一般都采用“属 性名属性值”的形式 上面的例子中针对TextBlock的Style&#xff0c;Style中使用 若…

解决csdn强制关注博主才能阅读文章

问题 有的时候查阅资料的时候,关注博主并不是很方便,查csdn会出现下面的提示解决办法 打开控制台输入以下代码: var article_content=document.getElementById("article_content"); article_content.removeAttribute("style");var follow_text=document…

深入理解计算机系统——第七章 Linking

深入理解计算机系统——第七章 Linking7.1 Compiler Drivers7.2 Static Linking7.3 Object Files7.4 Relocatable Object Files7.5 Symbols and Symbol Tables7.6 Symbol Resolution7.6.1 How Linkers Resolve Duplicate Symbol Names7.6.2 Linking with Static Libraries7.6.3…

人体神经元结构示意图,神经细胞内部结构图

人体神经结构图&#xff1f;&#xff1f;&#xff1f;&#xff1f; 谷歌人工智能写作项目&#xff1a;神经网络伪原创 下图为神经系统的结构示意图&#xff0c;请根据图回答&#xff1a; &#xff08;1&#xff09;构成神经系统的结构、功能单位是神经元&#xff0c;图中E部分…

19【迭代器设计模式】

文章目录十九、迭代器设计模式19.1 迭代器设计模式简介19.1.1 迭代器设计模式概述19.1.2 迭代器设计模式的UML类图19.2 迭代器设计模式的实现19.3 迭代器设计模式的优缺点十九、迭代器设计模式 19.1 迭代器设计模式简介 19.1.1 迭代器设计模式概述 迭代器设计模式&#xff0…

DeFi借贷重新洗牌 透过协议变化能找到哪些新趋势?

在过去的几个月里&#xff0c;DeFi 借贷赛道产生了重大变化&#xff0c;1kx 研究员 Mikey 0x 对此场域重新进行梳理&#xff0c;BlockBeats 对其整理翻译如下&#xff1a; 本文内容将包括对新借贷协议的介绍、核心数据统计以及发展趋势&#xff0c;也许可以让我们大致把握下一…

Python3操作MongoDB数据库

Python3操作MongoDB数据库 文章目录Python3操作MongoDB数据库0. 写在前面1. 安装开源驱动库pymongo2. 参考0. 写在前面 Linux&#xff1a;Ubuntu Kylin 16.04MongoDB&#xff1a;MongoDB3.2.7Python&#xff1a;Anaconda With Python3.7 1. 安装开源驱动库pymongo pymongo驱动…

公众号题库搜题对接(免费接口)

公众号题库搜题对接(免费接口) 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&a…

用神经网络表示与逻辑,神经网络实现逻辑运算

数据挖掘中的神经网络和模糊逻辑的概念是啥&#xff1f; 【神经网络】人工神经网络&#xff08;Artificial Neural Networks&#xff0c;简写为ANNs&#xff09;也简称为神经网络&#xff08;NNs&#xff09;或称作连接模型&#xff08;Connection Model&#xff09;&#xff…

Frp内网穿透win系统实录

文章目录前言公网服务器端配置基于Docker配置简单文件配置内网服务器端配置frpc配置安装OpenSSH服务配置连接XShell和Xftp连接前言 由于实验室的某些原因&#xff0c;分配了一台win10的服务器&#xff08;QAQ&#xff09;&#xff0c;但是由于服务器在内网&#xff0c;无法访问…

【常用排序算法】

文章目录写在最前面只想用其中的某个算法&#xff1f;类关系图工具类NumberArrayUtil用于测试排序的父类 SortTest冒泡排序堆排序插入排序归并排序快速排序选择排序希尔排序写在最前面 只想用其中的某个算法&#xff1f; 如果你只是想要对应的排序算法&#xff0c;可删除每个…