前几篇事件分发中,我们已经了解了在Android中你所看到的界面的的分层情况Activity->DecorView->ViewGroup->View。本篇我们进入视图绘制的世界。
太详细的此处不多说了(主要是不能暴露自己水平太低…),简之,系统是通过ViewRoot(这里主要是ViewRootImpl)来实现DecorView的视图绘制的。
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
...
}
performMeasure 内部执行的也是 decorView.measure(childWidthMeasureSpec, childHeightMeasureSpec),而decorView是ViewGroup,又会循环是线子View.measure。在这里,我们脑中先有一个印象,View的绘制是measure->layout->draw。
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);
}
}
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* Creates a measure specification based on the supplied size and mode.
*
* The mode must always be one of the following:
*
* - {@link android.view.View.MeasureSpec#UNSPECIFIED}
* - {@link android.view.View.MeasureSpec#EXACTLY}
* - {@link android.view.View.MeasureSpec#AT_MOST}
*
*
* Note: On API level 17 and lower, makeMeasureSpec's
* implementation was such that the order of arguments did not matter
* and overflow in either value could impact the resulting MeasureSpec.
* {@link android.widget.RelativeLayout} was affected by this bug.
* Apps targeting API levels greater than 17 will get the fixed, more strict
* behavior.
*
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
1、MeasureSpec 本身是表示父视图对子视图的描述、规定。由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格(模式)。是一个32位的int,前两位用于表示specMode,后30位用于表示specSize。make和get的过程都是通过位运算生成。
2、specMode的三种类型:
先看经典的view绘制过程。
首先进入View的measure方法,你会发现人家是final的,所以不用想什么了,但是里面调用了onMeasure(widthMeasureSpec, heightMeasureSpec),其中widthMeasureSpec、heightMeasureSpec是父视图对子试图(当前View)的测绘结果,规格要求。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
getSuggestedMinimumWidth是View或background的最小宽度。然后通过getDefaultSize得到父类测量好的大小。
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;
}
之后通过setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。
在ViewGroup中,对应的是measureChildren方法
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
之后遍历了measureChild方法。
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
其中
**
* 该方法是 measureChildren 中最繁重的部分,为每一个 ChildView 计算出自己的 MeasureSpec。
* 目标是将 ChildView 的 MeasureSpec 和 LayoutParams 结合起来去得到一个最合适的结果。
*
* @param spec 对该 View 的测绘要求
* @param padding 当前 View 在当前唯独上的 paddingand,也有可能含有 margins
*
* @param childDimension 在当前维度上(height 或 width)的具体指
* @return 子视图的 MeasureSpec
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
.........
// 根据获取到的子视图的测量要求和大小创建子视图的 MeasureSpec
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
View中layout的作用是ViewGroup用来确定子视图(子View)的位置,当ViewGroup的位置被确定后,它在onLayout中遍历所有子元素,执行各自的layout的方法;如果子元素是ViewGroup,将在固定好自己的位置后继续调用onlayout方法,一直循环下去。
如下View的Layout方法(简):
public void layout(int l, int t, int r, int b) {
***
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//1.setFrame()设置此View在父视图中的坐标位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//2.如果是ViewGroup,执行onLayout
onLayout(changed, l, t, r, b);
***
}
***
}
其实当我们去查看ViewGroup中的onLayout时,发现也是一个抽象方法,那我们可以参考LinearLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
来观察下其中的layoutVertical
void layoutVertical(int left, int top, int right, int bottom) {
...
final int count = getVirtualChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();//measure 过程确定的 Width
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
...
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
//确定 childLeft、childTop 的值
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
可以看到,layoutVertical就是遍历所有(非Gone)的view,并调用setChildFrame来确定子元素的位置
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
最终执行子view的layout的方法,子view在layout方法中执行setFrame确定自己的位置,同时如果子View时一个ViewGroup则继续执行onLayout方法。
此处有个点,我们常说getMeasuredWidth“通常”等于getWidth,为什么时“通常”呢,有特殊情况吗?
public final int getWidth() {
return mRight - mLeft;
}
带getMeasuredWidth是指测量宽高,getWidth指最终宽高。如最终的宽是通过mRight - mLeft得到的,如上方layoutVertical中,setChildFrame(child, childLeft+100, childTop + getLocationOffset(child),
childWidth, childHeight),我们将其中的参数改变下,岂不是就有可能不等于了。
View的draw总共6步,但是我们一般只需要知道四步必须的。
1. 绘制背景drawBackground(canvas)
2. 绘制自己onDraw(canvas)
3. 绘制children,dispatchDraw(canvas)
4. 绘制装饰(foreground, scrollbars)
public void draw(Canvas canvas) {
...
/*
* 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);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
...
// Step 2, save the canvas' layers
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
另外