android ui 流程,Android UI绘制流程(二)

Android UI绘制流程。我们平时写布局应该对ViewGroup非常了解。ViewGroup相当于一个容器,里边可以放ViewGroup或者View对象。而ViewGroup和View的包含区别就是ViewGroup里边可以添加,View不可以在添加。

了解了ViewGroup和View的关系后,那么ViewGroup里的ViewGroup和View是经过怎样的过程最终显示到设定的位置的呢?其实ViewGroup就相当于一棵树,View相当于树的叶子。ViewGroup要经历 测量onMeasure(),摆放onLayout(),画 onDraw()三个主要方法最终显示出来。

首先来看onMeasure()测量的方法。

首先我们提出一个猜想,在整个的ViewGroup的测量步骤中,首先要测量并确定所有的子View的大小,再确定View树的根ViewGroup的大小,就像数据结构中的树的前序遍历。

根据前面的猜想,我们应该先测量所有的子View。在ViewGroup方法的中,我们看到了measureChildren()方法,所以可以根据这个方法作为线索,向下分析。

1 measureChildren() 测量所有子View

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {

final int size = mChildrenCount;

final View[] children = mChildren;

//1 遍历子View

for (int i = 0; i < size; ++i) {

final View child = children[i];

if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {

//2 测量子View

measureChild(child, widthMeasureSpec, heightMeasureSpec);

}

}

}

这个方法有两个参数,widthMeasureSpec和heightMeasureSpec。所以在看这个方法之前,先来看一下MeasureSpec。

MeasureSpeck用32位的int值来表示,高两位表示MeasureSpec的SpecMode属性,低30位表示SpecSize属性。

SpecMode有三种:

EXACTLY 精确值 当宽高设置具体的精确值 比如200dp。 AT_MOST 最大值 一般用于当设置宽高为martch_parent或者wrap_content时。 UNSPECIFIED 未指定的大小,一般用于ListView,ScrollView等不能确定大小的控件。

SpecSize就表示控件的大小的值。

SpecMode和SpecSize可以通过MeasureSpec的getSpecMode()和getSpecSize()方法来获取。

同时,也可以把SpecMode和SpecSize通过MeasureSpec的makeMeasureSpec()方法来合成一个MeasureSpec。

下面再来看上面这段代码,从这个方法中可以看出,首先要遍历所有的子View,再调用measureChild()方法测量子View。下面来看测量View自己的方法。

2 measureChild() 子View测量自己

protected void measureChild(View child, int parentWidthMeasureSpec,

int parentHeightMeasureSpec) {

//1 获取child自己的相关参数

final LayoutParams lp = child.getLayoutParams();

//2 获取childWidthMeasureSpec 和 childHeightMeasureSpec

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

mPaddingLeft + mPaddingRight, lp.width);

final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,

mPaddingTop + mPaddingBottom, lp.height);

//3 根据宽高属性,测量自己

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

1 这个方法中首先获取了child自己的参数Layoutparams。

2 然后根据所在ViewGroup的MeasureSpec和padding,本身的宽度来获取宽和高的MeasureSpec。

3 根据宽和高的MeasureSpec来测量自己。

下面来看ViewGroup中测量的核心:

2.1 getChildMeasureSpec() 获得子View的MeasureSpec

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

//1 获得父ViewGroup的specMode和specSize

int specMode = MeasureSpec.getMode(spec);

int specSize = MeasureSpec.getSize(spec);

int size = Math.max(0, specSize - padding);

int resultSize = 0;

int resultMode = 0;

//2判断父ViewGroup的specMode

switch (specMode) {

//2.1如果父ViewGroup是一个具体的值

case MeasureSpec.EXACTLY:

//2.1.1 child是一个具体值

if (childDimension >= 0) {

//child的size将会是child自己设定的值

resultSize = childDimension;

//因为child的size是精确值,所以child的specMode是EXACTLY

resultMode = MeasureSpec.EXACTLY;

//2.1.2 child想和父ViewGroup一样大

} else if (childDimension == LayoutParams.MATCH_PARENT) {

//因为此时父ViewGroup的size是确定的,child又想和父ViewGroup一样大,所以child的size和父ViewGroup的size相等

resultSize = size;

//因为child的size是精确值,所以child的specMode是EXACTLY

resultMode = MeasureSpec.EXACTLY;

//2.3.3 child想要自己本身的大小

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

//限制child不能比父ViewGroup大 让child的size和父ViewGroup的size相等

resultSize = size;

//child大小此时不能确定 所以specMode也是AT_MOST

resultMode = MeasureSpec.AT_MOST;

}

break;

//2.2如果父ViewGroup是一个最大值

case MeasureSpec.AT_MOST:

//2.2.1 child是一个具体值

if (childDimension >= 0) {

//child的size将会是child自己设定的值

resultSize = childDimension;

//因为child的size是精确值,所以child的specMode是EXACTLY

resultMode = MeasureSpec.EXACTLY;

//2.3.2 child想和父ViewGroup一样大

} else if (childDimension == LayoutParams.MATCH_PARENT) {

//child想和父ViewGroup一样大,但是父ViewGroup的size是不固定的,所以限制child不能比父ViewGroup大

resultSize = size;

// child的specMode也是AT_MOST

resultMode = MeasureSpec.AT_MOST;

//2.3.3 child想要自己本身的大小

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

// Child wants to determine its own size. It can't be

// bigger than us.

//同样限制child不能比父ViewGroup大

resultSize = size;

//同样child的specMode也是AT_MOST

resultMode = MeasureSpec.AT_MOST;

}

break;

//2.3如果父ViewGroup想知道子控件有多大

case MeasureSpec.UNSPECIFIED:

//2.3.1 child是一个具体值

if (childDimension >= 0) {

//child的size将会是child自己设定的值

resultSize = childDimension;

//因为child的size是精确值,所以child的specMode是EXACTLY

resultMode = MeasureSpec.EXACTLY;

//2.3.2 child想和父ViewGroup一样大

} else if (childDimension == LayoutParams.MATCH_PARENT) {

//child的size将会是0或者父ViewGroup的大小

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

//因为child的父ViewGroup是未指定的,child又想和父ViewGroup一样大,所以child的specMode也是UNSPECIFIED

resultMode = MeasureSpec.UNSPECIFIED;

//2.3.3 child想要自己本身的大小

} else if (childDimension == LayoutParams.WRAP_CONTENT) {

//child的size将会是0或者父ViewGroup的大小

resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;

// 同时child的specMode也是UNSPECIFIED

resultMode = MeasureSpec.UNSPECIFIED;

}

break;

}

