Android View框架的layout机制
概述
Android中View框架的工作机制中,主要有三个过程:
1、View树的测量(measure) Android View框架的measure机制
2、View树的布局(layout)Android View框架的layout机制
3、View树的绘制(draw)Android View框架的draw机制
View框架的工作流程为:测量每个View大小(measure)-->把每个View放置到相应的位置(layout)-->绘制每个View(draw)。
本文主要讲述三大流程中的layout过程。不清楚measure过程的,可以参考这篇文章 Android View框架的measure机制。
带着问题来思考整个layout过程。
1、系统为什么要有layout过程?
View框架在经过第一步的measure过程后,成功计算了每一个View的尺寸。但是要成功的把View绘制到屏幕上,只有View的尺寸还不行,还需要准确的知道该View应该被绘制到什么位置。除此之外,对一个ViewGroup而言,还需要根据自己特定的layout规则,来正确的计算出子View的绘制位置,已达到正确的layout目的。这也就是layout过程的职责。
该位置是View相对于父布局坐标系的相对位置,而不是以屏幕坐标系为准的绝对位置。这样更容易保持树型结构的递归性和内部自治性。而View的位置,可以无限大,超出当前ViewGroup的可视范围,这也是通过改变View位置而实现滑动效果的原理。
2、layout过程都干了点什么事?
由于View是以树结构进行存储,所以典型的数据操作就是递归操作,所以,View框架中,采用了内部自治的layout过程。
每个叶子节点根据父节点传递过来的位置信息,设置自己的位置数据,每个非叶子节点,除了负责根据父节点传递过来的位置信息,设置自己的位置数据外(如果有父节点的话),还需要根据自己内部的layout规则(比如垂直排布等),计算出每一个子节点的位置信息,然后向子节点传递layout过程。
对于ViewGroup,除了根据自己的parent传递的位置信息,来设置自己的位置之外,还需要根据自己的layout规则,为每一个子View计算出准确的位置(相对于子View的父布局的位置)。
对于View,根据自己的parent传递的位置信息,来设置自己的位置。

View对象的位置信息,在内部是以4个成员变量的保存的,分别是mLeft、mRight、mTop、mBottom。他们的含义如图所示。

源代码分析
在View的源代码中,提取到了下面一些关于layout过程的信息。
我们知道,整棵View树的根节点是DecorView,它是一个FrameLayout,所以它是一个ViewGroup,所以整棵View树的测量是从一个ViewGroup对象的layout方法开始的。
View:
1、layout
/**
分配一个位置信息到一个View上面,每个parent会调用children的layout方法来设置children的位置。最好不要覆写该方法,有children的viewGroup,应该覆写onLayout方法
*/
public void layout(int l, int t, int r, int b) ;
源代码:
这里不给出,如果有兴趣,自行查阅SDK。
伪代码:
public void layout(int l, int t, int r, int b) {
if (根据一些flag,发现需要进一步measure) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
}
//暂存旧的位置信息
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//设置新的位置信息
mLeft = l;
mTop = t;
mBottom = b;
mRight = r;
if (layout改变了 || 需要layout) {
onLayout(changed, l, t, r, b);
//回调layoutChange事件
for (遍历监听对象) {
listener.onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
标记为已经执行过layout;
}
2、onLayout
/** 根据布局规则,计算每一个子View的位置,View类默认是空实现。 所以这里没有源代码*/
protected void
onLayout(boolean changed, int left, int top, int right, int bottom);
ViewGroup:
ViewGroup中,只需要覆写onLayout方法,来计算出每一个子View的位置,并且把layout流程传递给子View。
源代码:
ViewGroup没有实现,具体可以参考LinearLayout和RelativeLayout的onLayout方法。虽然各个具体实现都很复杂,但是基本流程是一样的,可以参考下面的伪代码。
伪代码:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (遍历子View) {
/**
根据如下数据计算。
1、自己当前布局规则。比如垂直排放或者水平排放。
2、子View的测量尺寸。
3、子View在所有子View中的位置。比如位置索引,第一个或者第二个等。
*/
计算每一个子View的位置信息;
child.layout(上面计算出来的位置信息);
}
}
结论
一般来说,自定义View,如果该View不包含子View,类似于TextView这种的,是不需要覆写onLayout方法的。而含有子View的,比如LinearLayout这种,就需要根据自己的布局规则,来计算每一个子View的位置。
动手操作
下面我们自己写一个自定义的ViewGroup,让它内部的每一个子View都垂直排布,并且让每一个子View的左边界都距离上一个子View的左边界一定的距离,大概看起来如下图所示:

实际运行效果如下:

