view绘制的起点---》ViewRootImpl的rerquestLayout方法
检查调用方法的线程是否与创建ViewRootImpl的线程相同
@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()开启三大流程。
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 { ... } }
预测量measureHierarchy()
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方法里面最少都要测量一次,最多是三次。
DecorView的MeasureSpec怎么来的
在预测量方法里面有一个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
我们测量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。
真正布局ViewTree的函数performLayout
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的测量和布局后,大小和位置都固定了,接下来就是执行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为什么默认不执行onDraw方法
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获得焦点时高亮
我们调用这三个方法也行。
总结
- 若要ViewGroup onDraw()执行,只需要setWillNotDraw(false)、设置背景、设置前景、设置焦点高亮,4个选项其中一项满足即可。
- 当然如果不想在 继承ViewGroup的自定义View onDraw里绘制,也可以重写ViewGroup dispatchDraw()方法,在该方法里绘制 自定义View 内容。