当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw。整个 View 树的绘图流程在ViewRoot.java
类的performTraversals()
函数展开,该函数所做 的工作可简单概况为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw),流程图如下:
在此之前
详见https://blog.csdn.net/jacklam200/article/details/50039189
可以说是: (测量)大小 -->(安排)位置 --> (绘制)内容
用来指定视图的宽度和高度等参数,对于height和width有以下选择:
测量规格,包含测量要求和尺寸信息,有三个模式:
UNSPECIFIED
父视图不对子视图有任何约束,它可以达到所期望的任意尺寸。比如 ListView、ScrollView,一般自定义 View 中用不到,
EXACTLY
父视图为子视图指定一个确切的尺寸,而且无论子视图期望多大,它都必须在该指定大小的边界内,对应的属性为 match_parent 或具体值,比如 100dp,父控件可以通过MeasureSpec.getSize(measureSpec)直接得到子控件的尺寸。
AT_MOST
父视图为子视图指定一个最大尺寸。子视图必须确保它自己所有子视图可以适应在该尺寸范围内,对应的属性为 wrap_content,这种模式下,父控件无法确定子 View 的尺寸,只能由子控件自己根据需求去计算自己的尺寸,这种模式就是我们自定义视图需要实现测量逻辑的情况。
一个View的真正测量工作在onMeasure(int,int)中,由measure()方法调用。
而measure()方法为final所以只有onMeasure(int,int)可以而且必须被子类复写。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
首先看一下onMeasure()方法(View.java):
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
往里看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);
}
我们看到measuredWidth
、measuredHeight
显然它是用来设置View的宽高的。
我们再来看看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;
}
specMode是View的测量模式,而specSize是View的测量大小,我们有必要查看一下MeasureSpec类:
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;
...省略
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
...省略
}
MeasureSpec类帮助我们来测量View
我们回头再看getDefaultSize()方法,显然在AT_MOST和EXACTLY模式下,都返回specSize这个值,也就是View测量后的大小,而在UNSPECIFIED模式(未指定模式)返回的是getDefaultSize()方法的第一个参数的值,这第一个参数从onMeasure()方法来看是getSuggestedMinimumWidth()方法和getSuggestedMinimumHeight()得到的,看一下这两个方法:(类似的只放Width)
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果View没有设置背景(MBackground==null)则取值为mMinWidth,mMinWidth是可以设置的,它对应于android:minWidth这个属性设置的值或者View的setMinimumWidth的值,若不指定则默认0:
public void setMinimumWidth(int minWidth) {
mMinWidth = minWidth;
requestLayout();
}
如果View设置了背景 在取值为max(mMinWidth,mBackground.getMinimumWidth()),取值mMinWidth和mBackground.getMinimumWidth()两者中的最大值,mMinWidth上面已经看过,我们接下来看看mBackground.getMinimumWidth(),这个mBackground是Drawable类型的,看一下Drawable类的getMInimumWidth()方法(Drawable.java):
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
intrinsicWidth得到的是这个Drawable的固有宽度,如果其大于0则返回固有宽度否则返回0。
我们可以看到,getSuggestedMinimumWidth()方法:如果View没有设置背景则返回mMinWidth,如果设置了背景就返回mMinWidth和Drawable最小宽度两个值的最大值。
我们知道对于ViewGroup,它除了需要measure自己本身,还要遍历调用子视图的measure()方法
ViewGroup中没有定义onMeasure()方法,然而它定义了measureChildren()方法(ViewGroup.java):
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);
}
}
}
显然它是一个遍历children的方法,看一下调用的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);
}
调用child.getLayoutParams()方法来获得子元素的LayoutParams属性,并获取到子元素的MeasureSpec并调用其measure()方法进行测量。
看一下getChildMeasureSpec()方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
...省略
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
这是根据父容器的MeasureSpec的模式,结合子元素的LayoutParams属性得出自容器的MeasureSpec属性。
ViewGroup没有提供onMeasure方法,而是让其子类来个字实现测量的方法,我们看一下ViewGroup的子类LinearLayout的measure流程,看一下onMeasure()方法(LinearLayout.java):
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
复写了View的onMeasure()方法。
看一下measureVertical()方法部分源码:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
mTotalLength = 0;
mTotalLength = 0;
...省略
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
// Optimization: don't bother measuring children who are only
// laid out using excess space. These views will get measured
// later if we have space to distribute.
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
if (useExcessSpace) {
// The heightMode is either UNSPECIFIED or AT_MOST, and
// this child is only laid out using excess space. Measure
// using WRAP_CONTENT so that we can find out the view's
// optimal height. We'll restore the original height of 0
// after measurement.
lp.height = LayoutParams.WRAP_CONTENT;
}
// Determine how big this child would like to be. If this or
// previous children have given a weight, then we allow it to
// use all available space (and we will shrink things later
// if needed).
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
if (useExcessSpace) {
// Restore the original height and record how much space
// we've allocated to excess-only children so that we can
// match the behavior of EXACTLY measurement.
lp.height = 0;
consumedExcessSpace += childHeight;
}
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child));
...省略
if (useLargestChild &&
(heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
child.getLayoutParams();
// Account for negative margins
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
}
}
// Add in our padding
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Check against our minimum height
定义了mTotalLength用来储存LinearLayout在垂直方向的高度,然后遍历子元素,根据子元素的MeasureSpec模式分别计算每个子元素的高度,如果是wrap_content则将每个子元素的高度和margin垂直高度等值相加 并且赋值给mTotalLength得出LinearLayout的高度。如果布局高度设置为match_parent,具体数值则和view的测量方法一样。
先来看一下View的layout()方法:
public void layout(int l, int t, int r, int b) {
...省略
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
...省略
}
传进来的分别是View的四个点的坐标,注意其是对于父布局来说的。
看看setFrame()方法:
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
//在setFrame()方法里主要是用来设置View的四个顶点的值,
//即mLeft、mTop、mRight和mBottom
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...省略
}
return changed;
}
在setFrame()方法里主要是用来设置View的四个顶点的值,即mLeft、mTop、mRight和mBottom。调用setFrame()方法后调用onLayout()方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
和onMeasure()方法类似,确定位置时候,不同控件有不同的实现,所以需要子类重写这个方法。我们看一下LinearLayout的onLayout()方法:
@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) {
...省略
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
...
setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
该方法会遍历子元素,调用setChildFrame()方法:
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
该方法中调用子元素的layout()方法来确定自己的位置。我们看到childTop这个值逐渐增大,这是为了在垂直方向,子元素一个接一个安排而不重叠。(setChildFrame()传入的第三个参数为childTop + getLocationOffset(child));
View.draw(Canvas canvas):
由于 ViewGroup 并没有复写此方法,因此,所有的视图最终都是调用 View 的 draw 方法进行绘制的。在自定义的视图中,也不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制,如果自定义的视图确实要复写该方法,那么请先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制。
View.onDraw():
View 的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。
dispatchDraw()
发起对子视图的绘制。View 中默认是空实现,ViewGroup 复写了dispatchDraw()来对其子视图进行绘制。该方法我们不用去管,自定义的 ViewGroup 不应该对dispatchDraw()进行复写。
我们先来看一下ViewGroup的dispatchDraw的源码
protected void dispatchDraw(Canvas canvas){
...省略
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {//处理 ChildView 的动画
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {//只绘制 Visible 状态的布局,因此可以通过延时加载来提高效率
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);// 添加布局变化的动画
bindLayoutAnimation(child);//为 Child 绑定动画
if (cache) {
child.setDrawingCacheEnabled(true);
if (buildCache) {
child.buildDrawingCache(true);
}
}
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();// 启动 View 的动画
}
// 绘制 ChildView
for (int i = 0; i < childrenCount; i++) {
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);//在drawChild中调用view.draw
}
}
...
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
}
drawChild(canvas, this, drawingTime)
直接调用了 View 的child.draw(canvas, this,drawingTime)方法,文档中也说明了,除了被ViewGroup.drawChild()方法外,你不应该在其它任何地方去复写或调用该方法,它属于 ViewGroup。而View.draw(Canvas)方法是我们自定义控件中可以复写的方法,具体可以参考上述对view.draw(Canvas)的说明。从参数中可以看到,child.draw(canvas, this, drawingTime) 肯定是处理了和父视图相关的逻辑,但 View 的最终绘制,还是 View.draw(Canvas)方法。
invalidate()
请求重绘 View 树,即 draw 过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些调用了invalidate()方法的 View。
dispatchDraw()–>drawChild()–>view.draw()
注意:view.draw()中调用的onDraw(Canvas)默认是空实现,自定义绘制过程需要复写的方法,绘制自身的内容。
我们再来看一下draw():
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);
}
...省略
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
...省略
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas); //onDraw空实现
// Step 4, draw the children
dispatchDraw(canvas);
...省略
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
...省略
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
}
从源码的注释我们看到draw流程有六个步骤,其中第2步和第5步可以跳过:
1.若有背景则绘制
2.保存canvas层
3.绘制自身内容
4.如果有子元素则绘制子元素
5.绘制效果
6.绘制装饰品(scrollbars)
注意:由上面的处理过程,我们也可以得出一些优化的小技巧:当不需要绘制 Layer 的时候第二步和第五步会跳过。因此在绘制的时候,能省的 layer 尽可省,可以提高绘制效率
引用:
https://blog.csdn.net/itachi85/article/details/50708391
http://developer.android.com/guide/topics/ui/how-android-draws.html
http://blog.csdn.net/wangjinyu501/article/details/9008271
http://blog.csdn.net/qinjuning/article/details/7110211
http://blog.csdn.net/qinjuning/article/details/8074262