Android onLayout布局流程解析

news2025/1/15 13:02:33

组件布局流程结论

1.)layout流程始于ViewRootImpl的performLayout()方法,该方法会调用根View(DecorView)的layout()方法进行布局,因为DecorView是ViewGroup(FrameLayout),所以layout流程来到了ViewGroup(其实调用的是父类View类)的layout()方法

  1. )layout()方法负责组件自身的布局,会存储mLeft、mTop、mRight、mBottom 四个值来存储自身的位置,如果布局变化,还会回调onSizeChanged()方法通知我们布局发生了变化。最后layout()方法还会调用onLayout()方法进行子组件的布局。
  2. )View类因为没有子组件,所以View类的onLayout()方法是个空实现,ViewGroup类因为有子组件,但我们自定义的ViewGroup类都有自己的布局规则,所以ViewGroup类的onLayout()方法是个抽象方法,需要子类去实现,所以我们直接继承ViewGroup类一定需要重写onLayout()方法。
  3. )实现ViewGroup的onLayout()方法,我们需要循环所有的子组件,根据我们自己的布局规则,确定他们的位置,然后调用子组件的layout()方法 ,让子组件存储自身的位置,如果子组件是View,则没有后续的布局流程,如果子组件还是ViewGroup,则会重复上述步骤,直到完成所有的布局流程。

View的layout方法

这是一个被final修饰的方法,意味着无法被子类重写。但是内部事件还是调用了View中的layout方法

View中的onLayout是一个空实现的方法,通过layout方法将新的left、top、right、bottom传给onLayout。   官方注释在此视图应该时从布局调用(layout方法执行),此视图(onLayout方法执行)给每个孩子分配一个大小和位置。子类的派生类应该重写此方法并在每个子View上调用布局(layout方法)。   View的子类当然是ViewGroup了,所以ViewGroup更加像是一个View的管理器,用来实现对子View的大小和位置变化进行控制。简单来说View会通过onLayout方法进行确认View的显示位置。

ViewGroup中的onLayout方法是一个抽象方法,所以子类必须实现。并且调用View中的layout方法来确认View的位置和大小。

在layout方法中mLeft、mTop、mRight、mBottom这四个值是用来进行对View位置的摆放和大小的限制,是相对于父控件而言的。

如图白色区域是父View,黑色区域为子View。

针对getWidth()和getMeasuredWidth()进行分析 ,先看下区别

mMeasuredWidth这个值之前在 小试牛刀-onMeasure方法 中介绍过是View的实际大小宽对应的值是mMeasuredWidth,而mMeasuredWidth是通过setMeasuredDimension方法设置进来的。所以在measure方法结束后mMeasuredWidth才会有值,此时调用getMeasuredWidth可以获取对应的值。

在getWidth()中是通过mRight - mLeft的计算返回的结果。而mRight和mLeft这两个值,上面有介绍是通过layout传递过来的。

Layout(源码分析):

Layout的作用就是为整个View树计算实际的位置,而通过刚才对View树的介绍知道,想计算整个View树的位置,就需要递归的去计算每一个子视图的位置(Measure同理)。

而确定这个位置很简单,只需要mLeft,mTop,mRight,mBottom四个值(注意:这4个值是子View相对于父View的值,下面会详细介绍)。

在代码中如何设置这4个值呢? 首先,无论是系统提供的LinearLayout还是我们自定义的View视图,他都需要继承自ViewGroup类,之后必须要做的就是重写onLayout方法(因为在onLayout在ViewGroup中被定义为抽象方法)。

ViewGroup-onlayout:

@Override  
protected abstract void onLayout(boolean changed, int l, int t, int r, int b); 12

onLayout被定义为抽象方法,所以在继承ViewGroup时必须要重写该方法(onMeasure不需要)。另外这个方法也被override标注,所以也是重写的方法,他重写的是其父类view中的onLayout方法。

View-onlayout:

/** 
 * 当这个view和其子view被分配一个大小和位置时,被layout调用。 
 * @param changed 当前View的大小和位置改变了 
 * @param left 左部位置(相对于父视图) 
 * @param top 顶部位置(相对于父视图) 
 * @param right 右部位置(相对于父视图) 
 * @param bottom 底部位置(相对于父视图) 
 */  
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}  123456789

注解说:当这个view和其子view被分配一个大小和位置时,被layout调用。所以我们去看看layout中做了什么。(注解没有完全按照英文翻译,并且有省略)

View-layout:

/** 
 * 给View和其所有子View分配大小和位置 
 * 
 * 这是布局的第二个阶段(第一个阶段是测量)。在这个阶段中,每个父视图需要去调用layout去为他所有的子视图确定位置 
 * 派生的子类不应该重写layout方法,应该重写onLayout方法,在onlayout方法中应该去调用每一个view的layout 
 */  
