前言
前几篇文章,笔者分别讲述了DecorVIEw,measure,layout流程等,接下来将详细分析三大工作流程的最后一个流程――绘制流程。测量流程决定了VIEw的大小,布局流程决定了VIEw的位置,那么绘制流程将决定VIEw的样子,一个VIEw该显示什么由绘制流程完成。以下源码均取自AndroID API 21。
从performDraw说起
前面几篇文章提到,三大工作流程始于VIEwRootImpl#performTraversals,在这个方法内部会分别调用performMeasure,performlayout,performDraw三个方法来分别完成测量,布局,绘制流程。那么我们现在先从performDraw方法看起,VIEwRootImpl#performDraw:
private voID performDraw() { //... final boolean fullRedrawNeeded = mFullRedrawNeeded; try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } //省略...}
里面又调用了VIEwRootImpl#draw方法,并传递了fullRedrawNeeded参数,而该参数由mFullRedrawNeeded成员变量获取,它的作用是判断是否需要重新绘制全部视图,如果是第一次绘制视图,那么显然应该绘制所以的视图,如果由于某些原因,导致了视图重绘,那么就没有必要绘制所有视图。我们来看看VIEwRootImpl#draw:
private voID draw(boolean fullRedrawNeeded) { ... //获取mDirty,该值表示需要重绘的区域 final Rect dirty = mDirty; if (mSurfaceHolder != null) { // The app owns the surface,we won't draw. dirty.setEmpty(); if (animating) { if (mScroller != null) { mScroller.abortAnimation(); } disposeResizeBuffer(); } return; } //如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制 //第一次绘制流程,需要绘制所有视图 if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; dirty.set(0,(int) (mWIDth * appScale + 0.5f),(int) (mHeight * appScale + 0.5f)); } //省略... if (!drawSoftware(surface,mAttachInfo,xOffset,yOffset,scalingrequired,dirty)) { return; }}
这里省略了一部分代码,我们只看关键代码,首先是先获取了mDirty值,该值保存了需要重绘的区域的信息,关于视图重绘,后面会有文章专门叙述,这里先熟悉一下。接着根据fullRedrawNeeded来判断是否需要重置dirty区域,最后调用了VIEwRootImpl#drawSoftware方法,并把相关参数传递进去,包括dirty区域,我们接着看该方法的源码:
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区域,由dirty区域决定 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; } canvas.setDensity(mDensity); } try { if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { canvas.drawcolor(0,PorterDuff.Mode.CLEAR); } dirty.setEmpty(); mIsAnimating = false; attachInfo.mDrawingTime = SystemClock.uptimeMillis(); mVIEw.mPrivateFlags |= VIEw.PFLAG_DRAWN; try { canvas.translate(-xoff,-yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingrequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; //正式开始绘制 mVIEw.draw(canvas); } } return true;}
可以看书,首先是实例化了Canvas对象,然后锁定该canvas的区域,由dirty区域决定,接着对canvas进行一系列的属性赋值,最后调用了mVIEw.draw(canvas)方法,前面分析过,mVIEw就是DecorVIEw,也就是说从DecorVIEw开始绘制,前面所做的一切工作都是准备工作,而现在则是正式开始绘制流程。
VIEw的绘制
由于VIEwGroup没有重写draw方法,因此所有的VIEw都是调用VIEw#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 * 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); // 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); // we're done... return; } ...}
可以看到,draw过程比较复杂,但是逻辑十分清晰,而官方注释也清楚地说明了每一步的做法。我们首先来看一开始的标记位dirtyOpaque,该标记位的作用是判断当前VIEw是否是透明的,如果VIEw是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等。这样很容易理解,因为一个VIEw既然是透明的,那就没必要绘制它了。接着是绘制流程的六个步骤,这里先小结这六个步骤分别是什么,然后再展开来讲。
绘制流程的六个步骤:
1、对VIEw的背景进行绘制
2、保存当前的图层信息(可跳过)
3、绘制VIEw的内容
4、对VIEw的子VIEw进行绘制(如果有子VIEw)
5、绘制VIEw的褪色的边缘,类似于阴影效果(可跳过)
6、绘制VIEw的装饰(例如:滚动条)
其中第2步和第5步是可以跳过的,我们这里不做分析,我们重点来分析其它步骤。
Skip 1:绘制背景
这里调用了VIEw#drawBackground方法,我们看它的源码:
private voID drawBackground(Canvas canvas) { //mBackground是该VIEw的背景参数,比如背景颜色 final Drawable background = mBackground; if (background == null) { return; } //根据VIEw四个布局参数来确定背景的边界 setBackgroundBounds(); ... //获取当前VIEw的mScrollX和mScrollY值 final int scrollX = mScrollX; final int scrollY = mScrollY; if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { //如果scrollX和scrollY有值,则对canvas的坐标进行偏移,再绘制背景 canvas.translate(scrollX,scrollY); background.draw(canvas); canvas.translate(-scrollX,-scrollY); }}
可以看出,这里考虑到了vIEw的偏移参数,scrollX和scrollY,绘制背景在偏移后的vIEw中绘制。
Skip 3:绘制内容
这里调用了VIEw#onDraw方法,VIEw中该方法是一个空实现,因为不同的VIEw有着不同的内容,这需要我们自己去实现,即在自定义view中重写该方法来实现。
Skip 4: 绘制子VIEw
如果当前的VIEw是一个VIEwGroup类型,那么就需要绘制它的子VIEw,这里调用了dispatchDraw,而VIEw中该方法是空实现,实际是VIEwGroup重写了这个方法,那么我们来看看,VIEwGroup#dispatchDraw:
protected voID dispatchDraw(Canvas canvas) { boolean usingRenderNodePropertIEs = canvas.isRecordingFor(mRenderNode); final int childrenCount = mChildrenCount; final VIEw[] children = mChildren; int flags = mGroupFlags; 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; } } int childindex = customOrder ? getChildDrawingOrder(childrenCount,i) : i; final VIEw child = (preorderedList == null) ? children[childindex] : preorderedList.get(childindex); if ((child.mVIEwFlags & VISIBIliTY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas,child,drawingTime); } } //省略...}
源码很长,这里简单说明一下,里面主要遍历了所以子VIEw,每个子VIEw都调用了drawChild这个方法,我们找到这个方法,VIEwGroup#drawChild:
protected boolean drawChild(Canvas canvas,VIEw child,long drawingTime) { return child.draw(canvas,this,drawingTime);}
可以看出,这里调用了VIEw的draw方法,但这个方法并不是上面所说的,因为参数不同,我们来看看这个方法,VIEw#draw:
boolean draw(Canvas canvas,VIEwGroup parent,long drawingTime) { //省略... if (!drawingWithDrawingCache) { if (drawingWithRenderNode) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; ((displayListCanvas) canvas).draWrenderNode(renderNode); } else { // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchDraw(canvas); } else { draw(canvas); } } } else if (cache != null) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; if (layerType == LAYER_TYPE_NONE) { // no layer paint,use temporary paint to draw bitmap Paint cachePaint = parent.mCachePaint; if (cachePaint == null) { cachePaint = new Paint(); cachePaint.setDither(false); parent.mCachePaint = cachePaint; } cachePaint.setAlpha((int) (Alpha * 255)); canvas.drawBitmap(cache,0.0f,cachePaint); } else { // use layer paint to draw the bitmap,merging the two Alphas,but also restore int layerPaintAlpha = mLayerPaint.getAlpha(); mLayerPaint.setAlpha((int) (Alpha * layerPaintAlpha)); canvas.drawBitmap(cache,mLayerPaint); mLayerPaint.setAlpha(layerPaintAlpha); } }}
我们主要来看核心部分,首先判断是否已经有缓存,即之前是否已经绘制过一次了,如果没有,则会调用draw(canvas)方法,开始正常的绘制,即上面所说的六个步骤,否则利用缓存来显示。
这一步也可以归纳为VIEwGroup绘制过程,它对子VIEw进行了绘制,而子VIEw又会调用自身的draw方法来绘制自身,这样不断遍历子VIEw及子VIEw的不断对自身的绘制,从而使得VIEw树完成绘制。
Skip 6 绘制装饰
所谓的绘制装饰,就是指VIEw除了背景、内容、子VIEw的其余部分,例如滚动条等,我们看VIEw#onDrawForeground:
public voID onDrawForeground(Canvas canvas) { onDrawScrollindicators(canvas); onDrawScrollbars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; if (foreground != null) { if (mForegroundInfo.mBoundsChanged) { mForegroundInfo.mBoundsChanged = false; final Rect selfBounds = mForegroundInfo.mSelfBounds; final Rect overlayBounds = mForegroundInfo.mOverlayBounds; if (mForegroundInfo.mInsIDepadding) { selfBounds.set(0,getWIDth(),getHeight()); } else { selfBounds.set(getpaddingleft(),getpaddingtop(),getWIDth() - getpaddingRight(),getHeight() - getpaddingBottom()); } final int ld = getLayoutDirection(); Gravity.apply(mForegroundInfo.mGravity,foreground.getIntrinsicWIDth(),foreground.getIntrinsicHeight(),selfBounds,overlayBounds,ld); foreground.setBounds(overlayBounds); } foreground.draw(canvas); }}
可以看出,逻辑很清晰,和一般的绘制流程非常相似,都是先设定绘制区域,然后利用canvas进行绘制,这里就不展开详细地说了,有兴趣的可以继续了解下去。
那么,到目前为止,VIEw的绘制流程也讲述完毕了,希望这篇文章对你们起到帮助作用,谢谢你们的阅读。
更多阅读
Android View 测量流程(Measure)全面解析
Android View 布局流程(Layout)全面解析
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。
总结以上是内存溢出为你收集整理的Android View 绘制流程(Draw)全面解析全部内容,希望文章能够帮你解决Android View 绘制流程(Draw)全面解析所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)