canvas教程

你需要知道的Android View的绘制

字号+ 作者:H5之家 来源:H5之家 2017-03-02 10:01 我要评论( )

你需要知道的Android View的绘制

经过上一篇AndroidView的布局分析之后,我们继续View的绘制分析讲解。我们依旧从ViewRootImpl#performTraversals说起。

private void performTraversals() { ... if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); } } ...}

我们对performDraw()执行绘制方法进行分析:

private void performDraw() { ··· final boolean fullRedrawNeeded = mFullRedrawNeeded; try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ···}

在ViewRootImpl#performDraw里调用了ViewRootImpl#draw方法,并将fullRedrawNeeded形参传进方法体中,我们通过变量的命名方式大概推断出该成员变量的作用是判断是否需要重新绘制全部视图,因为我们从DecorView根布局分析至今,我们显然是绘制所有视图的。那么我们继续分析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, 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方法,并把相关参数传递进去。

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进行一系列的赋值,最后调用mView.draw(canvas)方法。从之前的分析可以知道mView指向的就是DecorView。

View#draw。我们清楚知道DecorView、FrameLayout、ViewGroup、View之间的继承关系。我们在FrameLayout里面用方法搜索的时候,搜到的draw(Canvas canvas)是View里面的方法。那就是说,View的子类都是是调用View#draw()方法的。(源码注释中不建议重写draw方法)

public class View implements···{ ··· 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; // 绘制背景,只有dirtyOpaque为false时才进行绘制 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 // 绘制子View 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; ··· }}

我们可以看到官方给出的注释是非常清晰地说明每一步的做法。我们首先来看一开始的标记位dirtyOpaque,这是标记的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,有一些步骤就不进行。我们还是跟着注释来分析一下有哪六个步骤:

绘制背景

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • Android中的SurfaceView详解

    Android中的SurfaceView详解

    2017-02-28 17:01

  • Android编程实现支持拖动改变位置的图片中叠加文字功能示例

    Android编程实现支持拖动改变位置的图片中叠加文字功能示例

    2017-02-28 15:02

  • Android倾斜、描边、自定义字体的TextView

    Android倾斜、描边、自定义字体的TextView

    2017-02-26 11:03

  • Android中圆角矩形和圆形的多种实现方式

    Android中圆角矩形和圆形的多种实现方式

    2017-02-24 12:03

网友点评
p