public void layout(int l, int t, int r, int b) {  
    // 将当前视图的左上右下记录为old值(参数中传入的为新的l,t,r,b值)  
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
​
    // setFrame方法的作用就是将新传入的ltrb属性赋值给View,然后判断当前View大小和位置是否发生了变化并返回  
    boolean changed = setFrame(l, t, r, b);  
​
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  
        // 调用onLayout回调方法,具体实现由重写了onLayout方法的ViewGroup的子类去实现(后面详细说明)  
        onLayout(changed, l, t, r, b);  
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;  
​
        // 调用所有重写了onLayoutChange监听的方法,通知View大小和位置发生了改变  
        ListenerInfo li = mListenerInfo;  
        if (li != null && li.mOnLayoutChangeListeners != null) {  
            ArrayList<OnLayoutChangeListener> listenersCopy =  
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();  
            int numListeners = listenersCopy.size();  
            for (int i = 0; i < numListeners; ++i) {  
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
            }  
        }  
    }  
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;  
}  12345678910111213141516171819202122232425262728293031323334

在这段代码中我们只要知道:如果视图的大小和位置发生变化后,会调用我们前面分析过的onLayout方法。 对于onLayout方法的最终实现全部依靠我们在自定义ViewGroup类中重写的onLayout去实现。

计算View位置:

在重写的onLayout方法中,唯一的目的就是: 对当前视图和其所有子View设置它们在父视图中具体位置(确定这个位置就依靠mLeft,mTop,mRight,mBottom这四个值) 之前介绍过,mLeft,mTop,mRight,mBottom这四个值表示的是子view相对于父view的位置。下面我贴出我画的图看一下就明白了。

如图,黄色区域是我们的父view,而中间的深色的区域就是我们的子view。 所以对于这个View来说,我列出它相对于父view的各个值是如何计算和相关函数:

mLeft,mTop,mRight,mBottom:

view.getLeft()——mLeft:子View左边界到父view左边界的距离

public final int getLeft() {  
    return mLeft;  
}  123

view.getTop()——mTop:子View上边界到父view上边界的距离 view.getRight()——mRight:子View右边界到父view左边界的距离 view.getBottom()——mBottom:子View下边距到父View上边界的距离

视图宽高:

视图宽度 view.getWidth();子View的右边界 - 子view的左边界。

public final int getWidth() {  
    return mRight - mLeft;  
}  123

视图高度 view.getHeight() ;子View的下边界 - 子view的上边界。

public final int getHeight() {  
    return mBottom - mTop;  
} 123

测量宽高:

view.getMeasuredWidth();measure过程中返回的mMeasuredWidth

public final int getMeasuredWidth() {  
    return mMeasuredWidth & MEASURED_SIZE_MASK;  
}  123

view.getMeasuredHeight();measure过程中返回的mMeasuredHeight

public final int getMeasuredHeight() {  
    return mMeasuredHeight & MEASURED_SIZE_MASK;  
}  123

最后介绍一下getWidth/Height和getMeasuredWidth/Height的区别: getWidth,和getLeft等这些函数都是View相对于其父View的位置。而getMeasuredWidth,getMeasuredHeight是测量后该View的实际值(有点绕,下面摘录一段jafsldkfj所写的Blog中的解释). 实际上在当屏幕可以包裹内容的时候,他们的值是相等的,只有当view超出屏幕后,才能看出他们的区别: getMeasuredHeight()是实际View的大小,与屏幕无关,而getHeight的大小此时则是屏幕的大小。 当超出屏幕后,getMeasuredHeight()等于getHeight()加上屏幕之外没有显示的大小

在计算子View在父View中的位置时,主要就是应用上面这几个函数。下面就来看看如何去重写onLayout。

onLayout:

对于重写onLayout的思路和重写onMeasure相同: 如果只需要测量单个View,则单独测量它自己就行。如果需要测量的View其下还有子View,则需要测量其所有的子View。

就以上面的View为例子,他最外面是一个黄色的父View,中间一个居中的深色子View。 我的思路如下: 如果想画出一个View,就要计算它的l,t,r,b值。并传递到onlayout( l, t, r, b )中; mRight = view.getWidth + mLeft; mBottom = view.getHeight + mTop; 所以最后可以用如下形式传入:onlayout( l, t, l+width, t+height );

