@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
在ViewRootImpl里面的requestLayout方法,首先会检查当前的线程是否和创建ViewRootImpl的线程是一致的,如果不是一致的会报错。
接下来看scheduleTraversals方法:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
这边先发送了一个屏障消息,异步消息。然后等待屏幕刷新信号到来之时,移除屏障消息,执行performTraversals()开启三大流程。
private void performTraversals() {
...
//之前记录的Window LayoutParams
WindowManager.LayoutParams lp = mWindowAttributes;
//Window需要的大小
int desiredWindowWidth;
int desiredWindowHeight;
...
Rect frame = mWinFrame;
if (mFirst) {
...
if (shouldUseDisplaySize(lp)) {
...
} else {
//mWinFrame即是之前添加Window时返回的Window最大尺寸
desiredWindowWidth = mWinFrame.width();
desiredWindowHeight = mWinFrame.height();
}
...
} else {
...
}
...
if (layoutRequested) {
...
//ViewTree的预测量 -----------(1)
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
...
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
...
try {
...
//对window进行布局(宽高) --------(2)
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
} catch (RemoteException e) {
}
...
if (!mStopped || mReportNextDraw) {
...
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
...
//再次测量ViewTree -------- (3)
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
}
} else {
...
}
...
if (didLayout) {
//对ViewTree 进行Layout ---------- (4)
performLayout(lp, mWidth, mHeight);
...
}
...
if (!cancelDraw) {
...
//开始ViewTree Draw过程 ------- (5)
performDraw();
} else {
...
}
}
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;// 合成后的用于描述宽度的MeasureSpec
int childHeightMeasureSpec;// 合成后的用于描述高度的MeasureSpec
boolean windowSizeMayChange = false;// 表示测量结果是否可能导致窗口的尺寸发生变化
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false;// 表示测量能否满足控件树充分显示内容的要求
// 测量协商仅发生在LayoutParams.width被指定为WRAP_CONTENT的情况下
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
/* 第一次协商。measureHierarchy()使用它最期望的宽度限制进行测量。这一宽度限制定义为一个系统资源。
可以在frameworks/base/core/res/res/values/config.xml找到它的定义,320dp */
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
// 宽度限制被存放在baseSize中
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
// 使用getRootMeasureSpec()函数组合SPEC_MODE与SPEC_SIZE为一个MeasureSpec
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// 第一次测量。由performMeasure()方法完成
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
/* 控件树的测量结果可以通过mView的getmeasuredWidthAndState()方法获取。
如果控件树对这个测量结果不满意,则会在返回值中添加MEASURED_STATE_TOO_SMALL位 */
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;// 控件树对测量结果满意,测量完成
} else {
// Didn't fit in that size... try expanding a bit.
/* 第二次协商。上次测量结果表明控件树认为measureHierarchy()给予的宽度太小,
在此适当地放宽对宽度的限制,使用最大宽度与期望宽度的中间值作为宽度限制 */
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
// 第二次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
// 再次检查控件树是否满足此次测量
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;//控件树对测量结果满意,测量完成
}
}
}
}
if (!goodMeasure) {
/* 当控件树对上述两次协商的结果都不满意时,measureHierarchy()放弃所有限制做最终测量。
这一次将不再检查控件树是否满意了,因为即便其不满意,measurehierarchy()也没有更多的空间供其使用了 */
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
/* 最后,如果测量结果与ViewRootImpl中当前的窗口尺寸不一致,则表明随后可能有必要进行窗口尺寸的调整 */
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}
// 返回窗口尺寸是否可能需要发生变化
return windowSizeMayChange;
}
在开始view树的真正绘制流程之前,会对整个viewTree进行预测量。首先判定DecorView的LayoutParams进行一次判断,如果是WRAP_CONTENT的话,会先给一个宽度给到DecorView,然后去测量整个viewTree,看看这个宽度够不够。如果不够的话,再把宽度扩大一点,再给DecorView,通过调用performMeasure方法去测量整个viewTree树。如果宽度还是不够的话,把屏幕的全部宽度都给Decorview,再去测量整个viewTree树。这时候宽度够不够都不管了,已经把全部的宽度都交出去了。
1.判断DecorView的LayoutParams是否为WRAP_CONTENT
2.满足条件的话,先给一个宽度给到DecorView,然后去测量整个viewTree,看看这个宽度够不够。不满足条件的话直接把最大值给DecorView。
3.宽度不够再把宽度扩大一点,再给DecorView,通过调用performMeasure方法去测量整个viewTree树。
4.如果宽度还是不够的话,把屏幕的全部宽度都给Decorview,再去测量整个viewTree树。
所以在预测量measureHierarchy方法里面最少都要测量一次,最多是三次。
在预测量方法里面有一个getRootMeasureSpec方法,这个方法是确定DecorView的宽高和具体测量模式的。
private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) {
int measureSpec;
final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0
? MATCH_PARENT : measurement;
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;
}
可以看到如果是MATCH_PARENT,直接就是窗口的宽度;如果是WRAP_CONTENT,DecorView的模式是MeasureSpec.AT_MOST;如果是其他情况,说明DecorView的宽度是一个固定值,直接给固定值就行了。
我们测量ViewTree的函数是performMeasure。我们看到在performTraversals中执行完预测量后会执行performMeasure方法:
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);
}
}
这里的mView显然就是我们保存在ViewRootImpl里面的DecorView,也就是说ViewRootImpl里面的performMeasure执行了DecorView的measure方法。
DecorView的父类是FrameLayout,FrameLayout里面没有重写measure方法,ViewGroup也没有重写measure方法,那measure方法一定是View里面的了:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
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);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
/* 仅当给予的MeasureSpec发生变化,或要求强制重新布局时,才会进行测量。
所谓强制重新布局,是指当控件树中的一个子控件的内容发生变化时,需要进行重新的测量和布局的情况。
在这种情况下,这个子控件的父控件(以及其父控件的父控件)所提供的MeasureSpec必定与上次测量
时的值相同,因而导致从ViewRootImpl到这个控件的路径上的父控件的measure()方法无法得到执行,
进而导致子控件无法重新测量其尺寸或布局。因此,当子控件因内容发生变化时,从子控件沿着控件树回溯
到ViewRootImpl,并依次调用沿途父控件的requestLayout()方法。这个方法会在mPrivateFlags中
加入标记PFLAG_FORCE_LAYOUT,从而使得这些父控件的measure()方法得以顺利执行,
进而这个子控件有机会进行重新测量与布局。这便是强制重新布局的意义 */
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
/* 准备工作。从mPrivateFlags中将PFLAG_MEASURED_DIMENSION_SET标记去除。
PFLAG_MEASURED_DIMENSION_SET标记用于检查控件在onMeasure()方法中是否通过
调用setMeasuredDimension()将测量结果存储下来 */
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
/* 对本控件进行测量,每个View子类都需要重载这个方法以便正确地对自身进行测量。
View类的onMeasure()方法仅仅根据背景Drawable或style中设置的最小尺寸作为测量结果*/
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
/* 检查onMeasure()的实现是否调用了setMeasuredDimension(),
setMeasuredDimension()会将PFLAG_MEASURED_DIMENSION_SET标记重新加入mPrivateFlags中。
之所以做这样的检查,是由于onMeasure()的实现可能由开发者完成,而在Android看来,开发者是不可信的 */
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
// 将PFLAG_LAYOUT_REQUIRED标记加入mPrivateFlags。这一操作会对随后的布局操作放行
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
// 记录父控件给予的MeasureSpec,用以检查之后的测量操作是否有必要进行
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
这个方法里面比较重要的是onMeasure(widthMeasureSpec, heightMeasureSpec)这个方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure实现对本控件进行测量,onMeasure方法在FrameLayout里面是有重写的:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//如果FrameLayout宽高不是精确值的,需要再测量一次match_parent的子视图
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
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());
//如果需要测量match_parent宽或高的子视图,将match_parent子视图添加到集合
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//完成测量
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
//读取需要再测量的子视图数量
count = mMatchParentChildren.size();
// count 需要大于1
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec;
//如果是match_parent,测量子视图时使用精确模式+可用宽度
//下面的高度也是差不多
if (lp.width == LayoutParams.MATCH_PARENT) {
final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
}
final int childHeightMeasureSpec;
if (lp.height == LayoutParams.MATCH_PARENT) {
final int height = Math.max(0, getMeasuredHeight()
- getPaddingTopWithForeground() - getPaddingBottomWithForeground()
- lp.topMargin - lp.bottomMargin);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
height, MeasureSpec.EXACTLY);
} else {
childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
lp.topMargin + lp.bottomMargin,
lp.height);
}
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
可以看到在FrameLayout的onMeasue里面,调用setMeasuredDimension完成对自己的测量之后,还有循环调用子View的measure方法。如果子View是一个ViewGroup,又会循环调用它的子View的measure直到测完viewTree顶部的所有子View。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
final View host = mView;
...
try {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
} ...
}
这个host依旧是DecorView,我们到View里面去看看:
public void layout(int l, int t, int r, int b) {
...
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 (...) {
onLayout(changed, l, t, r, b);
...
}
...
}
setFrame(l, t, r, b) 可以理解为给 mLeft , mTop, mRight, mBottom 这四个变量赋值, 然后基本就能确定当前 View 在父视图的位置了。这几个值构成的矩形区域就是当前 View 显示的位置,这里的具体位置都是相对与父视图的位置。
onLayout方法在FrameLayout被重写:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
到这个时候就知道了,在FrameLayout通过layout确认自己的位置,然后在onLayout里面循环子View确定子View相对于自己的位置。
执行完viewTree的测量和布局后,大小和位置都固定了,接下来就是执行performDraw方法对整个viewTree进行绘制了。
private void performDraw() {
...
boolean canUseAsync = draw(fullRedrawNeeded, usingAsyncReport && mSyncBuffer);
...
}
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
........
if(){
//硬件绘制,效果更好
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
}else{
//软件绘制
drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)
}
........
}
performDraw方法触发draw方法,draw里面根据条件选择触发硬件绘制和软件绘制。硬件绘制效果会更加好一点。
我们来看软件绘制流程:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
...
mView.draw(canvas);
...
}
?软件绘制流程调用native方法创建了一个canvas,然后调用DecorView的draw方法。实际上还是会跑到View的draw方法里面:
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
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)
* 7. If necessary, draw the default focus highlight
*/
// Step 1, draw the background, if needed
int saveCount;
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
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(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);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
........
}
在View的draw方法里面,会先绘制背景图片,如果没有就不绘制;接着调用onDraw方法,View 的 onDraw(canvas) 是空方法,因为每个 View 的内容是各不相同的,所以需要由子类去实现具体逻辑。接着执行dispatchDraw方法,这个方法在View中是一个空实现,但是在ViewGroup里面有具体的实现:
@Override
protected void dispatchDraw(Canvas canvas) {
...
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
...
for (int i = 0; i < childrenCount; i++) {
...
if (...) {
more |= drawChild(canvas, child, drawingTime);
}
...
}
...
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
可以看到在ViewGroup里面又循环执行子View的三参的draw方法:
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
//跳过当前 View 的绘制, 直接绘制子 View.
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
}
...
return more;
}
?我们再来看看viewGroup的构造函数里面:
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!isShowingLayoutBounds()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
......
}
原来在ViewGroup的构造函数里面已经设置了这些参数,默认让他不执行draw。?
?从这块代码可以看到如果当前的ViewGroup没有backgrounds的话,是不会执行draw绘制自己的,直接执行dispatchDraw绘制子View。
也就是说,从DecorView开始,执行draw方法,先绘制自己,然后循环自己的所有的子view,如果子View是ViewGroup,那么默认直接绘制这个ViewGroup的子View,否则执行draw方法。
ViewGroup的构造函数:
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!isShowingLayoutBounds()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
......
}
在ViewGroup的父类执行draw方法,最终会执行到当前ViewGroup的三参的draw方法,这个份方法里面判断了WILL_NOT_DRAW参数,命中则不会执行onDraw,直接执行dispatchDraw绘制子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);
}
/**
* Returns whether or not this View draws on its own.
*
* @return true if this view has nothing to draw, false otherwise
*/
@ViewDebug.ExportedProperty(category = "drawing")
public boolean willNotDraw() {
return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
}
?View类里暴露了设置WILL_NOT_DRAW标记的接口:setWillNotDraw(boolean willNotDraw),可以在? 继承ViewGroup的自定义View? 里使用setWillNotDraw(false)。
view.java
public void setBackgroundDrawable(Drawable background) {
if (background != null) {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
}
}
}
public void setForeground(Drawable foreground) {
if (foreground != null) {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
}
}
private void setDefaultFocusHighlight(Drawable highlight) {
mDefaultFocusHighlight = highlight;
mDefaultFocusHighlightSizeChanged = true;
if (highlight != null) {
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
}
}
}
background:view背景
foreground(mDrawable字段):view前景
focusHighLight:view获得焦点时高亮
我们调用这三个方法也行。