前言

在上一篇文章了,我们学习了 View 三大流程之一的 measure 过程,当 measure 过程完成后,View 的大小就测量好了。接下来就到了 layout 的过程了,layout 的过程就是用于确定 View 的位置。下面通过查看源码,来更深入的了解下 layout 的整个过程。

源码分析

View 的 layout 过程是从 ViewRoot 开始的,ViewRoot 的 performTraversals 方法会在 measure 过程执行完后继续执行 performLayout 方法,在该方法中又执行了 View 的 layout 方法:

host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());复制代码

该方法接收四个参数,分别是 left、top、right、bottom 四个坐标,代表 View 的四个顶点位置,其中 left、top 的参数为 0,right、bottom 则把测量好的宽、高传入了进来,下面继续看下 layout 方法的具体实现:

public void layout(int l, int t, int r, int b) {

if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {

onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);

mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;

}

// 记录 View 的原始位置

int oldL = mLeft;

int oldT = mTop;

int oldB = mBottom;

int oldR = mRight;

// 判断 isLayoutModeOptical 方法的返回值, true 则执行 setOpticalFrame 方法,否则执行 setFrame

// setOpticalFrame 方法最终也走了 setFrame 方法,所以最终都会执行 setFrame 方法

// setFrame 方法会判断 View 位置是否发生改变, 如果发生改变, 会将 View 新的 left、top、right、bottom 值赋值给成员变量,并返回一个 boolean 值,表示位置是否改变

// 当 setFrame 完成后,表示 View 本身的位置已经确定

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

// 如果位置发生改变,执行 onLayout 方法,该方法在 View 中是个空实现,需要继承 ViewGroup 的类实现

// onLayout 方法的作用是 ViewGroup 确定子 View 的位置

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

onLayout(changed, l, t, r, b);

mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

ListenerInfo li = mListenerInfo;

if (li != null && li.mOnLayoutChangeListeners != null) {

ArrayList listenersCopy =

(ArrayList)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;

mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

}复制代码

layout 方法首先通过调用 setFrame 方法判断 View 的位置是否发生改变,如果发生改变,则将新位置的值 left、top、right、bottom 赋值给 mLeft、mTop、mRight、mBottom 这几个成员变量,这四个值保存着 View 的位置信息,我们可以通过 getLeft、getTop、getRight、getBottom 方法获取到。

所以我们如果想要得到 View 的位置信息,那么必须在 setFrame 方法执行完毕后获取,比如说在 onLayout 方法中获取,因为通过看源码我们已经知道,onLayout 方法是在 setFrame 方法之后执行的。

当 setFrame 方法执行完成后,会返回一个 View 位置是否发生改变的 boolean 值,如果发生改变,那么就会走 onLayout 方法,该方法在 ViewGroup 中调用,用于确定子 View 的位置。

由于具体每种布局的实现效果都不同,所以 onLayout 的默认实现,和上一篇 measure 测量过程中 ViewGroup 的 onMeasure 方法一样,是空实现,具体怎么确定子 View 的位置,由 ViewGroup 的具体实现类去作不同实现。

下面,我们可以自定义一个只能包含一个 View 的布局,来实现下 onLayout 方法,有兴趣的同学也可以自己去看看 LinearLayout 等布局的 onLayout 实现。

实例 (单布局)

代码实现:

public class SingleLayout extends ViewGroup {

public SingleLayout(Context context, AttributeSet attrs) {

super(context, attrs);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

if(getChildCount() > 0){

View childAt = getChildAt(0);

measureChild(childAt, widthMeasureSpec, heightMeasureSpec);

}

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

if(getChildCount() > 0){

View childAt = getChildAt(0);

childAt.layout(0, 0, childAt.getMeasuredWidth(), childAt.getMeasuredHeight());

}

}

}复制代码

该自定义布局很简单,首先在 onMeasure 方法中判断是否有子 View,如果有,那么只测量第一个 View。接着在 onLayout 中判断是否有子 View,如果有,则调用了第一个子 View 的 layout 方法,分别传入 0, 0, childAt.getMeasuredWidth(), childAt.getMeasuredHeight() 四个参数,这四个参数分别代表了子 View 在 当前自定义布局中的位置,也就是放置在当前自定义布局的左上角。

当子 VIew 在 layout 方法中确定了自己的位置后,如果子 View 是 ViewGroup 那么又会去调用 onLayout 方法去确定它子 View 的位置。这样一层一层传递下去,就完成了整个 View 数的 layout 过程。

下面我们把刚刚写的布局运行下,看下效果:

android:layout_width="match_parent"

android:layout_height="wrap_content">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Hello World!"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Hello World2!"/>

复制代码

运行结果:

可以看到,我们尽管在布局下面放了两个 View,但最终显示的只有一个 View。

getMeasuredWidth 和 getWidth 的区别

getMeasuredWidth / getMeasuredHeight 的值是在 onMeausre 方法结束后可以获取到的,getWidth / getHeight 的值是在 onLayout 方法结束后可以获取到的。

通过查看 View 的 getWidth 、getHeight 源码:

public final int getWidth() {

return mRight - mLeft;

}

public final int getHeight() {

return mBottom - mTop;

}复制代码

结合 mLeft、mTop、mRight、mBottom 这四个成员变量的赋值过程来看,getWidth 方法的返回值刚好就是 getMeasuredWidth,因为 getMeasuredWidth - 0 不就还是 getMeasuredWidth 吗。

但是在某些特殊情况下,还是会导致两者的返回值不相同,比如说以刚刚自定义的 SingleLayout 的例子看来,我如果将调用子 View 的 layout 方法修改为:

