Android UICanvas 画布 ⑥ ( Canvas 绘图源码分析 | ViewRootImpl#draw 方法源码 | ViewRootImpl#drawSoftware 方法源码 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android UICanvas 画布 ⑥ ( Canvas 绘图源码分析 | ViewRootImpl#draw 方法源码 | ViewRootImpl#drawSoftware 方法源码 )相关的知识,希望对你有一定的参考价值。
文章目录
Canvas 状态保存机制 中 , 存在两个栈结构 , 分别是 状态栈 和 图层栈 ;
其中 图层栈 又称为 Layer 栈 ;
Canvas 画布中 , 有 2 2 2 套坐标系 , 分别是 :
- Canvas 自身坐标系
- Canvas 绘图坐标系
一、Canvas 绘图源码分析
参考 【Android 应用开发】UI绘制流程 ( 生命周期机制 | 布局加载机制 | UI 绘制流程 | 布局测量 | 布局摆放 | 组件绘制 | 瀑布流布局案例 ) 博客 , android 的 UI 界面绘制流程为 :
- 布局测量
- 布局摆放
- 组件绘制
这里 分析 Android 组件绘制过程中 , Canvas 画布相关操作 ;
在绘制时 , 最终调用的方法是 ViewRootImpl#draw 方法 , 在该方法中
Surface surface
是最终绘制的面板 ,
Surface surface = mSurface;
绘图时 , 首先要确认绘制区域 , 下面的代码就是 在 手机界面 中 定位出 绘制区域 用的 , 这块绘制区域 是属于 Surface 的 ,
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));
在该方法最后调用了 ViewRootImpl#drawSoftware 方法 , 进行下一步绘制操作 ;
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty))
return;
在 ViewRootImpl#drawSoftware 方法中 , 由 ViewRootImpl#mSurface 生成 Canvas 画布 , 并为 Canvas 设置绘制坐标 , Rect dirty 可以理解为 Canvas 的绘制坐标区域 ;
// 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);
二、ViewRootImpl#draw 方法源码
ViewRootImpl#draw 方法源码 :
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks
private void draw(boolean fullRedrawNeeded)
// 具体绘画的面板
Surface surface = mSurface;
if (!surface.isValid())
return;
if (DEBUG_FPS)
trackFPS();
if (!sFirstDrawComplete)
synchronized (sFirstDrawHandlers)
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++)
mHandler.post(sFirstDrawHandlers.get(i));
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged)
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating)
curScrollY = mScroller.getCurrY();
else
curScrollY = mScrollY;
if (mCurScrollY != curScrollY)
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker)
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
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));
if (DEBUG_ORIENTATION || DEBUG_DRAW)
Log.v(mTag, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty=" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + " surface="
+ surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
appScale + ", width=" + mWidth + ", height=" + mHeight);
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();
源码参考路径 : frameworks/base/core/java/android/view/ViewRootImpl.java
三、ViewRootImpl#drawSoftware 方法源码
ViewRootImpl#drawSoftware 方法源码 :
/**
* @return true if drawing was successful, false if an error occurred
*/
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;
源码参考路径 : frameworks/base/core/java/android/view/ViewRootImpl.java
以上是关于Android UICanvas 画布 ⑥ ( Canvas 绘图源码分析 | ViewRootImpl#draw 方法源码 | ViewRootImpl#drawSoftware 方法源码 )的主要内容,如果未能解决你的问题,请参考以下文章
Android UICanvas 画布 ② ( Canvas 状态栈 | Canvas 状态栈出栈到指定层级 )
Android UICanvas 画布 ⑦ ( Canvas 绘制显示区域 | Canvas 绘制矩形源码分析 )
Android UICanvas 画布 ⑤ ( Canvas 坐标系 | Canvas 绘图坐标系变换示例 )
Android UICanvas 画布 ③ ( Canvas 图层栈 | Canvas#saveLayer() 新建图层 | Canvas 状态栈保存信息标志位 )
Android UICanvas 画布 ④ ( Canvas 坐标系 | Canvas 自身坐标系 | Canvas 绘图坐标系 )
Android UICanvas 画布 ⑧ ( Canvas 绘图坐标系 2x2 矩阵 | Canvas 绘图坐标系 3x3 操作矩阵 )