前几篇文章,讲述了measure,layout流程等,接下来将详细分析绘制流程。

测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么由绘制流程完成;
那我们就开始开车了;
三大工作流程始于ViewRootImpl#performTraversals,在这个方法内部会分别调用performMeasure,performLayout,performDraw三个方法来分别完成测量,布局,绘制流程。那么我们现在先从performDraw方法看起;
- private void performDraw() {
 - //...
 - final boolean fullRedrawNeeded = mFullRedrawNeeded;
 - try {
 - draw(fullRedrawNeeded);
 - } finally {
 - mIsDrawing = false;
 - Trace.traceEnd(Trace.TRACE_TAG_VIEW);
 - }
 - }
 
里面又调用了ViewRootImpl#draw方法,我们来看看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;
 - }
 - }
 
根据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开始绘制;
由于ViewGroup没有重写draw方法,因此所有的View都是调用View#draw方法,因此,我们直接看它的源码
- public void draw(Canvas canvas) {
 - ....
 - // 1. 绘制本身View背景
 - if (!dirtyOpaque) {
 - drawBackground(canvas);
 - }
 - if (!verticalEdges && !horizontalEdges) {
 - // Step 3, draw the content
 - // 2. 绘制内容,默认空实现 需复写
 - if (!dirtyOpaque) onDraw(canvas);
 - // 3. 绘制 children
 - dispatchDraw(canvas);
 - drawAutofilledHighlight(canvas);
 - // 4. 分发Draw (单一View空实现,ViewGroup见下面分析)
 - if (mOverlay != null && !mOverlay.isEmpty()) {
 - mOverlay.getOverlayView().dispatchDraw(canvas);
 - }
 - // 5. 绘制装饰 (前景色,滚动条)
 - onDrawForeground(canvas);
 - return;
 - }
 - ....
 - }
 
可以看到,draw过程比较复杂,但是逻辑十分清晰。首先来看一开始的标记位dirtyOpaque,
该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等;
绘制流程的五个步骤:
绘制View的装饰(例如:前景色,滚动条);
- //绘制背景
 - private void drawBackground(Canvas canvas) {
 - final Drawable background = mBackground;
 - if (background == null) {
 - return;
 - }
 - // 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界
 - setBackgroundBounds();
 - // 先尝试用HWUI绘制
 - 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) {
 - //调用 Drawable 的 draw 方法来进行背景的绘制
 - background.draw(canvas);
 - } else {
 - // 若 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移平移画布
 - canvas.translate(scrollX, scrollY);
 - //调用 Drawable 的 draw 方法来进行背景的绘制
 - background.draw(canvas);
 - canvas.translate(-scrollX, -scrollY);
 - }
 - }
 
- // 绘制View本身内容,空实现,子类必须复写
 - protected void onDraw(Canvas canvas) {
 - }
 
这里调用了View#onDraw方法,View中该方法是一个空实现,因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现;
当前的View是一个ViewGroup类型,那么就需要绘制它的子View,这里调用了dispatchDraw,而View中该方法是空实现,实际是ViewGroup重写了这个方法,那么我们来看看;
- @Override
 - protected void dispatchDraw(Canvas canvas) {
 - ...
 - // 遍历子View
 - final int childrenCount = mChildrenCount;
 - ...
 - 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) {
 - // 调用 drawChild 方法,进行子元素绘制
 - more |= drawChild(canvas, child, drawingTime);
 - }
 - }
 - ....
 - }
 
- @Override
 - protected void dispatchDraw(Canvas canvas) {
 - ...
 - // 1. 遍历子View
 - final int childrenCount = mChildrenCount;
 - ...
 - 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) {
 - // 调用 drawChild 方法,进行子元素绘制
 - more |= drawChild(canvas, child, drawingTime);
 - }
 - }
 - ....
 - }
 
所谓的绘制装饰,就是指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, 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);
 - }
 - //调用 Drawable 的 draw 方法,绘制前景色
 - foreground.draw(canvas);
 - }
 - }
 
到目前为止,View的绘制流程也讲述完毕了;
其实绘制这块还是很重要的,下次还是要继续讲解下;
学如逆水行舟,不进则退;心似平原走马,易放难收;
一起加油老铁们
本文转载自微信公众号「Android开发编程」
Copyright © 2009-2022 www.wtcwzsj.com 青羊区广皓图文设计工作室(个体工商户) 版权所有 蜀ICP备19037934号