代码如下:
public class VerticalOffsetLayout extends ViewGroup {
private static final int OFFSET = 100;
public VerticalOffsetLayout(Context context) {
super(context);
}
public VerticalOffsetLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public VerticalOffsetLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width = 0;
int height = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
child.measure(childWidthSpec, childHeightSpec);
}
switch (widthMode) {
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
int widthAddOffset = i * OFFSET + child.getMeasuredWidth();
width = Math.max(width, widthAddOffset);
}
break;
default:
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
height = height + child.getMeasuredHeight();
}
break;
default:
break;
}
setMeasuredDimension(width, height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = 0;
int right = 0;
int top = 0;
int bottom = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
left = i * OFFSET;
right = left + child.getMeasuredWidth();
bottom = top + child.getMeasuredHeight();
child.layout(left, top, right, bottom);
top += child.getMeasuredHeight();
}
}
}
Android View框架的layout机制的更多相关文章
- Android View框架的measure机制
概述 Android中View框架的工作机制中,主要有三个过程: 1.View树的測量(measure)Android View框架的measure机制 2.View树的布局(layout) Andr ...
- Android View框架的draw机制
概述 Android中View框架的工作机制中,主要有三个过程: 1.View树的测量(measure) Android View框架的measure机制 2.View树的布局(layout)Andr ...
- Android View框架总结(八)ViewGroup事件分发机制
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52298780 上篇分析了View的事件分发流程,留了一个问题:如果上 ...
- Android View的事件分发机制探索
概述 Android事件传递机制也是Android系统中比较重要的一块,事件类型有很多种,这里主要讨论TouchEvent的事件在framework层的传递处理机制.因为对于App开发人员来说,理解f ...
- Android View框架总结(四)View布局流程之Measure
View树的measure流程 View的measures时序图 View布局流程之measure measure过程 View的measure过程 ViewGroup的measure过程 Frame ...
- Android View框架总结(七)View事件分发机制
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52282833 View布局告一段落,从本篇开始View事件相关分析, ...
- Android View框架总结(五)View布局流程之Layout
转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52216195 View树的Layout流程 View的Layout时序图 View布局 ...
- Android view 的事件分发机制
1 事件的传递顺序是 Activity -> Window -> 顶层View touch 事件产生后,最先由 activity 的 dispatchTouchEvent 处理 /** * ...
- Android View框架总结(二)View焦点
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52263256 前言:View框架写到第六篇,发现前面第二篇竟然没有, ...
随机推荐
- 13-Servlet&Request
今日内容: 1. Servlet 2. Request Servlet 1. 概念 2. 步骤 3. 执行原理 4. 生命周期 5. Servlet3.0注解配置 6. Servlet的体系结构 se ...
- comm diff 文件对比
comm: 利用comm命令进行处理的文件必须首先通过sort命令进行排序处理并且是unix格式而非dos格式的文本文件 功能说明:比较两个已排过序的文件.(使用sort排序)语 法:comm [-1 ...
- U盘模式无法引导进入pe系统
有些笔记本.一体机 特别是win8.win10系统维护时需要 通过u盘进入pe系统,就是进不去,需要到bios中更改一下设置. 1.首先我们将已经使用u启动u盘启动盘制作 ...
- nginX 安装 启动
选择源码安装 下载安装包 nginx-1.8.0.tar.gz 解压 tar -zxvf nginx-1.8.0.tar.gz 进入文件夹 ...
- Mysql 导入导出备份
恢复MySQL服务器上面的txt格式文件(需要FILE权限,各数据值之间用"制表符"分隔) 1.导入数据库服务器上的txt文件 mysql>load data infil ...
- 51nod 1002 数塔取值问题 dp
动态规划 1002 数塔取数问题 1.0 秒 131,072.0 KB 5 分 1级题 一个高度为N的由正整数组成的三角形,从上走到下,求经过的数字和的最大值. 每次只能走到下一层相邻的数上,例如 ...
- PYTHON 学习笔记4 模块的使用、基本IO 写入读取、JSON序列化
前言 若在之前写代码的方式中,从Python 解释器进入.退出后再次进入,其定义的变量.函数等都会丢失.为了解决这个为,我们需要将需要的函数.以及定义的变量等都写入一个文件当中.这个文件就叫做脚本 随 ...
- table-cell设置宽高、居中
table-cell默认宽高由内容决定 <style type="text/css" rel="stylesheet"> .content { co ...
- 解决打开的小窗口回调参数的问题,layui
问题: 给用户新增优惠券,单个用户,单个优惠券,新增的话,用输入窗点击事件,点击后弹出一个子窗口列表页,选择数据后,点击确定,信息传回父窗口,主要是解决传值的问题. 解决思路: ...
- JS从键盘输入当月利润,求应发放奖金总数
题目:企业发放的奖金根据利润提成.利润(I)低于或等于10万元时,奖金可提10%:利润高 于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可可提 成7.5%:20万到4 ...