//3 把child的SpecSize和SpecMode合成一个MeasureSpec并返回

return MeasureSpec.makeMeasureSpec(resultSize, resultMode);

}

这段代码可以算是ViewGroup中的重点代码了。我几乎把所有的代码都写了注释。这个方法主要是结合ViewGroup的MeasureSpec和child本身的大小来确定child的MeasureSpec。具体是如何影响child的MeasureSpec的,可以看我写的注释。写的非常清楚,这里不再赘述。

获得了child的MeasureSpec后,开始调用

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

而measure()方法中的测量工作主要是在onMeasure()中来完成了的。来看下面这段伪代码:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

onMeasure(widthMeasureSpec, heightMeasureSpec);

}

再来看onMeasure()方法:

3 onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

}

在onMeasure()方法中,

1 首先获得推荐的最小宽度或高度。

protected int getSuggestedMinimumWidth() {

return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());

}

protected int getSuggestedMinimumHeight() {

return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

}

2 根据推荐的高度和宽度,在结合child的MeasureSpec。来确定child的默认宽高。

public static int getDefaultSize(int size, int measureSpec) {

int result = size;

int specMode = MeasureSpec.getMode(measureSpec);

int specSize = MeasureSpec.getSize(measureSpec);

switch (specMode) {

case MeasureSpec.UNSPECIFIED:

result = size;

break;

case MeasureSpec.AT_MOST:

case MeasureSpec.EXACTLY:

result = specSize;

break;

}

return result;

}

3最后通过setMeasuredDimension()方法来把存储View的宽和高

4 setMeasuredDimension

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {

boolean optical = isLayoutModeOptical(this);

if (optical != isLayoutModeOptical(mParent)) {

Insets insets = getOpticalInsets();

int opticalWidth = insets.left + insets.right;

int opticalHeight = insets.top + insets.bottom;

measuredWidth += optical ? opticalWidth : -opticalWidth;

measuredHeight += optical ? opticalHeight : -opticalHeight;

}

setMeasuredDimensionRaw(measuredWidth, measuredHeight);

}

存储真正的操作是在setMeasuredDimensionRaw()方法中,在setMeasuredDimensionRaw()方法中直接把宽和高保存在成员变量中。

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {

mMeasuredWidth = measuredWidth;

mMeasuredHeight = measuredHeight;

mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;

}

至此,View的宽和高已经确定。测量过成完成。但是这只是ViewGroup其中的一个子View的过程。如果ViewGroup1的子View也是一个ViewGroup2。那么最先测量的应该是ViewGroup2的子View。这是个递归的过程,就像是树的遍历。先遍历最小最深的结点。

需要注意的是,如果ViewGroup在测量过程中,子View也是一个ViewGroup。那么是怎么实现递归的呢?如FramLayout是怎么再去测量呢?其实FrameLayout重写了onMeasure()方法,在此方法中,还是要先去遍历所有的子View,然后再测量。如下:

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int count = getChildCount();

final boolean measureMatchParentChildren =

MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||

MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;

mMatchParentChildren.clear();

int maxHeight = 0;

int maxWidth = 0;

int childState = 0;

//1 遍历每个child

for (int i = 0; i < count; i++) {

final View child = getChildAt(i);

if (mMeasureAllChildren || child.getVisibility() != GONE) {

//2 开始测量每个child

measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

final LayoutParams lp = (LayoutParams) child.getLayoutParams();

maxWidth = Math.max(maxWidth,

child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);

maxHeight = Math.max(maxHeight,

child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);

childState = combineMeasuredStates(childState, child.getMeasuredState());

if (measureMatchParentChildren) {

if (lp.width == LayoutParams.MATCH_PARENT ||

lp.height == LayoutParams.MATCH_PARENT) {

mMatchParentChildren.add(child);

}

}

}

}

这是FramLayout中的onMeasure()方法的一部分,可以看出先去遍历,然后再去测量每个childView。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值