剩下的任务就只需要知道它的mLeft值,mTop值,加上长、宽值就行了。 长宽值很简单,使用getWidth/Height和getMeasuredWidth/Height都可以。 由于这个View需要居中显示,剩下的问题就是如何计算该View的mLeft值和mTop值。我的思路如下: r(父View的mRight) = mLeft + width + mLeft(因为左右间距一样) b(父View的mBottom) = mTop + height + mTop(因为上下间距一样)

我的代码如下:

@Override  
protected void onLayout(boolean changed, int l, int t, int r, int b) {  
​
       // 循环所有子View  
       for (int i=0; i<getChildCount(); i++) {  
           View child = getChildAt(i);  
           // 取出当前子View长宽  
           int width = child.getMeasuredWidth();  
           int height = child.getMeasuredHeight();  
​
           // 计算当前的mLeft和mTop值(r,b为传递进来的父View的mRight和mBottom值)  
           int mLeft = (r - width) / 2;  
           int mTop = (b - height) / 2;  
​
           // 调用layout并传递计算过的参数为子view布局  
           child.layout(mLeft, mTop, mLeft + width, mTop + height);  
       }  
   }  123456789101112131415161718

布局文件如下:

<com.gxy.text.CostomViewGroup xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="wrap_content"  
    android:layout_height="wrap_content"  
    android:background="#eee999" >  
​
    <Button  
        android:text="ChildView"  
        android:layout_width="200dip"  
        android:layout_height="200dip"  
        android:background="#333444"  
        android:id="@+id/textView2" />  
</com.gxy.text.CostomViewGroup> 123456789101112

效果图:

总结:

onMeasure和onLayout的大致总结完了,在自定义View的时候最关键的是onLayout,因为无论你如何Measure这个View的大小,最后的决定权永远在onLayout手中,onLayout会决定具体View的大小和位置。当然onMeasure也很重要,有的情况控件的宽高不确定或者需要自定义,这时候需要我们人工Measure它。而在复杂的自定义View时,很多计算也需要在onMeasure中完成,并且些值会记录下来在onLayout中重新使用。

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

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

相关文章

【C语言指针练习题】你真的学会指针了吗?

✨✨✨✨如果文章对你有帮助记得点赞收藏关注哦&#xff01;&#xff01;✨✨✨✨ 文章目录✨✨✨✨如果文章对你有帮助记得点赞收藏关注哦&#xff01;&#xff01;✨✨✨✨一维数组练习题&#xff1a;字符数组练习题&#xff1a;字符指针练习题&#xff1a;二维数组练习题&am…

LeetCode-63. 不同路径 II

