Android源码进阶之深入理解View的绘制流程(Draw)机制

开发 前端
三大工作流程始于ViewRootImpl#performTraversals,在这个方法内部会分别调用performMeasure,performLayout,performDraw三个方法来分别完成测量,布局,绘制流程。那么我们现在先从performDraw方法看起。

[[426758]]

前言

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

测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么由绘制流程完成;

那我们就开始开车了;

一、performDraw

三大工作流程始于ViewRootImpl#performTraversals,在这个方法内部会分别调用performMeasure,performLayout,performDraw三个方法来分别完成测量,布局,绘制流程。那么我们现在先从performDraw方法看起;

performDraw

  1. private void performDraw() { 
  2.     //... 
  3.     final boolean fullRedrawNeeded = mFullRedrawNeeded; 
  4.     try { 
  5.         draw(fullRedrawNeeded); 
  6.     } finally { 
  7.         mIsDrawing = false
  8.         Trace.traceEnd(Trace.TRACE_TAG_VIEW); 
  9.     } 

里面又调用了ViewRootImpl#draw方法,我们来看看ViewRootImpl#draw:

  1. private void draw(boolean fullRedrawNeeded) { 
  2.     ... 
  3.     //获取mDirty,该值表示需要重绘的区域 
  4.     final Rect dirty = mDirty; 
  5.     if (mSurfaceHolder != null) { 
  6.         // The app owns the surface, we won't draw. 
  7.         dirty.setEmpty(); 
  8.         if (animating) { 
  9.             if (mScroller != null) { 
  10.                 mScroller.abortAnimation(); 
  11.             } 
  12.             disposeResizeBuffer(); 
  13.         } 
  14.         return
  15.     } 
  16.     //如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制 
  17.     //第一次绘制流程,需要绘制所有视图 
  18.     if (fullRedrawNeeded) { 
  19.         mAttachInfo.mIgnoreDirtyState = true
  20.         dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); 
  21.     } 
  22.     //... 
  23.     if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { 
  24.                 return
  25.         } 

根据fullRedrawNeeded来判断是否需要重置dirty区域,最后调用了ViewRootImpl#drawSoftware方法,并把相关参数传递进去,包括dirty区域,我们接着看该方法的源码;

  1. private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, 
  2.             boolean scalingRequired, Rect dirty) { 
  3.     // Draw with software renderer. 
  4.     final Canvas canvas; 
  5.     try { 
  6.         final int left = dirty.left
  7.         final int top = dirty.top
  8.         final int right = dirty.right
  9.         final int bottom = dirty.bottom; 
  10.         //锁定canvas区域,由dirty区域决定 
  11.         canvas = mSurface.lockCanvas(dirty); 
  12.         // The dirty rectangle can be modified by Surface.lockCanvas() 
  13.         //noinspection ConstantConditions 
  14.         if (left != dirty.left || top != dirty.top || right != dirty.right 
  15.                 || bottom != dirty.bottom) { 
  16.             attachInfo.mIgnoreDirtyState = true
  17.         } 
  18.         canvas.setDensity(mDensity); 
  19.     }  
  20.     try { 
  21.         if (!canvas.isOpaque() || yoff != 0 || xoff != 0) { 
  22.             canvas.drawColor(0, PorterDuff.Mode.CLEAR); 
  23.         } 
  24.         dirty.setEmpty(); 
  25.         mIsAnimating = false
  26.         attachInfo.mDrawingTime = SystemClock.uptimeMillis(); 
  27.         mView.mPrivateFlags |= View.PFLAG_DRAWN; 
  28.         try { 
  29.             canvas.translate(-xoff, -yoff); 
  30.             if (mTranslator != null) { 
  31.                 mTranslator.translateCanvas(canvas); 
  32.             } 
  33.             canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); 
  34.             attachInfo.mSetIgnoreDirtyState = false
  35.             //正式开始绘制 
  36.             mView.draw(canvas); 
  37.         } 
  38.     }  
  39.     return true

实例化了Canvas对象,然后锁定该canvas的区域,由dirty区域决定,接着对canvas进行一系列的属性赋值,最后调用了mView.draw(canvas)方法,

mView就是DecorView,也就是说从DecorView开始绘制;

