您的位置:首页 > 产品设计 > UI/UE

(二)UI绘制流程-绘制过程源码分析

2017-05-19 17:16 483 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。

本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

从上一篇可以可以知道,UI的绘制流程的起点在 ViewRootImpl 下的performTraversals()方法。并且按顺序调用了 performMeasure(),performLayout()和 performDraw()这三个方法。

一、MeasureSpec 测量规格

位于 View 类的内部类,由于在安卓布局中有自适应尺寸,而不是全部都是固定的宽高,所以在这里引入 MeasureSpec 来进行测量。MeasureSpec 主要包含的信息是 测量模mode 和 测量大小size 。MeasureSpec 是一个 32 位的数据,高2位表示 mode, 低30位表示 size 。

测量模式分三种:

EXACTLY :父容器已经测量出所需要的精确大小,这也是 childview 的最终大小
------match_parent,精确值
ATMOST : child view 最终的大小不能超过父容器的给的
------wrap_content
UNSPECIFIED: 不确定,没有限制
-------一般在 ScrollView,ListView


点击查看 MeasureSpec 下的 makeMeasureSpec() 方法,返回基于 size 跟 mode 的一个测量规范。

public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}


安卓绘制的时候是从最外层开始绘制,然后再一层层往下绘制子控件。这时候父容器的布局就会影响到子容器的布局,MeasureSpec 在这里传递父容器布局对子容器布局的一些限制。

二、Measure()

1.在 performTraversals()下调用 performMeasure() 方法会传进去两个参数,这两个参数在调用的上面初始化。

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);


我们点击查看 getRootMeasureSpec() 方法。

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {

case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}


getRootMeasureSpec()是用于获取测量跟节点的测量规范,由上一篇我们知道这里的根节点就是我们的 DecorView,并且 DecorView 的布局为 MATCH_PARENT,所以走第一个分支。

2.点击查看我们的 performMeasure()方法。

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}


这时候我们的 mView 是 DecorView,DecorView 本身没有 measure()方法,最终会调到 View 里面的 measure()方法。首先会去判断该容器是否有光影的效果(边沿阴影),有的话会加上去光影的宽高。

boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth  = insets.left + insets.right;
int oHeight = insets.top  + insets.bottom;
widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}


在 measure()方法里面没有去进行具体的测量,具体的测量是在 onMeasure()这个方法里面进行。

这是 View 中的 onMeasure(),调用了一个 setMeasuredDimension()方法, setMeasuredDimension() 又直接调用 setMeasuredDimensionRaw(),在这里面进行宽高的直接赋值。getMeasuredWidth()获取的值就是在这里的赋值,所以 getMeasuredWidth()必须在 onMeasure()之后。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}


当 DecorView 调用 onMeasure()的时候会调用到 FrameLayout 中的 onMeasure()方法。

在 FrameLayout 中的 onMeasure()中,先通过一个 for 循环对所有的子容器进行遍历。其中有个判断 child.getVisibility() != GONE 的时候才进行测量,这也是为什么 Visibility 设置为 GONE 的时候不占位置的原因。

for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
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);
}
}
}
}


每个子布局会调用 measureChildWithMargins()这个方法,在 measureChildWithMargins()最后又调用 measure()这个方法,如果子容器是 ViewGroup 的话重复上述布局,如果是非 ViewGroup 的 View 容器最终调到前面讲的 View 的 onMeasure()方法,从而结束。

protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}


这是一个递归的过程,跟上一篇中加载布局是一样的。测量完成之后一样会调用 setMeasuredDimension() 方法进行测量大小的设置。



查看 getChildMeasureSpec()这个方法,getChildMeasureSpec()是根据当前父容器的测量规格(里面有测量模式和测量大小)以及子容器的一些属性来确定子容器的测量规格。子容器的测量规格受父容器的测量规格影响。

非 ViewGroup 的 View 的测量:onMeasure 方法里面调用 setMeasuredDimension()确定当前View的大小。

ViewGroup 的测量:1、通过 measureChildWithMargins()、measureChild() 或 measureChildren()三个方法来遍历测量 Child。2、setMeasuredDimension 确定当前ViewGroup的大小。

三、Layout()

performTraversals()下调用完 performMeasure() 后续调用 performLayout()方法。主要的内容在 Layout()方法里,在 Layout()方法里,确认了容器布局的左上右下四个边沿的值,然后调用容器的 onLayout()方法。

我们获取容器的宽度 getWidth() 方法是直接返回 右边沿 - 左边沿,所以 getWidth() 方法要在 Layout()方法之后使用。

可以看见在 View 中 onLayout()是一个空方法,什么都没做。在 ViewGroup 中 onLayout()是一个抽象方法,我们继续查看他的实现类 FrameLayout 下 onLayout()的实现。

FrameLayout 下 onLayout()直接调用 layoutChildren(),在这里面也是一个 for 循环对子容器的遍历,根据 Gravity 属性对子容器的上下左右边沿进行处理。这是 Gravity 属性生效的地方。最后调用各个子容器的 layout(),子容器是一个 ViewGroup 的话一样又进行了递归。

四、Draw()

performTraversals()下调用完 performLayout() 后续调用 performDraw()方法。一样的主要在 draw()方法里,在 draw()方法里真正开始绘制是在最后面调用的 drawSoftware()方法里。在这里有会调用的 DecorView 的 draw()方法,由于 DecorView 本身没有这个方法,从而就调用到了 View 的 draw()方法。

这是 View 的 draw()方法,很只是很清楚的写出了主要要做的事情。在这里,主要是对背景进行绘制。

public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
*      1. Draw the background
*      2. If necessary, save the canvas' layers to prepare for fading
*      3. Draw view's content
*      4. Draw children
*      5. If necessary, draw the fading edges and restore layers
*      6. Draw decorations (scrollbars for instance)
*/

// Step 1, draw the background, if needed
int saveCount;

if (!dirtyOpaque) {
drawBackground(canvas);
}

// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);

// Step 4, draw the children
dispatchDraw(canvas);

// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);

if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}

// we're done...
return;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息