题目来源 63. 不同路径 II 递归 class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int row obstacleGrid.length-1;int col obstacleGrid[0].length-1;return process(row,col,0,0,obstacleGrid);}private int process(int row ,int col,int i…

重装系统一半电脑蓝屏如何解决

小编相信大家在使用电脑或者给电脑重装系统的时候都遇到过电脑蓝屏等等故障问题。最近有用户就遇到了这样一个问题&#xff0c;问小编重装系统一半电脑蓝屏怎么办&#xff0c;那么今天小编就告诉大家重装系统一半电脑蓝屏的解决方法。 工具/原料&#xff1a; 系统版本&#x…

Tecent libco C++协程库初探

安装 https://github.com/Tencent/libco 上把release版本的下下来&#xff1a; mkdir build && cd build && cmake .. && make拿到动态和静态库啦&#xff0c;然后cp到/usr/local/lib就完成安装啦。 项目有很多example&#xff0c;直接进根目录make就…

phpinfo包含临时文件Getshell全过程及源码

目录 前言 原理 漏洞复现 靶场环境 源码 复现过程 前言 PHP LFI本地文件包含漏洞主要是包含本地服务器上存储的一些文件&#xff0c;例如session文件、日志文件、临时文件等。但是&#xff0c;只有我们能够控制包含的文件存储我们的恶意代码才能拿到服务器权限。假如在服…

B. Sherlock and his girlfriend

Sherlock has a new girlfriend (so unlike him!). Valentines day is coming and he wants to gift her some jewelry. He bought n pieces of jewelry. The i-th piece has price equal to i  1, that is, the prices of the jewelry are 2, 3, 4, ... n  1. Watson…

跳表--C++实现

目录 作者有话说 为何要学习跳表&#xff1f;为了快&#xff0c;为了更快&#xff0c;为了折磨自己..... 跳表作用场景 1.不少公司自己会设计哈希表&#xff0c;如果解决哈希冲突是不可避免的事情。通常情况下会使用链址&#xff0c;很好理解&#xff0c;当有冲突产生时&#…

深度学习训练营之识别宝可梦人物和角色

深度学习训练营之识别宝可梦人物和角色原文链接环境介绍前置工作设置GPU数据加载数据查看数据预处理加载数据可视化数据检查数据配置数据集prefetch()功能详细介绍&#xff1a;调用官方的网络的模型模型训练官方模型调用设置动态学习率模型训练模型评估结果分析参考链接原文链接…

【Redis】Redis 如何实现分布式锁

Redis 如何实现分布式锁1. 什么是分布式锁1.1 分布式锁的特点1.2 分布式锁的场景1.3 分布式锁的实现方式2. Redis 实现分布式锁2.1 setnx expire2.2 set ex px nx2.3 set ex px nx 校验唯一随机值&#xff0c;再删除2.4 Redisson 实现分布式锁1. 什么是分布式锁 分布式锁其实…

【C语言进阶:指针的进阶】回调函数

本章重点内容&#xff1a; 字符指针指针数组数组指针数组传参和指针传参函数指针函数指针数组指向函数指针数组的指针回调函数指针和数组面试题的解析什么是回调函数&#xff1a; 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针&#xff08;地址&#xff09;作…

Lenovo 联想-IdeaPad-Y530电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。硬件型号驱动情况主板联想-IdeaPad-Y530处理器Intel 酷睿2双核 T9400已驱动内存2GB已驱动硬盘2TB HP EX950 PCI-E Gen3 x4 NVMe SSD已驱动显卡NVIDIA GeForce 9300M GS无法驱动声卡Realtek ALC888无法驱动网卡RTL8168H Giga…

【Java学习笔记】3.Java 基础语法

Java 基础语法 一个 Java 程序可以认为是一系列对象的集合&#xff0c;而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。 对象&#xff1a;对象是类的一个实例&#xff0c;有状态和行为。例如&#xff0c;一条狗是一个对象&#xff…

【NLP相关】从零开始理解BERT模型:NLP领域的突破(BERT详解与代码实现)

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Python异常处理更新,正常和不正常的都在这里

嗨害大家好鸭&#xff01;我是小熊猫~ 异常处理篇嗨害大家好鸭&#xff01;我是小熊猫~Python标准异常&#x1f4a8;什么是异常&#xff1f;不正常异常处理&#x1f4a8;使用except而不带任何异常类型使用except而带多种异常类型try-finally 语句异常的参数触发异常用户自定义异…

Lesson12---人工神经网络(1)

12.1 神经元与感知机 12.1.1 感知机 感知机&#xff1a; 1957&#xff0c; Fank Rosenblatt 由两层神经元组成&#xff0c;可以简化为右边这种&#xff0c;输入通常不参与计算&#xff0c;不计入神经网络的层数&#xff0c;因此感知机是一个单层神经网络 感知机 训练法则&am…

MyBatis - 13 - MyBatis逆向工程

文章目录1.准备工作1.1 建表1.2 创建Maven工程1.2.1 在pom.xml中添加依赖和插件&#xff0c;更新maven1.2.2 在src/main/resources下创建mybatis-config.xml1.2.3 在src/main/resources下创建jdbc.properties1.2.4 在src/main/resources下创建log4j.xml文件1.2.5 在src/main/re…

搭建zabbix4.0监控服务实例

一.Zabbix服务介绍 1.1服务介绍 Zabbix是基于WEB界面的分布式系统监控的开源解决方案&#xff0c;Zabbix能够监控各种网络参数&#xff0c;保证服务器系统安全稳定的运行&#xff0c;并提供灵活的通知机制让SA快速定位并解决存在的各种问题。 1.2 Zabbix优点 Zabbix分布式监…

python用openpyxl包操作xlsx文件,统计表中合作电影数目最多的两个演员

题目&#x1f389;&#x1f389;&#x1f389;&#xff1a;编程完成下面任务&#xff1a;已知excel文件“电影导演演员信息表.xlsx”如下图所示&#xff1a;&#x1f373;&#x1f373;&#x1f373;要求&#xff1a;使用 openpyxl 包操作打开此文件&#xff0c;编写程序统计在…

sqlli-labs基本使用

1.安装hackbar插件 链接&#xff1a;https://pan.baidu.com/s/1-QIYmAU-BV_DEONfxovizQ 提取码&#xff1a;dc66 2.SQL注入表信息解析&#xff08;案例使用的sqlli-labs自带的数据库security&#xff09; 2.1 通过order by 判断表有多少列 分析表有多少列&#xff08;通过…

【Storm】【六】Storm 集成 Redis 详解

Storm 集成 Redis 详解 一、简介二、集成案例三、storm-redis 实现原理四、自定义RedisBolt实现词频统计一、简介 Storm-Redis 提供了 Storm 与 Redis 的集成支持&#xff0c;你只需要引入对应的依赖即可使用&#xff1a; <dependency><groupId>org.apache.storm…