二、draw源码详解

由于ViewGroup没有重写draw方法,因此所有的View都是调用View#draw方法,因此,我们直接看它的源码

  1. public void draw(Canvas canvas) { 
  2.     ....  
  3.     // 1. 绘制本身View背景 
  4.     if (!dirtyOpaque) { 
  5.         drawBackground(canvas); 
  6.     } 
  7.     if (!verticalEdges && !horizontalEdges) { 
  8.         // Step 3, draw the content 
  9.         // 2. 绘制内容,默认空实现 需复写 
  10.         if (!dirtyOpaque) onDraw(canvas); 
  11.         // 3. 绘制 children  
  12.         dispatchDraw(canvas); 
  13.         drawAutofilledHighlight(canvas); 
  14.         // 4. 分发Draw (单一View空实现,ViewGroup见下面分析) 
  15.         if (mOverlay != null && !mOverlay.isEmpty()) { 
  16.             mOverlay.getOverlayView().dispatchDraw(canvas); 
  17.         } 
  18.         // 5. 绘制装饰 (前景色,滚动条) 
  19.         onDrawForeground(canvas); 
  20.         return
  21.     } 
  22.     .... 

可以看到,draw过程比较复杂,但是逻辑十分清晰。首先来看一开始的标记位dirtyOpaque,

该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,比如绘制背景、绘制内容等;

绘制流程的五个步骤:

  • 对View的背景进行绘制;
  • 绘制View的内容;
  • 对View的子View进行绘制(如果有子View);
  • 分发Draw;

绘制View的装饰(例如:前景色,滚动条);

1、绘制背景

  1. //绘制背景 
  2. private void drawBackground(Canvas canvas) { 
  3.     final Drawable background = mBackground; 
  4.     if (background == null) { 
  5.         return
  6.     } 
  7.     // 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界 
  8.     setBackgroundBounds(); 
  9.     // 先尝试用HWUI绘制 
  10.     if (canvas.isHardwareAccelerated() && mAttachInfo != null 
  11.             && mAttachInfo.mThreadedRenderer != null) { 
  12.         mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode); 
  13.         final RenderNode renderNode = mBackgroundRenderNode; 
  14.         if (renderNode != null && renderNode.isValid()) { 
  15.             setBackgroundRenderNodeProperties(renderNode); 
  16.             ((DisplayListCanvas) canvas).drawRenderNode(renderNode); 
  17.             return
  18.         } 
  19.     } 
  20.     final int scrollX = mScrollX; 
  21.     final int scrollY = mScrollY; 
  22.     if ((scrollX | scrollY) == 0) { 
  23.         //调用 Drawable 的 draw 方法来进行背景的绘制 
  24.         background.draw(canvas); 
  25.     } else { 
  26.         // 若 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移平移画布 
  27.         canvas.translate(scrollX, scrollY); 
  28.         //调用 Drawable 的 draw 方法来进行背景的绘制 
  29.         background.draw(canvas); 
  30.         canvas.translate(-scrollX, -scrollY); 
  31.     } 

2、绘制View的内容

  1. // 绘制View本身内容,空实现,子类必须复写 
  2. protected void onDraw(Canvas canvas) { 

这里调用了View#onDraw方法,View中该方法是一个空实现,因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现;

3、子View进行绘制

当前的View是一个ViewGroup类型,那么就需要绘制它的子View,这里调用了dispatchDraw,而View中该方法是空实现,实际是ViewGroup重写了这个方法,那么我们来看看;

  1. @Override 
  2. protected void dispatchDraw(Canvas canvas) { 
  3.     ... 
  4.     //  遍历子View 
  5.     final int childrenCount = mChildrenCount; 
  6.     ... 
  7.     for (int i = 0; i < childrenCount; i++) { 
  8.         while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { 
  9.             final View transientChild = mTransientViews.get(transientIndex); 
  10.             if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || 
  11.                     transientChild.getAnimation() != null) { 
  12.                 more |= drawChild(canvas, transientChild, drawingTime); 
  13.             } 
  14.             transientIndex++; 
  15.             if (transientIndex >= transientCount) { 
  16.                 transientIndex = -1; 
  17.             } 
  18.         } 
  19.         final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); 
  20.         final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); 
  21.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { 
  22.             // 调用 drawChild 方法,进行子元素绘制 
  23.             more |= drawChild(canvas, child, drawingTime); 
  24.         } 
  25.     } 
  26.     .... 

