那么今天我们需要了解的是对UI具体的绘制流程measure,layout,Draw进行深入分析
首先我们找到了绘制流程当中performTraversals()的测量布局方法
这里可以看到,performmeasure方法里面调用了mView.measure方法,这里的mView指的是我们的顶层DecorView
在分析measure方法之前,我们来分析一下里面的参数,childWidthMeasureSpec和childHeightMeasureSpec,进入
到getRootMeasureSpec方法下来
这里引入了一个对象MeasureSpec
MeasureSpec代表一个32位的int值,高两位代表SpecMode,低30位代表SpecSize,SpecMode指的是测量模式,SpecSize是指在某种测量模式下的规格大小。MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的内存分配,为了方便操作,他提供了打包和解包方法。
由上面可以得出结论,系统内部是通过MeasureSpec来进行view的测量,而通常我们都是给View设置LayoutParams。需要注意的是:View的LayoutParams会和父容器的约束条件一起决定View的MeasureSpec,从而进一步决定View的宽高。另外,对于顶级View(即DecorView)和普通View来说,MeasureSpec的转换过程略有不同。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽高
对于DecorView来说,其MeasureSpec产生过程如下图所示,其中mWidth和mHeight是屏幕的尺寸
由于DecorView是一个FrameLayout,所以我们以FrameLayout为例来分析measure过程。回到mView.measure方法
measure方法里面调用了onMeasure方法,且measure方法是一个final方法,所以不可被重写
看View类中的onMeasure方法的介绍上说如果被重写,则是子类的责任,所以要看Decorview的onMeasure方法就是看
FrameLayout的onMeasure方法
总结一下:首先,FrameLayout根据它的MeasureSpec来对每一个子View进行测量,即调用measureChildWithMargin方法,这个方法下面会详细说明;对于每一个测量完成的子View,会寻找其中最大的宽高,那么FrameLayout的测量宽高会受到这个子View的最大宽高的影响(wrap_content模式),接着调用setMeasureDimension方法,把FrameLayout的测量宽高保存。最后则是特殊情况的处理,即当FrameLayout为wrap_content属性时,如果其子View是match_parent属性的话,则要重新设置FrameLayout的测量规格,然后重新对该部分View测量。
这里面调用了getChildMeasureSpec,通过父容器的MeasureSpec和自身的layoutParams来确定自己的MeasureSpec,然后再调用子View的的measure方法。
总结:那么现在我们能得到整体的测量流程:在performTraversals开始获得DecorView种的系统布局的尺寸,然后在performMeasure方法中开始测量流程,对于不同的layout布局有着不同的实现方式,但大体上是在onMeasure方法中,对每一个子View进行遍历,根据ViewGroup的MeasureSpec及子View的layoutParams来确定自身的测量宽高,然后最后根据所有子View的测量宽高信息再确定父亲的宽高。不断的遍历子View的measure方法,根据ViewGroup的MeasureSpec及子View的LayoutParams来决定子View的MeasureSpec,进一步获取子View的测量宽高,然后逐层返回,不断保存ViewGroup的测量宽高。
在ViewRootImpl.performLayout()方法中,调用了根视图的layout()方法,也就是View.layout()方法。
view的layout方法
这个方法有三个需要注意的地方
1;l,t,r,b四个参数。分别代表的是距离父控件的上下左右距离
2,SetFrame方法
注意上面用红色标记的部分,其中newWidth并不是mMeasuredWidth,而是用right - left。难道我(View)的宽度值都不算数吗?要通过所谓的右边减去左边来确定我的宽度?
没错就是这样,setFrame()的这个Frame,可以理解为一个View真正渲染到屏幕上的矩形区域。而四个参数left, top, right, bottom就是指定了这个矩形区域的四个顶点。
可以想象一下这样的情况,父控件的宽度是500,padding值为0,那么其子控件可用的宽度自然就是500了,假如有一个控件已经占满了300px的宽度,而另一个控件同样需要300px的宽度,而父控件只剩下了200px的宽度,Android是怎么处理这件事情的呢?
首先父控件调用onMeasure()方法,遍历子控件,调用子控件的onMeasure()方法,这样一来大家都知道了自己有多大了。
然后父控件调用了onLayout()方法,onLayout()方法实际上是给自己的子控件布局,假如一个控件是View的话,它就没有子控件了,onLayout实际上就没什么作用。回到这个情景,onLayout()遍历的调用子控件的layout()方法,指定子控件的上下左右的位置,layout()方法中调用setFrame()方法,根据参数值设置自己实际渲染的区域。那么当第一个控件占了300px的宽度,这个时候父控件已经知道了剩下的可用宽度只有200px了,那么它就会根据这个值来进行调整,将计算好,根据剩下的空间把第二个子控件的上下左右四个参数交给它的layout方法,让他去设置自己的frame。也就是说,第二个空间的Frame的宽度只有200px了,并不会超出这个范围。
这里得出一个事实:measure出来的宽度与高度,是该控件期望得到的尺寸,但是真正显示到屏幕上的位置与大小是由layout()方法来决定的。left, top决定位置,right,bottom决定frame渲染尺寸。
3,onLayout方法(同样是以FrameLayout为例来分析)
这个时候我们会发现,当前我们的组件在不断的迭代当前的子view,然后让它们开始调用自己layout方法进行定位,所以直接从此处可以看出来,当前我们的布局摆放流程实际上是,先得到顶层, 顶层自己先开始layout进行布局定位,然后调用onLayout调用子view让子view调用自己的layout对自己进行定位以达到定位的所有目的。
这里我们思考一个问题:View的测量宽/高和最终宽/高有什么区别?
分析四个方法可以看出来,View的测量宽高和最终宽高是相等的,只不过测量宽高形成于View的measure过程,而最终宽高形成于View的layout过程,即两者的赋值时间不同,测量的宽高赋值时间稍微早一些。但是也有特别情况会导致两者不一致。
下面来举个例子来测试:自定义一个ViewGroup
public class WaterfallFlowLayout extends ViewGroup {
public WaterfallFlowLayout(Context context) {
super(context);
}
public WaterfallFlowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public WaterfallFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//思路,通过前面两节课我们知道了其实,绘制流程最终会调用到我门的OnMesure 和 onLayout,
//而不同的布局,他们自己的实现不一样,所以才有了我们使用的这些基本布局组件
//那么我们现在自己来开发一个瀑布式的流式布局
//不规则控件进行流式
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i("Sven","onLayout"+System.currentTimeMillis());
int left,top,right,bottom;
int curLeft = 0;
int curTop = 0;
//开始迭代
for (int i = 0;i < lstLineView.size();i++){
List lineviews = lstLineView.get(i);
for (int j = 0;j < lineviews.size();j++){
View view = lineviews.get(j);
MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
left = curLeft + layoutParams.leftMargin;
top = curTop + layoutParams.topMargin;
right = left + view.getMeasuredWidth();
bottom = top + view.getMeasuredHeight();
view.layout(left,top,right,bottom);
curLeft += view.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
}
curLeft = 0;
curTop += lstLineHegiht.get(i);
}
lstLineView.clear();
lstLineHegiht.clear();
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
List lstLineHegiht = new ArrayList<>();
List> lstLineView = new ArrayList<>();
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//onmeasure方法会调用两次,所以要做优化
lstLineHegiht.clear();//
lstLineView.clear();//
Log.i("Sven","onMeasure"+System.currentTimeMillis());
//1.先完成自己的宽高测量
//需要得到mode进行判断我的显示模式是怎样的
//得到宽高模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//父容器宽高
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//当前控件宽高(自己)
int measureWidth = 0;
int measureHeight = 0;
if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY ){
measureWidth = widthSize;
measureHeight = heightSize;
}else{
//当前行高宽
int iChildHegiht = 0;
int iChildWidth = 0;
int iCurWidth = 0;
int iCurHeight = 0;
//数量
int childCount = getChildCount();
List viewList = new ArrayList<>();
for (int i = 0;i < childCount ; i++){
//确定两个事情,当前行高,当前行宽
View child = getChildAt(i);
//先让子控件测量自己
measureChild(child,widthMeasureSpec,heightMeasureSpec);
//MARGIN 获取到当前LayoutParams
MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
//获取实际宽高
iChildWidth = child.getMeasuredWidth() + layoutParams.rightMargin + layoutParams.leftMargin;
iChildHegiht = child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
//是否需要开始换行
if(iChildWidth + iCurWidth > widthSize){
//1.保存当前行信息
measureWidth = Math.max(measureWidth,iCurWidth);
measureHeight += iCurHeight;
lstLineHegiht.add(iCurHeight);
lstLineView.add(viewList);
//更新的行信息
iCurWidth = iChildWidth;
iCurHeight = iChildHegiht;
viewList = new ArrayList<>();
viewList.add(child);
//2.记录新行信息
}else{
iCurWidth += iChildWidth;
iCurHeight = Math.max(iCurHeight,iChildHegiht);
viewList.add(child);
}
//6.如果正好是最后一行需要换行
if(i == childCount - 1){
//6.1.记录当前行的最大宽度,高度累加
measureWidth = Math.max(measureWidth,iCurWidth);
measureHeight += iCurHeight;
//6.2.将当前行的viewList添加至总的mViewsList,将行高添加至总的行高List
lstLineView.add(viewList);
lstLineHegiht.add(iCurHeight);
}
}
}
setMeasuredDimension(measureWidth,measureHeight);
}
使用方式xml
上面就是自定义一个自己控制measure和layout规则的viewgroup控件
来看看performTraversals中是怎么样调用的performDraw方法
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
在这里我们可以看到一个关键点就是在isViewVisible = true(也就是view为显示状态下,这里会在此发起一次scheduleTraversals,所以,这也是为什么我们的onMeasure会调用两次的原因)
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
} else if (mView == null) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
根据一个变量fullRedrawNeeded来判断是否需要全部绘制,如果是部分视图发生改变,就没必要全部重绘。
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
...
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return;
}
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
boolean accessibilityFocusDirty = false;
final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
if (drawable != null) {
final Rect bounds = mAttachInfo.mTmpInvalRect;
final boolean hasFocus = getAccessibilityFocusedRect(bounds);
if (!hasFocus) {
bounds.setEmpty();
}
if (!bounds.equals(drawable.getBounds())) {
accessibilityFocusDirty = true;
}
}
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
// Draw with hardware renderer.
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
}
if (invalidateRoot) {
mAttachInfo.mThreadedRenderer.invalidateRoot();
}
dirty.setEmpty();
// Stage the content drawn size now. It will be transferred to the renderer
// shortly before the draw commands get send to the renderer.
final boolean updated = updateContentDrawBounds();
if (mReportNextDraw) {
// report next draw overrides setStopped()
// This value is re-sync'd to the value of mStopped
// in the handling of mReportNextDraw post-draw.
mAttachInfo.mThreadedRenderer.setStopped(false);
}
if (updated) {
requestDrawWindow();
}
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
if (mAttachInfo.mThreadedRenderer != null &&
!mAttachInfo.mThreadedRenderer.isEnabled() &&
mAttachInfo.mThreadedRenderer.isRequested()) {
try {
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return;
}
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
首先是先获取了mDirty值,这里保存了需要重绘的区域的信息,。接着根据fullRedrawNeeded来判断是否需要重置dirty区域,最后调用了ViewRootImpl#drawSoftware方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
canvas = mSurface.lockCanvas(dirty);
// The dirty rectangle can be modified by Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not lock surface", e);
// Don't assume this is due to out of memory, it could be
// something else, and if it is something else then we could
// kill stuff (or ourself) for no reason.
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}
// If this bitmap's format includes an alpha channel, we
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
// or
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
// left in the blank areas.
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
", metrics=" + cxt.getResources().getDisplayMetrics() +
", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
}
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
if (LOCAL_LOGV) {
Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
}
}
return true;
}
可以看出,首先是用Surface(真正的画板)生成了一个Canvas对象,然后由dirty区域来决定canvas的区域,接着对canvas进行一系列的属性赋值,最后调用了mView.draw(canvas)方法,那么之前就讲过这里的mView就是我们的DectorView所以是从DectorView顶层开始绘制 那么之前的一切都是在进行准备一块画板具体的绘制实在mView.draw当中,这里将画板给入,而现在则是正式开始绘制流程。
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(画子view)
* 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);
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 (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// 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
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
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);
if (debugDraw()) {
debugDrawFocus(canvas);
}
}
可以看到,draw过程比较复杂,但是逻辑十分清晰,而官方注释也清楚地说明了每一步的做法。我们首先来看一开始的标记位dirtyOpaque,该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等。这样很容易理解,因为一个View既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,这里先小结这六个步骤分别是什么,然后再展开来讲。
6、绘制View的装饰
1,绘制背景:
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mThreadedRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
3,绘制内容
那么在这里看到第三步调用了onDraw,View中该方法是一个空实现,这里同理于之前的onMeasure和onLayout因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现
4.在第四步的dispatchDraw(canvas)当中(未实现方法,由子类去重写),这个方法我们会发现他在一直迭代子view,这里我门是以ViewGroup为例
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
}
final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
}
int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
clipSaveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
if (usingRenderNodeProperties) canvas.insertReorderBarrier();
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
int transientIndex = transientCount != 0 ? 0 : -1;
// Only use the preordered list if not HW accelerated, since the HW pipeline will do the
// draw reordering internally
final ArrayList preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
while (transientIndex >= 0) {
// there may be additional transient views after the normal views
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
break;
}
}
if (preorderedList != null) preorderedList.clear();
// Draw any disappearing views that have animations
if (mDisappearingChildren != null) {
final ArrayList disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
if (usingRenderNodeProperties) canvas.insertInorderBarrier();
if (debugDraw()) {
onDebugDraw(canvas);
}
if (clipToPadding) {
canvas.restoreToCount(clipSaveCount);
}
// mGroupFlags might have been updated by drawChild()
flags = mGroupFlags;
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
mLayoutAnimationController.isDone() && !more) {
// We want to erase the drawing cache and notify the listener after the
// next frame is drawn because one extra invalidate() is caused by
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
@Override
public void run() {
notifyAnimationListener();
}
};
post(end);
}
}
源码很长,这里简单讲下,里面主要遍历了所以子View,每个子View都调用了drawChild这个方法,我们找到这个方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
这里开始调用了子view的draw,同样开始向下遍历。
那么此时,其实同理于我门之前的测量和布局,父亲取得所有子控件开始遍历,调用子控件让子控件自己调用自己的draw开始绘制自己
逻辑很清晰,都是先设定绘制区域,然后利用canvas进行绘制。