博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android中draw过程分析 (结合Android 4.0.4 最新源码)
阅读量:4222 次
发布时间:2019-05-26

本文共 5022 字,大约阅读时间需要 16 分钟。

分类:
6038人阅读
(5)

      经过对View树的measure和layout过程后,接下来将结合前两步得到的结果对View树进行绘制,之前以为measure过程是measure、layout和draw三部曲中最复杂的一步,在仔细分析draw过程后才发现自己之前的论断有失准确性。不过从整体来看,draw过程的逻辑是比较清晰的,和measure和layout过程十分相似,而本文将从整体来介绍draw的整个流程,至于其中的细节可能会在另外一篇文章中介绍。

      和measure和layout一样,draw过程也是在ViewRoot的performTraversals()的内部发起的,其调用顺序在measure()和layout()之后,同样的,performTraversals()发起的draw过程最终会调用到mView的draw()函数,这里的mView对于Actiity来说就是PhoneWindow.DecorView。

      首先来看下与draw过程相关的函数,之所以先列出相关函数,目的是让大家对这些函数有个印象,以免在介绍具体细节的时候茫然:

  • ViewRootImpl.draw(),仅在ViewRootImpl.performTraversals()的内部调用
  • DecorView.draw(), 上一步中的ViewRootImpl.draw()会调用到该函数,DecorView.draw()继承自Framelayout,DecorView和FrameLayout,以及FrameLayout的父类ViewGroup都未重载draw(),而ViewGroup的父类是View类,因此DecorView.draw()调用的是View.draw()的默认实现
  • View.onDraw(),绘制View本身,自定义View往往会重载该函数来绘制View本身的内容
  • View.dispatchDraw(), View中的dispatchDraw默认是空实现,ViewGroup重载了该函数,内部会循环调用View.drawChild()来发起对子视图的绘制,应用程序不应该重载ViewGroup,因为该函数的默认实现代表了View的绘制流程
  • ViewGroup.drawChild(),该函数只在ViewGroup中实现,原因就是只有ViewGroup才需要绘制child,drawChild内部又会调用View.draw()函数来完成子视图的绘制(有可能直接调用dispatchDraw)
      下面笔者将从源码来展现draw的整体过程,当然源码中只展现关键部分,细节部分将在下一篇文章中具体介绍,首先来看performTraversals(),因为draw过程最先是从这里发起的:
[java]
  1. private void performTraversals() {  
  2.     final View host = mView;  
  3.     ...  
  4.     host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  5.     ...  
  6.     host.layout(00, host.getMeasuredWidth(), host.getMeasuredHeight());  
  7.     ...  
  8.     draw(fullRedrawNeeded);  
  9. }  
      注意到measure和layout过程直接调用的是mView的measure和layout函数,而draw调用的是ViewRootImpl的内部draw(boolean fullRedrawNeeded)函数,再由draw(boolean fullRedrawNeeded)函数来调用mView.draw()函数,draw(boolean fullRedrawNeeded)包含draw过程的一些前期处理,通过下面的代码可以看出调用关系:
[java]
  1. private void draw(boolean fullRedrawNeeded) {  
  2.         Surface surface = mSurface;  
  3.         if (surface == null || !surface.isValid()) {  
  4.             return;  
  5.         }  
  6.     ...  
  7.         try {  
  8.          canvas.translate(0, -yoff);  
  9.          if (mTranslator != null) {  
  10.          <span style="white-space:pre"> </span>mTranslator.translateCanvas(canvas);  
  11.          }  
  12.          canvas.setScreenDensity(scalingRequired  
  13.                    ? DisplayMetrics.DENSITY_DEVICE : 0);  
  14.          mAttachInfo.mSetIgnoreDirtyState = false;  
  15.          mView.draw(canvas);  
  16.        } finally {  
  17.         if (!mAttachInfo.mSetIgnoreDirtyState) {  
  18.         // Only clear the flag if it was not set during the mView.draw() call  
  19.         mAttachInfo.mIgnoreDirtyState = false;  
  20.          }  
  21.       }  
  22.     ...  
  23. }  
      下面将转到mView.draw(),之前提到mView.draw()调用的就是View.java的默认实现,View类中的draw函数体现了View绘制的核心流程,因此我们下面重点来看下View.java中draw的调用流程:
