• Android中View的工作流程之draw过程


    文章收藏的好句子:没有一帆风顺的人生,只有不服输的坚持。

    ps:文章是基于 Android Api 31来分析源码的。

    目录

    1、View 的 draw 过程

         1、1 View(它不是ViewGroup) 的 draw 过程

              1、1、1 原始 View 的 draw 过程

              1、1、2 具体 View 的 draw 过程

         1、2 ViewGroup 的 draw 过程

    1、View 的 draw 过程

    这里 View 的 draw 过程就是 View 的绘制过程,也就是将 View 显示在屏幕上,这里的 View 是一种是指 ViewGroup,一种是指原始的 View 或者是 View 的子类(更具体的 View)但不是 ViewGroup 。

    1、1 View(它不是ViewGroup)的 draw 过程

    1、1、1 原始 View 的 draw 过程

    假设我们的 xml 布局文件有一个 View 标签,如下所示;

    e165c14da304a6e80033d14e58035241.png

    如果我们把上面的代码运行一下,在屏幕上就会显示一个红色的矩形对不对?,好,我们现在看它的实现过程,我们看一下 View 的 draw(Canvas canvas)方法;

    6d0bc247e857b9655bfd31d1010ba997.png

    看注释1,是绘制 View 的背景,看我们 xml 布局中的 View 标签,是不是添加了一个背景为 #FF0000 的颜色值呢?它代表了红色;注释4表示绘制 View 的装饰,比如前景和滚动条;注释2表示绘制 View 的内容,那我们看看 View 的 onDraw(Canvas canvas)方法的具体实现;

    248f7ba109d3a4de5d03ef3070ad1e23.png

    看到没,View 的 onDraw(Canvas canvas)方法是空实现,也就是 View 标签不绘制内容,如果我们比设置 background 属性的话,我们只能看到一个透明的 View。

    看注释3,它表示绘制 View 的子元素,我们再看 View 的 dispatchDraw(Canvas canvas)方法的具体实现;

    a3e65af447e481c8ed2caeaaabe80377.png

    看到没有,View 的 dispatchDraw(Canvas canvas)方法也是一个空实现,也就是说 View 标签下面是不会存在子元素的。

    1、1、2 具体 View 的 draw 过程

    这里所说的具体 View 是指继承了 View 的子类,常见的系统具体 View 有 TextView、ImageView、EditText 等,下面我们就以 TextView 为例,分析 TextView 的 draw 过程;TextView 没有重写 View 的 draw(Canvas canvas) 方法,确重写了 View 的 onDraw(Canvas canvas)方法,该方法是绘制自己的内容;

    1. @Override
    2. protected void onDraw(Canvas canvas) {
    3. ......
    4. //5、
    5. final int compoundPaddingLeft = getCompoundPaddingLeft();
    6. final int compoundPaddingTop = getCompoundPaddingTop();
    7. final int compoundPaddingRight = getCompoundPaddingRight();
    8. final int compoundPaddingBottom = getCompoundPaddingBottom();
    9. //6、
    10. final int scrollX = mScrollX;
    11. final int scrollY = mScrollY;
    12. //7、
    13. final int right = mRight;
    14. final int left = mLeft;
    15. final int bottom = mBottom;
    16. final int top = mTop;
    17. ......
    18. //8、
    19. final Drawables dr = mDrawables;
    20. if (dr != null) {
    21. /*
    22. * Compound, not extended, because the icon is not clipped
    23. * if the text height is smaller.
    24. */
    25. //9、
    26. int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
    27. //10、
    28. int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
    29. // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
    30. // Make sure to update invalidateDrawable() when changing this code.
    31. if (dr.mShowing[Drawables.LEFT] != null) {
    32. //11、
    33. canvas.save();
    34. ......
    35. //12、
    36. canvas.restore();
    37. }
    38. ......
    39. }
    40. ......
    41. //13、
    42. if (mLayout == null) {
    43. assumeLayout();
    44. }
    45. ......
    46. //14、
    47. if (mHint != null && mText.length() == 0) {
    48. if (mHintTextColor != null) {
    49. color = mCurHintTextColor;
    50. }
    51. layout = mHintLayout;
    52. }
    53. ......
    54. //15、
    55. float clipLeft = compoundPaddingLeft + scrollX;
    56. float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
    57. float clipRight = right - left - getCompoundPaddingRight() + scrollX;
    58. float clipBottom = bottom - top + scrollY
    59. - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
    60. //16、
    61. if (mShadowRadius != 0) {
    62. clipLeft += Math.min(0, mShadowDx - mShadowRadius);
    63. clipRight += Math.max(0, mShadowDx + mShadowRadius);
    64. clipTop += Math.min(0, mShadowDy - mShadowRadius);
    65. clipBottom += Math.max(0, mShadowDy + mShadowRadius);
    66. }
    67. //17、
    68. canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
    69. ......
    70. //18、
    71. if (mEditor != null) {
    72. mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
    73. } else {
    74. layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
    75. }
    76. ......
    77. }

    注释5表示获取上下左右的 padding 值;注释6表示获取 X 轴和 Y轴上的 scroll 值;注释7表示获取上下左右4个顶点的位置;注释8表示如果 Drawables 不为空,那么就绘制 Drawables;注释9表示计算垂直方向的空间;注释10表示计算水平方向的空间;注释11中的 save 方法调用之后,可以对 canvas 进行平移和旋转,确定新的原点然后绘制,等绘制完了之后,可以把原点恢复原状;注释13表示如果 layout 为 null,通过 makeNewLayout 方法,再去获得一个 layout;注释14表示如果当前没有文字,并且设置了 hint 属性,那么就显示 hint 属性的文字;注释15表示计算矩阵的上下左右4个坐标值;注释16表示处理文字阴影;注释17表示在画布中裁剪出刚计算出来的矩阵大小;注释18表示如果是 EditText 的就交给 mEditor 绘制。

    1、2 ViewGroup 的 draw 过程

    Android 中系统自带的 ViewGroup 子类常见的有 RelativeLayout、LinearLayout、GridLayout、TableLayout、FrameLayout 和 Constraint-Layout,这里我们就以 FrameLayout 为例,分析 FrameLayout 的 draw 过程;分析这个 FrameLayout 的 draw 过程之前,我们先看一下 View 中一个很有意思的方法,那就是 setWillNotDraw(boolean willNotDraw) 方法,我们看一下该方法的源码;

    e47ee215732b3adb3dfba0c2ed3c9065.png

    当 willNotDraw 值为 trure 的时候, View 的 setFlags 方法的第一个参数就为 WILL_NOT_DRAW,当 willNotDraw 值为 false 的时候,View 的 setFlags 方法的第一个参数就为 0;那这个 willNotDraw 代表什么含义呢?如果 willNotDraw 为 true 以后,当前这个 View 不需要绘制任何内容,系统会进行相应的优化;默认情况下,View 会将 setFlags 方法的第一个参数置为 0,也就是要绘制当前 View;但是 ViewGroup 会将 View 的 setFlags 方法的第一个参数设置为 WILL_NOT_DRAW,也就是不对这个 ViewGroup 进行绘制,不信的话我们看看 ViewGroup 的其中一个构造方法;

    ae6052ece74bd011134894d09b3b26c7.png

    看注释19,ViewGroup 的其中一个构造方法调用了 ViewGroup 的 initViewGroup 方法,我们往下看 initViewGroup 方法;

    6dda755c674d5925da08f49a8a2d7a61.png

    看到注释20的代码没有,ViewGroup 默认不对自身的内容进行绘制;如果我们的自定义控件继承于 ViewGroup 并需要对自定义的 ViewGroup 进行绘制时,可以在自定义的 ViewGroup 的构造方法中调用 View 的 setWillNot-Draw(boolean willNotDraw)  方法,并将 willNotDraw 参数设为 false。

    好,我们回到分析 FrameLayout 的 draw 过程,我们知道 FrameLayout 和 ViewGroup 都没有重写 draw 方法和 onDraw 方法,只有 ViewGroup 重写了 dispatchDraw(Canvas canvas) 方法,我们看看该方法;

    1. @Override
    2. protected void dispatchDraw(Canvas canvas) {
    3. ......
    4. for (int i = 0; i < childrenCount; i++) {
    5. while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
    6. final View transientChild = mTransientViews.get(transientIndex);
    7. if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
    8. transientChild.getAnimation() != null) {
    9. //21、
    10. more |= drawChild(canvas, transientChild, drawingTime);
    11. }
    12. ......
    13. }
    14. ......
    15. if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
    16. //22、
    17. more |= drawChild(canvas, child, drawingTime);
    18. }
    19. }
    20. while (transientIndex >= 0) {
    21. // there may be additional transient views after the normal views
    22. final View transientChild = mTransientViews.get(transientIndex);
    23. if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
    24. transientChild.getAnimation() != null) {
    25. //23、
    26. more |= drawChild(canvas, transientChild, drawingTime);
    27. }
    28. ......
    29. }
    30. if (preorderedList != null) preorderedList.clear();
    31. // Draw any disappearing views that have animations
    32. if (mDisappearingChildren != null) {
    33. final ArrayList<View> disappearingChildren = mDisappearingChildren;
    34. final int disappearingCount = disappearingChildren.size() - 1;
    35. // Go backwards -- we may delete as animations finish
    36. for (int i = disappearingCount; i >= 0; i--) {
    37. final View child = disappearingChildren.get(i);
    38. //24、
    39. more |= drawChild(canvas, child, drawingTime);
    40. }
    41. }
    42. ......
    43. }

    看到注释21、22、23、24 所在的代码没有,都是调用 ViewGroup 的 drawChild(Canvas canvas, View child, long drawingTime) 方法对不对?那它们有什么区别吗?答案是肯定有的;看注释21的代码,ViewGroup 绘制短暂的子 View;注释22的代码表示绘制普通可见的子视图;注释23的代码表示绘制普通子视图之外可能存在的临时子视图;注释24的代码表示绘制正在变为不可见的有动画的子视图。

    我们往下看 ViewGroup 的 drawChild(Canvas canvas, View child, long drawingTime) 方法;

    c267879a07f40dc0ffa8fc82b25d7fec.png

    这里的 child 就是 FramLayout 的子 View,ViewGroup 的 drawChild(Canvas canvas, View child, long drawingTime) 方法又调用了 View 的 draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法,我们往下看 draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法;

    1. boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    2. ......
    3. if (transformToApply != null
    4. || alpha < 1
    5. || !hasIdentityMatrix()
    6. || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
    7. ......
    8. if (!drawingWithDrawingCache) {
    9. if (drawingWithRenderNode) {
    10. ......
    11. } else {
    12. // Fast path for layouts with no backgrounds
    13. if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
    14. mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    15. //25、
    16. dispatchDraw(canvas);
    17. } else {
    18. //26、
    19. draw(canvas);
    20. }
    21. }
    22. } else if (cache != null) {
    23. ......
    24. }
    25. ......
    26. return more;
    27.  }

    如果 FrameLayout 的子 View 不需要绘制自身(子View)那么就调用子 View 的dispatchDraw 方法,如果 FrameLayout 的子 View 需要绘制自身的内容那么就调用子 View 的 draw(Canvas canvas)方法。

  • 相关阅读:
    ES-OAS-ERP-电子政务-企业信息化
    LeetCode每日一题 搜索插入位置(二分查找)
    Clock Domain Crossing Design & Verification Techniques Using System Verilog 学习
    Postman如何做接口测试
    vue中实现图片懒加载的几种方法
    springboot 集成GRPC
    Beautiful Soup抓取AJAX动态网站的注意事项
    【多线程】线程安全(重点)
    弘辽科技:玩转店铺标签,能让你首页流量快速起爆
    Docker实战-部署GPE微服务的监控体系
  • 原文地址:https://blog.csdn.net/qq_37837621/article/details/125611702