4、分发Draw

  1. @Override 
  2. protected void dispatchDraw(Canvas canvas) { 
  3.     ... 
  4.     // 1. 遍历子View 
  5.     final int childrenCount = mChildrenCount; 
  6.     ... 
  7.     for (int i = 0; i < childrenCount; i++) { 
  8.         while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { 
  9.             final View transientChild = mTransientViews.get(transientIndex); 
  10.             if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || 
  11.                     transientChild.getAnimation() != null) { 
  12.                 more |= drawChild(canvas, transientChild, drawingTime); 
  13.             } 
  14.             transientIndex++; 
  15.             if (transientIndex >= transientCount) { 
  16.                 transientIndex = -1; 
  17.             } 
  18.         } 
  19.         final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); 
  20.         final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); 
  21.         if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { 
  22.             // 调用 drawChild 方法,进行子元素绘制 
  23.             more |= drawChild(canvas, child, drawingTime); 
  24.         } 
  25.     } 
  26.     .... 

5、绘制View

所谓的绘制装饰,就是指View除了背景、内容、子View的其余部分,例如滚动条等,我们看View#onDrawForeground

  1. public void onDrawForeground(Canvas canvas) { 
  2.     //绘制指示器 
  3.     onDrawScrollIndicators(canvas); 
  4.     //绘制滚动条 
  5.     onDrawScrollBars(canvas); 
  6.     final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null
  7.     if (foreground != null) { 
  8.         if (mForegroundInfo.mBoundsChanged) { 
  9.             mForegroundInfo.mBoundsChanged = false
  10.             final Rect selfBounds = mForegroundInfo.mSelfBounds; 
  11.             final Rect overlayBounds = mForegroundInfo.mOverlayBounds; 
  12.             if (mForegroundInfo.mInsidePadding) { 
  13.                 selfBounds.set(0, 0, getWidth(), getHeight()); 
  14.             } else { 
  15.                 selfBounds.set(getPaddingLeft(), getPaddingTop(), 
  16.                         getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); 
  17.             } 
  18.             final int ld = getLayoutDirection(); 
  19.             Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(), 
  20.                     foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld); 
  21.             foreground.setBounds(overlayBounds); 
  22.         } 
  23.         //调用 Drawable 的 draw 方法,绘制前景色 
  24.         foreground.draw(canvas); 
  25.     } 

到目前为止,View的绘制流程也讲述完毕了;

总结

其实绘制这块还是很重要的,下次还是要继续讲解下;

学如逆水行舟,不进则退;心似平原走马,易放难收;

一起加油老铁们

本文转载自微信公众号「Android开发编程」

【编辑推荐】

 

责任编辑:姜华 来源: Android开发编程
相关推荐

2021-09-16 06:44:04

Android进阶流程

2021-10-15 09:19:17

AndroidSharedPrefe分析源码

2021-09-17 06:55:50

AndroidLayoutView

2021-09-08 06:51:52

AndroidRetrofit原理

2021-08-24 07:53:28

AndroidActivity生命周期

2021-09-15 07:31:33

Android窗口管理

2021-09-24 08:10:40

Java 语言 Java 基础

2021-09-10 07:31:54

AndroidAppStartup原理

2021-09-18 06:56:01

JavaCAS机制

2017-05-03 17:00:16

Android渲染机制

2021-08-17 13:41:11

AndroidView事件

2022-10-11 07:43:34

AndroidSyncGradle 构建

2014-07-15 17:17:31

AdapterAndroid

2017-01-13 22:42:15

iosswift

2017-08-08 09:15:41

前端JavaScript页面渲染

2017-07-12 14:58:21

AndroidInstant Run

2016-10-26 20:49:24

ReactJavascript前端

2021-02-17 11:25:33

前端JavaScriptthis

2023-10-13 13:30:00

MySQL锁机制

2011-07-18 14:38:44

子查询外部查询
点赞
收藏

51CTO技术栈公众号