前言
在上篇文章Android View体系 - 测量篇中,我们知道:每个View都会接收来自父View的MeasureSpec来进行测量,在确保自身能够独立完成测量逻辑的同时,也能向下推导,促使其子View的测量。
与测量流程同为View三大流程的布局流程,是如何独立完成自身的布局流程,又能向下推导,促使孩子节点的布局的?本文将围绕着View布局流程的源码,尝试探索View的布局流程。
View的布局
一棵View树的布局起源于ViewRootImpl的performTranversals方法:
1 | // ViewRootImpl.java |
在performTraversals内部会调用performLayout方法,从根View开始测量:
1 | // ViewRootImpl.java |
在performLayout内部,逻辑大致可以分为两块:
- 执行根
View的layout方法开始布局; - 处理在布局过程中,当前
View树的所有requestLayout请求;
第2块不是主流程,主要来看第1块:
1 | // View.java |
在View的layout方法中,主要的逻辑分为两块:
- setFrame - 为当前
View设置布局的位置 - onLayout - 为当前
View的子View布局
setFrame
先看看View的setFrame方法。
1 | // View.java |
setFrame方法内部的逻辑相对较为简单,内部逻辑会与上次设置的left、top、right、bottom进行比对,如果发生了改变,就会设置新值,否则啥也不干。
onLayout
再来看看View的onLayout方法:
1 | // View.java |
可以看到View的onLayout是一个空实现。这也可以理解,因为onLayout是用来布局子View的,一个View不一定会有子View,ViewGroup才有可能有。
ViewGroup的布局
ViewGroup也是View,不同的是,它能够承载其他View。ViewGroup布局的入口也是layout方法,它重写了(也是唯一一个重写layout的View子类)layout方法:
1 | // ViewGroup.java |
从代码逻辑可以看到,如果一个ViewGroup没有通过suppressLayout方法来禁用布局,且动画没有在改变布局的位置时,就会调用超类View的layout方法,因此所做的逻辑大致与其超类View的基本一致。
onLayout
ViewGroup的layout方法逻辑与View的基本一致,View的layout方法有两个关键的方法:setFrame和onLayout。setFrame方法ViewGroup没有重写,我们来看看ViewGroup的onLayout方法:
1 | // ViewGroup.java |
可以看到,ViewGroup的onLayout方法变成了抽象方法,子类必须实现它,以完成布局ViewGroup子View的逻辑。
以FrameLayout 为例:
1 | // FrameLayout.java |
虽然代码很长,但是能看出来这段代码做了三件事:
- 计算每个子
View的left - 计算每个子
View的top - 调用子
View的layout方法布局子View
计算子View的left依托于FrameLayout的left、水平方向的gravity(左对齐、水平居中、右对齐)
计算子View的top依托于FrameLayout的top、竖直方向的gravity(顶部对齐、竖直居中、底部对齐)
计算完子View的left和top后,依据子View的measuredWidth和measuredHeight,就能计算出子View的right和bottom,因此就能调用子View的layout方法为子View进行布局。
坐标系、padding和margin
通读完View、ViewGroup和FrameLayout的布局代码,left、top、right、bottom这几个代表什么意思?
如果把一个View理解成一个矩形:
- left - 代表这个矩形的左边与父
View左边的距离 - top - 代表这个矩形的上边与父
View上边的距离 - right - 代表这个矩形的右边与父
View左边的距离 - bottom - 代表这个矩形的下边与父
View上边的距离
可以看到,left、top、right、bottom是基于子View相对于父View的相对坐标系上的定义。相对坐标系的好处在于
- 父
View可以对子View的布局位置进行限制; - 相比于绝对坐标系(以屏幕为参考系),相对坐标系在递归时更容易对参数进行处理
此外,在View、ViewGroup和FrameLayout的布局代码中,还出现了padding和margin:
- padding - 父
View的空出的位置,压缩子View的布局空间 - margin - 子
View主动让出的空间
一图胜千言:

View树的布局
了解了坐标系、padding和margin后,就可以从FrameLayout的布局代码,还原出整棵View树的布局流程:
- 父
View调用setFrame为自己圈定布局的矩形范围; - 以父
View的left、top作为原点,根据padding将子View的布局限定在一个矩形区域内; - 在这个矩形区域内,根据子
View的margin、measuredWidth和measuredHeight,布局子View;
总而言之,一棵View树的布局流程如图:

总结
View布局的总体流程相较于View测量的而言较为简单,主要分为是否有子View这两种情况:
- 有子
View-ViewGroup会首先调用setFrame圈住一个(left, top, right, bottom)的矩形区域,再通过onLayout调用子View的layout方法为子View布局; - 无子
View- 没有子View的View只会调用setFrame方法,然后调用一般为空实现的onLayout方法。
而解决画在哪这个问题的关键在于setFrame方法,setFrame方法的参数left、top、right和bottom是建立在以当前View的父View为参考系的相对坐标系,从而使得View能够无需关心屏幕坐标及屏幕尺寸,能够在父View给定的(left, top, right, bottom)的约束下进行布局。