Android View 的绘制流程 02 - performM

作者: __Y_Q | 来源:发表于2020-02-19 01:53 被阅读0次

    上一章终于跟踪完了前置流程, 在 ViewRootImpl.performTraversals() 终于看到了熟悉的三个方法.

    • 本章主要学习三个方法中的 preformMeasure 测量方法.
    • 在上一章 performTraversals 方法中我们得到了 DecorView 的 MeasureSpec. 本章也会用到
    • 本章内容会以一个实际例子来学习测量的整体流程.

    下面流程会引起极度不适, 当大家看的特别恶心想吐的时候, 别急, 站起来泡杯咖啡, 或者冲杯茶, 休息一下, 也思考一下. 我也是第一次写类似这样递归的文章, 还请大家体谅.

    performMeasure 总结:
    (为什么总结要写到最开始呢? 因为你们看着看着就会回到这里的 -_-!!)
    measure 的基本核心思想是啥,
    就是父 View 把自己的 MeasureSpec 传给自己的子 View, 然后结合子 View 自己的 LayoutParams 算出子 View 的 MeasureSpec, 一层一层的向下传递, 直到最后一层. 从最后一层开始测量, 测量完后, 再一层一层的向上测量父 View , 直到 DecorView 为止. 在最后这个文集最后一章大总结的时候, 会画出流程图.

    2.preformMeasure

    2.1 ViewRootImpl.performMeasure()

    ViewRootImpl.java 2404 行

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        ...
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ...
    }
    

    还记得这个 mView 是谁吧, 就是从外层一层一层传进来的 DecorView.
    尽管 mView 是 DecorView ( FrameLayout ), 但是由于 measure() 方法是 final 型的. View 的子类都不能重写该方法, 所以点进去之后不是在ViewGroup, 而是 View 类的 measure() 方法, 现在我们进入 View.measure(), 下面的从步骤xx回来, 先不要看.

    从步骤 2.4 回来: 这时候测量已经全部完成. 可以重新看一遍最上面的总结了, 你会深有感悟. 恭喜你, 坚持到了最后


     
     

    2.2 View.measure(int widthMeasureSpec, int heightMeasureSpec)

    View.java 21961 行

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
            ...
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            ...
    }
    

    方法注释翻译

    这个方法被调用, 为了找出 View 有多大, 父布局在宽度和高度参数中提供了约束信息.
    View 的实际测量工作是这个方法调用 onMeasure(int, int) 方法中执行的. 因此, 只有 onMeasure(int,int) 可以而且必须被子类重写.
    (这里应该指的是,ViewGroup的子类必须重写该方法,才能绘制该容器内的子view。如果是自定义一个子控件,extends View,那么并不是必须重写该方法)
    参数 widthMeasureSpec:父布局加入的水平空间要求.
    参数heightMeasureSpec:父布局加入的垂直空间要求.
    (系统将其定义为一个final方法,可见系统不希望整个测量流程框架被修改)

    这里又调用了 onMeasure 方法, .我们所有的控件几乎都会重写 onMeasure方法. 所以当流程走到这里的时候, 会切换到那些重写的 onMeasure 方法中去. 这里就用一个实际的例子学习 measure 的过程.


     
     

    2.3 创建布局文件

    我们随便建立一个布局文件.

    <LinearLayout  id="mycontent"  >
          <TextView id="tv"/>
    </LinearLayout
    

    分析: 结合我们之前学的 setContentView 流程, 以加载的那个 screen_simple 为例, 把这个布局文件添加进去之后, 整个布局如下结构. (为了演示, 我们使用 Activity 的那个流程.) (对 screen_simple 不清楚是什么的朋友, 请转到 Activity.setContentView流程 这篇文章有介绍 )

    结构图

    这张图的主要目的是为了来了解 measure 的时候, 递归 view 的顺序.
    因为 DecorView 是一个 FrameLayout, 所以下面直接进入 FrameLayout 的 measure 方法. 并传入在 上一章1.10 步骤中计算好的 DecorView 的 widthMeasureSpec 和 heightMeasureSpec.. .


     
     

    2.4 FrameLayout.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

    FrameLayout.java 170行

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
            ...
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
      
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (...) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ...
            }
        }
        setMeasuredDimension(...);
        ...
    }
    

    目前层级为: DecorView ( FrameLayout ) --> LinearLayout --> mContentParent ( FrameLayout ) --> mycontent ( LinearLayout ) --> tv ( TextView )

    在 FrameLayout 的 onMeasure() 方法从 DecorView 开始循环测量自己的子 View , 测量完所有的子 View 再来测量自己. 从结构图中可知, DecorView 的子 View 布局文件ID 为 R.layout.screen_simple 的 LinearLayout.
    那么 measureChildWithMargins() 方法参数中的 child 就是 LinearLayout.
    现在进入 measureChildWithMargins() .

    从步骤 2.6 回来: 同理, 跳出循环, 这时候 DecorView 已经测量完了自己下面的子 View 的大小. 开始根据子 View 中的最大高度和最大宽度这两个值, 开始测量自己. 测量完成后, 回到步骤 2.1


     
     

    2.5 ViewGroup.measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

    点击后发现进入了 ViewGroup 的 measureChildWithMargins 方法中.
    ViewGroup.java 6568行

    protected void measureChildWithMargins(View child,
                                          int parentWidthMeasureSpec, int widthUsed,
                                          int parentHeightMeasureSpec, int heightUsed) {
        //第一部分
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //第二部分
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);
        //第三部分
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);
         //第四部分
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    这个方法主要作用就是 先计算计算子 View 的宽高测量模式, 再去根据测量模式去测量自己.
    先来说 这里面5个参数都代表了什么.

    • View child : 这个都知道, 就是一个子View.
    • int parentWidthMeasureSpec : 从名字都可以看出来, 父容器宽度的 widthMeasureSpec. (测量规格)
    • int widthUsed : 父容器已用掉的宽度
    • int parentHeightMeasureSpec : 父容器的 heightMeasureSpec. (测量规格)
    • int heightUsed : 父容器已用掉的高度.

    代码解读.

    第一部分

    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    

    获取子 View 的 LayoutParams, 就是在 XML 文件中 设置的一些属性, 例如 layout_width和layout_height.

     
    第二部分

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                      mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);
    

    可以看到有这里又调用了 getChildMeasureSpec() 方法, 传入了3个参数. 就是根据父 View 宽度的测量规格和自己的左右 Padding 值以及左右 margin 值和已用掉的宽度(widthUsed), 还有自己宽度的值, 来计算自己的宽度测量规格.
    简单来说就是, 获取子 View 的测量规格. .

    getChildMeasureSpec() 方法不在跟进去看了, 感兴趣的朋友可以进去看一下.
    getChildMeasureSpec() 方法最简单的理解方式就是根据父容器的测量模式以及子view的padding, margin, 还有子 view 自己的宽高值, 来构建子 View 的 测量模式(MeasureSpec )

    构建规则如下:

    (根据父布局的宽高模式 MeasureSpec.EXACTLY / MeasureSpec.AT_MOST 设置子布局的宽高模式)
    ( warp_content == AT_MOST, match_parent || fill_parent || 固定值 == EXACTLY)

    • 比如: 父布局的模式是 AT_MOST ,

      • 子布局的 lp.width / lp.height 是固定的值 , -------子布局的 size 就为固定的值, 模式为 EXACTLY
      • 子布局的 lp.width / lp.height 是 match_parent || fill_parent,------- size 就为父布局的大小, 模式为 AT_MOST
      • 子布局的 lp.width / lp.height 是 warp_content, -------size 就为父布局的大小, 模式为 AT_MOST
    • 比如: 父布局的模式是 EXACTLY,

      • 子布局的 lp.width / lp.height 是固定的值 , -------子布局的 size 就为固定的值, 模式为 EXACTLY
      • 子布局的 lp.width / lp.height 是 match_parent || fill_parent,------- size 就为父布局的大小, 模式为 EXACTLY
      • 子布局的 lp.width / lp.height 是 warp_content, -------size 就为父布局的大小, 模式为 AT_MOST

     
    第三部分

    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);
    

    不再描述, 和第二部分相同, 区别就是一个是宽度的测量规格, 一个是高度的测量规格.

     
    第四部分

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    

    到这里, 已经计算出了子 View 的测量规格, childWidthMeasureSpec, childHeightMeasureSpec,
    根据自己的的测量规格, 去测量自己 ( 子 View ) , 也就是说,子 View 有了自己的测量模式, 才能去测量自己.
    如果子 View 是ViewGroup 那还会递归往下测量。

    注:
    这里的 child 是什么?
    根据结构图, 是 DecorView 中的第一个子 View : LinearLayout.
    那也就是说, 下面将要调用的是 LinearLayout 的 onMeasure 方法. (为啥是 onMeasure, 在步骤 2.1 中已经说明)
    跟进去.


     
     

    2.6 LinearLayout.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

    LinearLayout.java 683 行.
    假设是 Vertical 的

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
    
    
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
          ...
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            ...
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                  ...
            if (...) {
                ...
            } else {
                ...
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);
                ...
            }
            ...
        }
        ...
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);
        ...
    }
    
    void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec,int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,heightMeasureSpec, totalHeight);
    }
    

    目前层级为: DecorView ( FrameLayout ) --> LinearLayout --> mContentParent ( FrameLayout ) --> mycontent ( LinearLayout ) --> tv ( TextView )

    根据代码 measureVertical 发现, 又是一个遍历所有的子 View, 然后去测量子 View.
    ( 根据结构图, 发现 LinearLayout 的子View 是 mContentParent, 是一个 FrameLayoue. )
    看来还是要继续调用 measureChildWithMargins, 只是 child 已经变成了结构图中的 mContentParent ( FrameLayout )
    再次进入 ViewGroup.measureChildWithMargins. 跟进.

    从步骤2.8 回来: 同理, 也是跳出循环. 根据子 View 的高度总和与子 View 中的最大宽度 这两个值开始测量自己. 测量完成后, 回到步骤 2.4 也就是最外层 DecorView 的循环.


     
     

    2.7 ViewGroup.measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

    protected void measureChildWithMargins(View child,
                                          int parentWidthMeasureSpec, int widthUsed,
                                          int parentHeightMeasureSpec, int heightUsed) {
        //第一部分
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //第二部分
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);
        //第三部分
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);
         //第四部分
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    与步骤 2.5 一样, 只是参数中的 child 变成了, 结构图中的 mContentParent (FrameLayout), 也是先计算得出 mContentParent 的宽高测量规格, 然后再进行 mContentParent 的测量.
    那么直接进入 mContentParent ( FrameLayout ) 的 onMeasure 方法. (步骤2.1中已说了为什么进入onMeasure)


     
     

    2.8 FrameLayout.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
            ...
        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
      
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (...) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ...
            }
        }
        setMeasuredDimension(...);
        ...
    }
    

    目前层级为: DecorView ( FrameLayout ) --> LinearLayout --> mContentParent ( FrameLayout ) --> mycontent ( LinearLayout ) --> tv ( TextView )

    和步骤 2.4 一样, 只不过 View 变成了结构图中的第三层(从外到内)

    又遍历 mContentParent 中的所有子 View. 又开始挨个调用 measureChildWithMargins, 来测量子 View. 跟进, 继续进 measureChildWithMargins.
    (mContentParent 中的子 View 就是我们在 2.3 中创建的布局文件, mycontent (LinearLayout) )

    从步骤2.10回来: 跳出循环, 同理, 因为 mContentParent ( FrameLayout ) 也只有一个子 View, 在循环的过程中, 也获取了子 View 中的最大高度值和子 View 中的最大宽度值, 根据这子View的这两个值, 然后开始测量自己.
    测量完成后, 会回到步骤 2.6


     
     

    2.9 ViewGroup.measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

    protected void measureChildWithMargins(View child,
                                          int parentWidthMeasureSpec, int widthUsed,
                                          int parentHeightMeasureSpec, int heightUsed) {
        //第一部分
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //第二部分
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);
        //第三部分
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);
         //第四部分
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    步骤 2.5 一样, 只是参数中的 child 变成了, 结构图中的 mycontent (LinearLayout), 也是先计算得出 mycontent 的宽高测量规格, 然后再进行 mycontent 的测量.

    那么直接进入 mycontent ( LinearLayout ) 的 onMeasure 方法. (步骤2.1中已说了为什么进入onMeasure)


     
     

    2.10 LinearLayout.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

    LinearLayout.java 683 行.
    假设是 Vertical 的

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }
    
    
    void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        //所有子View高度和, 在循环中每次都会计算和累加.
        mTotalLength = 0;  
        //所有子View中最大宽度值, 每次循环都会对比来获取一个最大宽度
        int maxWidth = 0;   
        ...
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            ...
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                  ...
            if (...) {
                ...
            } else {
                ...
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,heightMeasureSpec, usedHeight);
                ...
            }
            ...
        }
        ...
        //上面的for循环已经计算完了所有的子Veiw的高度,这里开始计算 LinearLayout 自己的高度
        //也就是在所有子View高度之和的基础之上加上上下Padding
        mTotalLength += mPaddingTop + mPaddingBottom;  
        int heightSize = mTotalLength;
        ...
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        ...
        //LinearLayout总宽度
        maxWidth += mPaddingLeft + mPaddingRight;   
        ...
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState);
        ...
    }
    
    void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec,int totalHeight) {
        measureChildWithMargins(child, widthMeasureSpec, totalWidth,heightMeasureSpec, totalHeight);
    }
    

    目前层级为: DecorView ( FrameLayout ) --> LinearLayout --> mContentParent ( FrameLayout ) -->mycontent ( LinearLayout ) --> tv ( TextView )

    和步骤2.6 相同, 只是当前 View 对象为 mycontent
    循环遍历 mycontent 下的所有子 View. 继续调用 measureChildWithMargins , 传入的 child 为 textView, 跟进, 继续进入 measureChildWithMargins.
    (mycontent 中只有一个View 就是 tv (textView)) .

    从步骤 2.12 回来: 跳出循环, 此时已经计算好了子 View 的高度累加, 已经获取了所有子 View 的最大的一个宽度值. 开始设置自己的宽高值. 现在 mycontent 也测量完了, 同理也会回到步骤 2.8 中.


     
     

    2.11 ViewGroup.measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

    protected void measureChildWithMargins(View child,
                                          int parentWidthMeasureSpec, int widthUsed,
                                          int parentHeightMeasureSpec, int heightUsed) {
        //第一部分
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //第二部分
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);
        //第三部分
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);
         //第四部分
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    步骤 2.5 一样, 只是参数中的 child 变成了, 结构图中的 tv ( textView ), 也是先计算得出 tv的宽高测量规格, 然后再进行 tv 的测量.
    ( 终于不用再继续嵌套了, 快吐了....., 所以大家想想, 如果我们布局有很多层级的话, 单一个测量就有多费劲了.)
    我们直接进入 TextView.onMesure 方法.
    (最后说一遍, 因为 measure 是 final 类型的, 无法被继承, 只有去 View 中看 measure, 而 View 中的 measure 又调用了当前 View 的 onMeasure)


     
     

    2.12 TextView.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

    TextView.java 9243 行

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        ...
        setMeasuredDimension(width, height);
    }
    

    目前层级为: DecorView ( FrameLayout ) --> LinearLayout --> mContentParent ( FrameLayout ) -->mycontent ( LinearLayout ) --> tv ( TextView )

    别的暂时都先不需要关心, 看到最后一行了吗?
    还记得我们自定义View 的时候, 重写 onMeasure, 测量完后. 都会调用 setMeasuredDimension. 这时候 textView 就有宽高值了.

    现在 View 已经递归到最后一层了. 也计算完了最后一层 View 的大小了, 开始向上传递值来测量父 View 的大小了.
    首先步骤 2.12 执行完后, 会回到 2.10,
    现在把界面滚动到 步骤 2.10. 看步骤 2.10 中下面 "从步骤2.12 回来" , 这几个字这里开始看.
    ( 因为 mycontent 中就一个子View (tv), 所以循环一遍就结束了, 现在流程回到步骤 2.10 )


    相关文章

      网友评论

        本文标题:Android View 的绘制流程 02 - performM

        本文链接:https://www.haomeiwen.com/subject/tndrfhtx.html