概述
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传递的位置信息,来设置自己的位置。
![](https://i-blog.csdnimg.cn/blog_migrate/b2f29ab526dd41e53d0a0c5d0167833e.png)
View对象的位置信息,在内部是以4个成员变量的保存的,分别是mLeft、mRight、mTop、mBottom。他们的含义如图所示。
![](https://i-blog.csdnimg.cn/blog_migrate/e13a3498773340df6b5229a8f0ed7eed.png)
源代码分析
在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;
</span><span style="color: #0000ff;">if</span> (layout改变了 ||<span style="color: #000000;"> 需要layout) {
onLayout(changed, l, t, r, b);
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 {
</span><span style="color: #0000ff;">private</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">final</span> <span style="color: #0000ff;">int</span> OFFSET = 100<span style="color: #000000;">;
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> VerticalOffsetLayout(Context context) {
</span><span style="color: #0000ff;">super</span><span style="color: #000000;">(context);
}
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> VerticalOffsetLayout(Context context, AttributeSet attrs) {
</span><span style="color: #0000ff;">super</span><span style="color: #000000;">(context, attrs);
}
</span><span style="color: #0000ff;">public</span> VerticalOffsetLayout(Context context, AttributeSet attrs, <span style="color: #0000ff;">int</span><span style="color: #000000;"> defStyleAttr) {
</span><span style="color: #0000ff;">super</span><span style="color: #000000;">(context, attrs, defStyleAttr);
}
@Override
</span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">void</span> onMeasure(<span style="color: #0000ff;">int</span> widthMeasureSpec, <span style="color: #0000ff;">int</span><span style="color: #000000;"> heightMeasureSpec) {
</span><span style="color: #0000ff;">super</span><span style="color: #000000;">.onMeasure(widthMeasureSpec, heightMeasureSpec);
</span><span style="color: #0000ff;">int</span> widthMode =<span style="color: #000000;"> MeasureSpec.getMode(widthMeasureSpec);
</span><span style="color: #0000ff;">int</span> heightMode =<span style="color: #000000;"> MeasureSpec.getMode(heightMeasureSpec);
</span><span style="color: #0000ff;">int</span> widthSize =<span style="color: #000000;"> MeasureSpec.getSize(widthMeasureSpec);
</span><span style="color: #0000ff;">int</span> heightSize =<span style="color: #000000;"> MeasureSpec.getSize(heightMeasureSpec);
</span><span style="color: #0000ff;">int</span> width = 0<span style="color: #000000;">;
</span><span style="color: #0000ff;">int</span> height = 0<span style="color: #000000;">;
</span><span style="color: #0000ff;">int</span> childCount =<span style="color: #000000;"> getChildCount();
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < childCount; i++<span style="color: #000000;">) {
View child </span>=<span style="color: #000000;"> getChildAt(i);
ViewGroup.LayoutParams lp </span>=<span style="color: #000000;"> child.getLayoutParams();
</span><span style="color: #0000ff;">int</span> childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0<span style="color: #000000;">, lp.width);
</span><span style="color: #0000ff;">int</span> childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0<span style="color: #000000;">, lp.height);
child.measure(childWidthSpec, childHeightSpec);
}
</span><span style="color: #0000ff;">switch</span><span style="color: #000000;"> (widthMode) {
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> MeasureSpec.EXACTLY:
width </span>=<span style="color: #000000;"> widthSize;
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> MeasureSpec.AT_MOST:
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> MeasureSpec.UNSPECIFIED:
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < childCount; i++<span style="color: #000000;">) {
View child </span>=<span style="color: #000000;"> getChildAt(i);
</span><span style="color: #0000ff;">int</span> widthAddOffset = i * OFFSET +<span style="color: #000000;"> child.getMeasuredWidth();
width </span>=<span style="color: #000000;"> Math.max(width, widthAddOffset);
}
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">default</span><span style="color: #000000;">:
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
}
</span><span style="color: #0000ff;">switch</span><span style="color: #000000;"> (heightMode) {
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> MeasureSpec.EXACTLY:
height </span>=<span style="color: #000000;"> heightSize;
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> MeasureSpec.AT_MOST:
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> MeasureSpec.UNSPECIFIED:
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < childCount; i++<span style="color: #000000;">) {
View child </span>=<span style="color: #000000;"> getChildAt(i);
height </span>= height +<span style="color: #000000;"> child.getMeasuredHeight();
}
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">default</span><span style="color: #000000;">:
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
}
setMeasuredDimension(width, height);
}
@Override
</span><span style="color: #0000ff;">protected</span> <span style="color: #0000ff;">void</span> onLayout(<span style="color: #0000ff;">boolean</span> changed, <span style="color: #0000ff;">int</span> l, <span style="color: #0000ff;">int</span> t, <span style="color: #0000ff;">int</span> r, <span style="color: #0000ff;">int</span><span style="color: #000000;"> b) {
</span><span style="color: #0000ff;">int</span> left = 0<span style="color: #000000;">;
</span><span style="color: #0000ff;">int</span> right = 0<span style="color: #000000;">;
</span><span style="color: #0000ff;">int</span> top = 0<span style="color: #000000;">;
</span><span style="color: #0000ff;">int</span> bottom = 0<span style="color: #000000;">;
</span><span style="color: #0000ff;">int</span> childCount =<span style="color: #000000;"> getChildCount();
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < childCount; i++<span style="color: #000000;">) {
View child </span>=<span style="color: #000000;"> getChildAt(i);
left </span>= i *<span style="color: #000000;"> OFFSET;
right </span>= left +<span style="color: #000000;"> child.getMeasuredWidth();
bottom </span>= top +<span style="color: #000000;"> child.getMeasuredHeight();
child.layout(left, top, right, bottom);
top </span>+=<span style="color: #000000;"> child.getMeasuredHeight();
}
}