通过上文 androidP: View的工作原理-理解MeasureSpec(https://blog.csdn.net/zhuowalun8427/article/details/123224642)已经获取到普通View与DecorView的MeasureSpec,后面根据MeasureSpec进行View的三个流程
**measure(测量):**确定View的测量宽/高;
**layout(布局):**确定VIew的最终宽/高和四个顶点的位置;
**draw(绘制):**将View绘制到屏幕上。
一、measure过程
measure的过程主要分两种情况,如果只是一个原始的View,那么通过measure方法就完成了其测量过程;如果是一个ViewGroup除了完成自己的测量过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个流程。
1、View的measure过程
根据上文源码可知,DecorView获取MeasureSpec后会调用performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)方法,该方法中调用View.measure()方法。
// android_build_9/frameworks/base/core/java/android/view/ViewRootImpl.java
View mView;
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
...
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
普通VIew获取MeasureSpec后,直接调用VIew.measure()方法。
// android_build_9/frameworks/base/core/java/android/view/ViewGroup.java
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);
}
下面直接看View的measure过程。
// android_build_9/frameworks/base/core/java/android/view/View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/**
* Utility to return a default size. Uses the supplied size if the
* MeasureSpec imposed no constraints. Will get larger if allowed
* by the MeasureSpec.
*
* @param size Default size for this view
* @param measureSpec Constraints imposed by the parent
* @return The size this view should be.
*/
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;
}
1)measure()方法是一个final类型的方法,这意味着子类不能重写此方法。
2)onMeasure()方法中直接调用setMeasuredDimension方法来设置View的宽/高的测量值,直接看getDefaultSize方法。
3)getDefaultSize()方法中对于AT_MOST和EXACTLY这两种情况,返回的大小为specSize,即为测量后的大小。所以对于直接继承View自定义的控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于match_parent。通过上文图标可以了解,当View在布局中使用wrap_content定义宽/高时,该View返回的MeasureSpec的mode均为AT_MOST类型,且size均为可用的父容器大小,即getDefaultSize方法中specSize为可用的父容器的大小,因此在布局中使用wrap_content就相当于match_parent。解决该问题需要重写onMeasure方法,给View一个默认的宽/高(mWidth和mHeight,无特殊要求根据实际情况定义),并在wrap_content时设置此宽/高,其他情况不变。系统TextView 、ImageView等控件针对wrap_content情形,在onMeasure方法中都有做特殊处理。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//默认的内部宽/高,没有固定的依据,根据需要灵活指定即可
int mWidth = 100;
int mheight = 100;
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth,mheight);
}else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth,heightSpecSize);
}else if (heightMeasureSpec == MeasureSpec.AT_MOST) {
(widthSpecSize,mheight);
}
}
View的测量流程:获取VIew(或者DecorView)的MeasureSpec->View.measure->onMeasure->重写自定义VIew的onMeasure方法。
4)getDefaultSize()方法中,对于UNSPECIFIED情况,View的宽/高为getSuggestedMinimumWidth()和getSuggestedMinimumHeight()的返回值,以getSuggestedMinimumWidth()为例,源码:
// android_build_9/frameworks/base/core/java/android/view/View.java
/**
* Returns the suggested minimum width that the view should use. This
* returns the maximum of the view's minimum width
* and the background's minimum width
* ({@link android.graphics.drawable.Drawable#getMinimumWidth()}).
* <p>
* When being used in {@link #onMeasure(int, int)}, the caller should still
* ensure the returned width is within the requirements of the parent.
*
* @return The suggested minimum width of the view.
*/
private Drawable mBackground;
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
// android_build_9/frameworks/base/graphics/java/android/graphics/drawable/Drawable.java
/**
* Returns the minimum width suggested by this Drawable. If a View uses this
* Drawable as a background, it is suggested that the View use at least this
* value for its width. (There will be some scenarios where this will not be
* possible.) This value should INCLUDE any padding.
*
* @return The minimum width suggested by this Drawable. If this Drawable
* doesn't have a suggested minimum width, 0 is returned.
*/
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
getSuggestedMinimumWidth()方法中,如果View没有设置背景,View的宽度为mMinWidth(对应于android:minWidth这个属性所制定的值,这个属性不指定,则mMinWidth默认为0);如果VIew设置了背景,则VIew的宽度为max(mMinWidth, mBackground.getMinimumWidth()。
Drawable.getMinimumWidth()返回的是Drawable的原始宽度,前提是这个Drawable有原始宽度,否则就返回0。那么Drawable在什么情况下有原始宽度呢,举个例子,ShapeDrawable无原始宽/高,而BitmapDrawable有原始宽/高(图片尺寸)。(这里不懂,需要学习《android开发艺术探索》第6章的内容)。
2、ViewGroup的measure过程
对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是它提供了一个叫measureChildren的方法:
// android_build_9/frameworks/base/core/java/android/view/ViewGroup.java
// android_build_9/frameworks/base/core/java/android/view/View.java
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
/**
* Ask all of the children of this view to measure themselves, taking into
* account both the MeasureSpec requirements for this view and its padding.
* We skip children that are in the GONE state The heavy lifting is done in
* getChildMeasureSpec.
*
* @param widthMeasureSpec The width requirements for this view
* @param heightMeasureSpec The height requirements for this view
*/
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);
}
}
}
/**
* Ask one of the children of this view to measure itself, taking into
* account both the MeasureSpec requirements for this view and its padding.
* The heavy lifting is done in getChildMeasureSpec.
*
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param parentHeightMeasureSpec The height requirements for this view
*/
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);
}
}
1)measureChildren()将所有的view取出来分别调用measureChild()方法,获取每一个view的MeasureSpec;
2)measureChild()的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec传递给View的measure方法来进行测量。
ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现,比如LinearLayout、RelativeLayout等。因为不同的ViewGrop子类有不同的布局特性,这导致他们的测量细节各部相同,比如LinearLayout、RelativeLayout这两者的布局特性显然不同,因此ViewGroup无法做统一实现。下面是LinearLayout的onMeasure方法的实现过程。
LinearLayout是Android开发中最常用的Layout之一,它支持水平或垂直线性布局,并且支持child设置权重weight,使child能够在主轴按一定比例填充LinearLayout。(线性布局的完整流程太难了,等三个流程都看完之后,再学这个。线性布局源码解析链接:https://blog.csdn.net/dehang0/article/details/104166111)
// android_build_9/frameworks/base/core/java/android/widget/LinearLayout.java
@RemoteView
public class LinearLayout extends ViewGroup {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
}
根据线性布局的布局方向(水平/垂直),调用measureVertical()或measureVertical()方法。以垂直方向为例:
// android_build_9/frameworks/base/core/java/android/widget/LinearLayout.java
/**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
* @param widthMeasureSpec Horizontal space requirements as imposed by the parent.
* @param heightMeasureSpec Vertical space requirements as imposed by the parent.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onMeasure(int, int)
*/
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// 内容总高度(所有child的测量高度总和+divider高度+边距)
mTotalLength = 0;
// 最大宽度(最大child宽度+边距)
int maxWidth = 0;
// child测量状态(可设置MEASURED_STATE_TOO_SMALL标识位,用于向父布局请求加大宽高)
int childState = 0;
// 备选最大宽度(记录非权重的child最大宽度)
int alternativeMaxWidth = 0;
// 权重最大宽度(记录含权重的child最大宽度)
int weightedMaxWidth = 0;
// 标记是否所有child的LayoutParams.width为MATCH_PARENT
boolean allFillParent = true;
// 计算child的权重之和
float totalWeight = 0;
// child数量(该方法内直接调用getChildCount,子类可重写该方法进行)
final int count = getVirtualChildCount();
// 取出父布局传入的测量规格模式。
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
// 标记是否在LinearLayout的宽度确定后,对LayoutParams.width为MATCH_PARENT的child进行再次测量。
boolean matchWidth = false;
// 标记是否有对某个child暂不测量。
boolean skippedMeasure = false;
// baselineAlignedChildIndex属性值,默认为-1。
final int baselineChildIndex = mBaselineAlignedChildIndex;
// measureWithLargestChild属性值,默认为false。
final boolean useLargestChild = mUseLargestChild;
// 最大child的高度,当useLargestChild为true时有用
int largestChildHeight = Integer.MIN_VALUE;
// 记录设置了LayoutParams.height为0像素且权重大于0的child占用的总高度
int consumedExcessSpace = 0;
// 记录在第一轮遍历child时,有效child个数。
int nonSkippedChildCount = 0;
}
特殊情况:比如我们在Activity已启动的时候获取某个View的宽/高,发现在onCreate onResume onStart 中均无法正确得到某个View的宽/高信息,这是因为View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate onResume onStart 时某个View已经测量完毕了,如果View还没有测量完毕,那么获得的宽/高就是0。下面四种方法来解决这个问题:
1)Activity/View #onWindowFocusChanged
onWindowFocusChanged这个方法的含义是:View已经初始化完毕了,宽/高已经准备好了,这个时候去获取宽/高是没有问题的。需要注意的是,onWindowFocusChanged会被调用多次,当Activity的窗口得到焦点(onResume)和失去焦点(onPause)时都会被调用一次。典型代码如下:
private TextView mTvFirst;
BtStart = findViewById(R.id.bt_start);
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
int width = mTvFirst.getMeasuredWidth();
int height = mTvFirst.getMeasuredHeight();
}
}
2)view.post(Runnable)
通过post可以将一个Runnable投递到消息队列的尾部,然后等待Looper调用此Runnable的时候,View也已经初始化好了,典型代码如下:
@Override
protected void onStart() {
super.onStart();
mTvFirst.post(new Runnable() {
@Override
public void run() {
int width = mTvFirst.getMeasuredWidth();
int height = mTvFirst.getMeasuredHeight();
}
});
}
3)ViewTreeObserver
使用ViewTreeObserver的众多回掉可以完成这个功能,比如使用onGlobalLayoutListener这个接口,当View树的状态发生改变或者View树内部的View的可见性发现改变时,onGlobalLayout方法将被回掉,因此这是获取view的宽/高一个很好的时机。需要注意的是,伴随View树的状态改变等,onGlobalLayout会被调用多次。典型代码如下:
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver observer = mTvFirst.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mTvFirst.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = mTvFirst.getMeasuredWidth();
int height = mTvFirst.getMeasuredHeight();
}
});
}
4)view.measure(int widhMeasureSpen, int heightMeasureSpec)
(没看懂,先这样吧)
通过手动对View进行measure来得到View的宽/高。根据LayoutParams分为四种情况:
a、match_parent
直接放弃,无法measure出具体的宽/高。根据View的measure过程,构造此MeasureSpec需要知道parentSize,即父容器的剩余空间,而这个时候我们无法知道parentSize的大小,所以理论上不可能测量出View的大小。
b、具体的数值(dp/px)
比如款/高都是100px,如下measure
二、layout过程
Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置确定后,它在onLayout中会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又被调用。
// android_build_9/frameworks/base/core/java/android/view/View.java
/**
* Assign a size and position to a view and all of its
* descendants
*
* <p>This is the second phase of the layout mechanism.
* (The first is measuring). In this phase, each parent calls
* layout on all of its children to position them.
* This is typically done using the child measurements
* that were stored in the measure pass().</p>
*
* <p>Derived classes should not override this method.
* Derived classes with children should override
* onLayout. In that method, they should
* call layout on each of their children.</p>
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
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);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
final boolean wasLayoutValid = isLayoutValid();
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if (!wasLayoutValid && isFocused()) {
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
if (canTakeFocus()) {
// We have a robust focus, so parents should no longer be wanting focus.
clearParentsWantFocus();
} else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
// This is a weird case. Most-likely the user, rather than ViewRootImpl, called
// layout. In this case, there's no guarantee that parent layouts will be evaluated
// and thus the safest action is to clear focus here.
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
clearParentsWantFocus();
} else if (!hasParentWantsFocus()) {
// original requestFocus was likely on this view directly, so just clear focus
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
}
// otherwise, we let parents handle re-assigning focus during their layout passes.
} else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
View focused = findFocus();
if (focused != null) {
// Try to restore focus as close as possible to our starting focus.
if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
// Give up and clear focus once we've reached the top-most parent which wants
// focus.
focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
}
}
}
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
layout方法的大致流程如下:首先通过setFrame方法来设定View的四个定点位置,即mLeft mTop mBottom mRight四个值,View的四个定点确定之后View在父容器中的位置就确定了;接着会调用onLayout方法,这个方法的用途是父容器确定子元素的位置,onLayout的具体实现与布局有关,需要重写onLayout方法。
例如线性布局onlayout方法(后面再学)。
补充:View的getMeasuredWidth与getWidth的区别(height一样)
源码:
// android_build_9/frameworks/base/core/java/android/view/View.java
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth() {
return mRight - mLeft;
}
getWidth方法的返回值刚好是View的测量宽度。在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于VIew的measure阶段,而最终宽/高形成于View的layout过程,即两者的赋值时机不同。但是有某些特殊情况两者的值是不同的。
总结:
1)getMeasuredWidth方法获得的值是setMeasuredDimension方法设置的值,它的值在measure方法运行后就会确定
2)getWidth方法获得是layout方法中传递的四个参数中的mRight-mLeft,它的值是在layout方法运行后确定的
3)一般情况下在onLayout方法中使用getMeasuredWidth方法,而在除onLayout方法之外的地方用getWidth方法。
三、draw过程
draw的作用就是将View绘制到屏幕上。View的绘制过程遵循如下几步:
1)绘制背景background.draw(canvas)
2 ) 绘制自己(onDraw)
3)绘制children(dispatchDraw)
4)绘制装饰(onSrawScrollBars)
源码:
// android_build_9/frameworks/base/core/java/android/view/View.java
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
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 (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
}
View绘制过程的传递时通过dispatchDraw来实现的,dispatchDraw会遍历元素的draw方法,如此draw事件就一层层地传递下去。View有一个特殊的方法setWillNotDraw,源码:
// android_build_9/frameworks/base/core/java/android/view/View.java
**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
如果一个View不需要绘制任何内容,那么设置这个标记为true以后,系统会进行相应的优化。默认情况下,View没有启用这个优化标记位,但是ViewGroup会默认启用这个优化标记位。这个标记位对实际开发的意义是:当我们自定义控件继承于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。当然,当明确知道一个ViewGroup需要通过onDraw来绘制内容时,我们需要显示地关闭WILL_NOT_DRAW这个标记位。