childAt.layout(100, 100, childAt.getMeasuredWidth(), childAt.getMeasuredHeight());复制代码

那么最终获取的结果,会发现 getWidth 的值比 getMeasuredWidth 的值少 100px。虽然这样做没有啥意义,但是证明了测量宽高并不一定会等于最终的宽高。

还有一种情况,就是当 View 需要多次测量才能确定自己的测量宽高时,那么在前几次的测量过程中得出的测量宽高有可能并不等于最终的测量宽高,这时获取的测量宽高并不一定与最终宽高相等。

参考

android layout过程分析,Andriod 从 0 开始自定义控件之 View 的 layout 过程 (八)相关推荐

  1. 一步一坑学android之安装andriod studio(andriod studio3.0)

    唔,我下的是andriod studio3.0.1.0,就是首页这个.下载好后,就要开始安装了. 1)安装时遇到有对勾的全打上,填值的默认就好. 2)出现下图 修改idea.properties,它在 ...

  2. android设计一个多线程和画图的程序小球,Android开发之多线程中实现利用自定义控件绘制小球并完成小球自动下落功能实例...

    本文实例讲述了Android开发之多线程中实现利用自定义控件绘制小球并完成小球自动下落功能的方法.分享给大家供大家参考,具体如下: 1.布局界面 xmlns:tools="http://sc ...

  3. Android Studio 配置OpenCV4.4.0 不用安装OpenCV Manager (泪崩居然用了礼拜天2天的时间居然还没配置成功,今天又看了下配置成功了)

    Android Studio 配置OpenCV4.4.0 ,说来惭愧居然用户礼拜天2天的时间呢,期间遇到的问题大致有4个问题 这里我也总结出了最后在列举出来,(可能是新版和之前旧版本不一样的问题按照网 ...

  4. Android使用NDK OpenGL ES3.0绘制一个三角形

    Android使用NDK  OpenGL ES3.0绘制一个三角形 [尊重原创,转载请注明出处]https://blog.csdn.net/guyuealian/article/details/820 ...

  5. Android O(SDK 8.0)新特性,刘海屏

    > Android刘海屏 Android 屏幕,16:9,17:9,19:10,18:9,18.5:9所谓全面屏.挖孔屏.凹凸屏等. android 全面屏/刘海屏有效适配- https://b ...

  6. [日更-2019.4.8、4.9、4.12、4.13] cm-14.1 Android系统启动过程分析(一)-init进程的启动、rc脚本解析、zygote启动、属性服务...

    2019独角兽企业重金招聘Python工程师标准>>> 声明 前阶段在项目中涉及到了Android系统定制任务,Android系统定制前提要知道Android系统是如何启动的. 本文 ...

  7. cm-14.1 Android系统启动过程分析(8)-应用程序进程启动过程

    文章目录 声明 0 写在前面 1 什么应用程序进程? 2 应用程序进程的启动过程 2.1 AMS发送启动应用程序进程请求 2.2 Zygote接收AMS的请求并创建应用程序进程 3 启动线程池 4 创 ...

  8. [日更-2019.4.26、27、28] cm-14.1 Android系统启动过程分析(四)-应用程序进程启动过程...

    2019独角兽企业重金招聘Python工程师标准>>> 声明 前阶段在项目中涉及到了Android系统定制任务,Android系统定制前提要知道Android系统是如何启动的: 本文 ...

  9. Android 百度地图 SDK v3.0.0 (四) 引入离线地图功能

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37758097 一直觉得地图应用支持离线地图很重要啊,我等移动2G屌丝,流量不易, ...

最新文章

  1. 最大流 ---- 最小路径覆盖 ---- P2765魔术球问题(网络流24题)
  2. VMware vSphere简介
  3. docker中开启时运行多个不同进程,安装ssh,并在启动docker时与jenkins同时启动运行
  4. 【ES6(2015)】Function函数
  5. matlab自带图片下载,数字图像处理中Matlab的应用.pdf
  6. binlog数据库不写入binlog_MySQL数据库及InnoDB存储引擎的日志文件
  7. 如何将常规元组或字典转换为 namedtuple
  8. Mac和Linux下测试端口是否存活一法[转载]
  9. 一台电脑实现Kvaser CAN总线理论实践、开发与测试!
  10. MathExamV2.0四则混合运算计算题生成器
  11. centos7安装apache
  12. Ubuntu虚拟机下载app网速太慢
  13. 页面加载时,下方内容在上方图片位置闪现
  14. verilog学习笔记:简单的数据选择器modelsim仿真
  15. vite:vue中引入图片报错require is not defined
  16. 巴比特首发 | 银行卡司法冻结应遵循法治程序
  17. matlab中lab颜色空间,使用Matlab绘制图像的rgb颜色空间和Lab颜色空间分量图和分量直方图...
  18. 硬盘验证器(硬盘检测工具)v1.7绿色汉化版
  19. Python去除列表中元素的前后空格和换行
  20. 项目对接支付宝支付,内网穿透实现监听支付宝的支付成功异步回调通知

热门文章

  1. Atcoder AGC031B Reversi (DP计数)
  2. JSON.stringify() 格式化 输出log
  3. App自动化测试之Adb基础命令使用
  4. MySQL DBA面试全揭秘
  5. 如何学习挖掘漏洞[参考多方面资料]
  6. Android UmengShareSDK第三方登录
  7. VSCode 启动 Vue 项目 npm install 报错
  8. bzoj2875: [Noi2012]随机数生成器
  9. [源码]解析 SynchronousQueue 上界,下界.. 数据保存和数据传递. 堵塞队列. 有无频繁await?...
  10. 微信平台开发1--开发者模式基本配置