[java]
  1. public void draw(Canvas canvas) {  
  2.     ...  
  3.         /* 
  4.          * Draw traversal performs several drawing steps which must be executed 
  5.          * in the appropriate order: 
  6.          * 
  7.          *      1. Draw the background 
  8.          *      2. If necessary, save the canvas' layers to prepare for fading 
  9.          *      3. Draw view's content 
  10.          *      4. Draw children 
  11.          *      5. If necessary, draw the fading edges and restore layers 
  12.          *      6. Draw decorations (scrollbars for instance) 
  13.          */  
  14.   
  15.         // Step 1, draw the background, if needed  
  16.     ...  
  17.         background.draw(canvas);  
  18.     ...  
  19.         // skip step 2 & 5 if possible (common case)  
  20.     ...  
  21.         // Step 2, save the canvas' layers  
  22.     ...  
  23.         if (solidColor == 0) {  
  24.             final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;  
  25.   
  26.             if (drawTop) {  
  27.                 canvas.saveLayer(left, top, right, top + length, null, flags);  
  28.             }  
  29.     ...  
  30.         // Step 3, draw the content  
  31.         if (!dirtyOpaque) onDraw(canvas);  
  32.   
  33.         // Step 4, draw the children  
  34.         dispatchDraw(canvas);  
  35.   
  36.         // Step 5, draw the fade effect and restore layers  
  37.   
  38.         if (drawTop) {  
  39.             matrix.setScale(1, fadeHeight * topFadeStrength);  
  40.             matrix.postTranslate(left, top);  
  41.             fade.setLocalMatrix(matrix);  
  42.             canvas.drawRect(left, top, right, top + length, p);  
  43.         }  
  44.     ...  
  45.         // Step 6, draw decorations (scrollbars)  
  46.         onDrawScrollBars(canvas);  
  47.     }  
      官方源码中对View的绘制流程给了非常详细的注释,笔者将整个绘制过程的关键步骤抽取出来方便了解整个过程,通过阅读上面的代码可以知道整个绘制过程包括View的背景绘制,View本身内容的绘制,子视图的绘制(如果包含子视图),渐变框的绘制以及滚动条的绘制。
      我们重点要关注的是View本身内容的绘制和子视图的绘制,即onDraw()和dispatchDraw()函数。
      对于View.java和ViewGroup.java,onDraw()默认都是空实现,因为具体View本身长什么样子是由View的设计者来决定的,默认不显示任何东西。
      View.java中dispatchDraw()默认为空实现,因为其不包含子视图,而ViewGroup重载了dispatchDraw()来对其子视图进行绘制,通常应用程序不应该对dispatchDraw()进行重载,其默认实现体现了View系统绘制的流程。那么,接下来我们继续分析下ViewGroup中dispatchDraw()的具体流程:
[java]
  1. @Override  
  2.     protected void dispatchDraw(Canvas canvas) {  
  3.        ...  
  4.   
  5.         if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {  
  6.             for (int i = 0; i < count; i++) {  
  7.                 final View child = children[i];  
  8.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  9.                     more |= drawChild(canvas, child, drawingTime);  
  10.                 }  
  11.             }  
  12.         } else {  
  13.             for (int i = 0; i < count; i++) {  
  14.                 final View child = children[getChildDrawingOrder(count, i)];  
  15.                 if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  16.                     more |= drawChild(canvas, child, drawingTime);  
  17.                 }  
  18.             }  
  19.         }  
  20.       ......  
  21.     }  
      dispatchDraw()的核心代码就是通过for循环调用drawChild()对ViewGroup的每个子视图进行绘制,上述代码中如果FLAG_USE_CHILD_DRAWING_ORDER为true,则子视图的绘制顺序通过getChildDrawingOrder来决定,默认的绘制顺序即是子视图加入ViewGroup的顺序,而我们可以重载getChildDrawingOrder函数来更改默认的绘制顺序,这会影响到子视图之间的重叠关系。
      drawChild()的核心过程就是为子视图分配合适的cavas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用子视图的draw()函数进行具体的绘制,如果子视图的包含SKIP_DRAW标识,那么仅调用dispatchDraw(),即跳过子视图本身的绘制,但要绘制视图可能包含的字视图。
      完成了dispatchDraw()过程后,View系统会调用onDrawScrollBars()来绘制滚动条,滚动条绘制的细节这里就不展开介绍了。
      总结:以上文字着重于对draw整体流程的介绍,通过对关键函数以及它们之间的调用关系来展示draw的内部过程,从整体上把握整个过程将更有利于后面对细节的理解和分析。

转载地址:http://moomi.baihongyu.com/

你可能感兴趣的文章
面向自动驾驶车辆验证的抽象仿真场景生成
查看>>
一种应用于GPS反欺骗的基于MLE的RAIM改进方法
查看>>
自动驾驶汽车CAN总线数字孪生建模(二)
查看>>
自动驾驶汽车GPS系统数字孪生建模(一)
查看>>
自动驾驶汽车GPS系统数字孪生建模(二)
查看>>
上海控安入选首批工控安全防护能力贯标咨询机构名单
查看>>
自动驾驶汽车传感器数字孪生建模(一)
查看>>
CUDA 学习(四)、线程
查看>>
CUDA 学习(五)、线程块
查看>>
CUDA 学习(八)、线程块调度
查看>>
CUDA 学习(九)、CUDA 内存
查看>>
CUDA 学习(十一)、共享内存
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十四章 生化尖兵
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十五章 超级马里奥64
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十七章 游戏感的原理
查看>>
游戏感:虚拟感觉的游戏设计师指南——第十八章 我想做的游戏
查看>>
游戏设计的艺术:一本透镜的书——第十章 某些元素是游戏机制
查看>>
游戏设计的艺术:一本透镜的书——第十一章 游戏机制必须平衡
查看>>
游戏设计的艺术:一本透镜的书——第十二章 游戏机制支撑谜题
查看>>
游戏设计的艺术:一本透镜的书——第十三章 玩家通过界面玩游戏
查看>>