【Android View绘制之旅】Measure过程

1.为什么要进行Measure?

替人做了原本应该做的工作。在写xml的时候,布局参数如 wrap_content,match_parent,weight 等等给我们开发界面的时候带来方便,但是机器可是死的,最终绘制界面的时候需要的是明确数字意义上的宽高数据

总之:measure过程类似执行一套算法,将整个View的宽高值给最终确定下来。

2.Measure从根开始

每个页面的视图结构就像View的树,从根开始产生很多分支,分支又产生分支。
这里写图片描述
最低层的View 是DecorView .然后再到其子组件,然后再衍生下去。

这里写图片描述

3.不得不说的工具类:MeasureSpec

A MeasureSpec encapsulates the layout requirements passed from parent to child。

在父视图组件即将要让子组件测量的时候,并且要传给子组件测量所需要的数据。这些数据都是用这个工具类中的方法合成的,解析的时候同样要用到它。

3.1.数据结构是什么?

  • 外表开起来是一个int型,
  • 0~29位都是size,为宽/高尺寸。
  • 30~31位是mode.

3.2mode的三种类型及含义

3.2.1.EXACTLY

这里写图片描述

3.2.2.AT_MOST

这里写图片描述

3.2.3.UNSPECIFIED

这里写图片描述
场景是什么?
ScrollView中的子View们,他们在高度上是没有限制的, ScrollView在高度上面会赋予他们UNSPECIFIED,自身有多高就显示多高。

4.View测量的第一步:View的measure()方法

在视图界里,万物皆由View衍生。并且它的measure()是final的,所以所有的子类都会原封不动的使用该方法。可以说理解它是View测量过程的第一步。

4.1.measure方法的入参

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

以下是官方解释:

The parent supplies constraint information in the width and height parameters

4.1.1.为什么父View要为子View计算尺寸约束呢?

这些尺寸约束是子测量自身所必要的参数:
1.match_parent,wrap_content并非具体尺寸值。比如子View的布局参数是match_parent ,这并非一个具体的尺寸大小,不让父给传尺寸下来,它自己是无法得到. 见【表一】,很多场景下子View自身size就是parentSize
2.mode模式也是需要结合父View的布局模式及子View的一起来决定的,比如子View的布局参数是match_parent , 如果父View的布局Mode是EXACTLY ,子View的也就是EXACTLY ,如果父View的布局Mode是AT_MOST, 则子View也是AT_MOST。 见【表一】

4.1.2.把父的参数传给子不就得了,为什么不让子View自己来计算这些尺寸约束呢?

这个其实就是代码设计的一种思路了,这个主要看看开发这部分功能跟父View牵涉的多一些还是跟子View多一些。交给子View的是一种尺寸约束(有的时候并不是最终很明确的尺寸数据,比如在AT_MOST时),父View这里起到类似资源分配者的作用,它需要根据自己的空间资源情况及子View的诉求来得出来约束值。那么父View一方面需要了解自身的padding ,marging,布局参数以及其他子View已占用的空间,显然牵涉父View的内容会更多一些。

4.1.3.父View下发的尺寸约束值,子View为之奈何?

场景一。如ViewGroup,如其布局参数为wrap_content,那么他在计算得到自己的尺寸后,是不能超过父View的尺寸限制值,这就是限制值的威力。
场景二。同样上面的ViewGroup,如其布局参数为match_parent,那么它的尺寸是定死了的,就是父View赋予的尺寸。

4.2.measure方法里基本上干了什么?

下面是绝大部分代码:
这里写图片描述

总体来说很简单,但是需要将几个重要的角色了解清楚

4.2.1.mMeasureCache

  • 它是一个key是Long,值也是Long的Map.

  • key的前32位是入参widthMeasureSpec,低32位是入参heightMeasureSpec。其代表了measure过程的输入

  • value的前32位测量出来的结果mMeasuredWidth,低32位是mMeasuredHeight。其代表了measure过程的输出

那么mMeasureCache的使命是?
如果某种测量曾经出现过了(入参和之前的相同),那么就不用再折腾了,测量结果在内存里还存着呢,毕竟测量工作还是很耗费资源的。直接从map里取出测量结果然后调用setMeasuredDimensionRaw设置即可。问题: 那对于ViewGroup怎么办呢?

4.2.2.mOldWidthMeasureSpec

上次measure()方法的入参。
它的使命就是: 防止重复测量,对于和上次相同的入参就没必要再测量了。

小结

measure()方法本身没有多少和测量相关的实质操作,其一堆代码的作用就是“防止重复测量”,而测量的核心工作在onMeasure()里做的。

5.测量的核心一步:onMeasure()方法

onMeasure()里进行了实际的测量工作,View的子类一般都会对该方法进行覆写。
该从哪里开始呢?View树结构中最基层的根部就是一个垂直方向的LinearLayout,那我们就从他开始吧。

5.1.LinearLayout

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
}

我们研究垂直方向就从measureVertical方法开始吧,measureVertical的代码非常的长!

主体上有两次对其包含的子组件的遍历及测量 那为什么会有两次测量呢?

5.1.1.第一次是正常的对子组件进行测量。

这里写图片描述

看看红框的条件,在条件满足的时候的子组件是不执行第一次测量的。

5.1.2.第二次是在有权重设置的时候对子组件根据权重值大小的再次分配。

这里写图片描述
看代码块A中条件

  • heightSize是线性布局实际拥有的高度。

  • mTotalLength则是完成对所有子组件测量
    后需要的总高度,

  • delta 就是“实际”和“期望”的差值,这个差值有可能为正,也可能
    为负。

只有满足A中的条件才会进行第二次绘制。

再看看代码块B:

  • childExtra 子组件的weight值

  • weightSum 是线性布局的权重总和,如果没有设置,则会将所有子组件的权重加起来。

share = (childExtra /weightSum)*delta 

上面公式说明,权重分割的不是整个线性布局的高度,而是分割的“实际”和“期望”的差值

代码块C :share会更新到对应子组件的新高度,并对子组件重新测量。

5.2.发散:为什么说应该在onLayout时getMeasuredWidth/Height?

这里写图片描述
在包含权重的线性布局中会测量两次,而第二次的宽高值是经过权重重新分配的,显然如果我们拿第一次的测量结果的话,那就是不对的。所以最好是等待所有的测量结束后,而当进行onLayout时,测量过程都结束了。

6.说了这么多,measure的结果是什么?

换句话说,经过了Measure流程后,产生的影响或输出是什么?我们得到了什么?

Created with Raphaël 2.1.0 ParentView ParentView View View measure() onMeasure() setMeasuredDimension() setMeasuredDimensionRaw()

经过measure流程后,得到的输出如下:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

在View中mMeasuredWidth,mMeasuredHeight相应得到测量的宽高值。

6.1.我们如何获取测量的宽高值呢?

getMeasuredWidth();getMeasuredHeight();

6.2.getMeasuredHeight和getMeasuredWidthAndState的区别是什么?

public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
}

原始的mMeasuredWidth的第24到31位是用来存储状态,这个状态有

  public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;

它的意义是什么呢?
这里写图片描述

如上图,size 为View自身想要的尺寸大小,而specSize是父View所能提供的最大尺寸,现在尴尬的情况是View想要的尺寸大小超过了父View所能提供的最大的尺寸,这个时候只能限定View的尺寸为specSize,结果是该View会显示不正常,因为挤得。现在了解它的作用了吧。

6.2.1.getMeasuredState可以判断ViewGroup中的子View是否挤不下了。

    public final int getMeasuredState() {
        return (mMeasuredWidth&MEASURED_STATE_MASK)
               | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)
               & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
    }

这样做的结果就是总共32位的int结果值,前4个字节用来存储width的测量状态。后4个字节用来存储height的测量状态。

检测有很大限制,那LinearLayout来说,它要求其为wrap_content,并且子View的尺寸是Exactly的超过最大尺寸

7.如何在Activity中正确的拿到某View的测量宽高值呢?

7.1.ViewTreeObserver

 @Override
 protected void onStart() {
        super.onStart();
        final ViewTreeObserver observer = textView.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                observer.removeOnGlobalLayoutListener(this);
                int width = textView.getMeasuredWidth();
                int height = textView.getMeasuredHeight();
         }
    });
}

7.2.post()

 @Override
    protected void onStart() {
        super.onStart();
        textView.post(new Runnable() {
            @Override
            public void run() {
                int width = textView.getMeasuredWidth();
                int height = textView.getMeasuredHeight();
            }
        });
    }

7.3.Activity.onWindowFocusChanged

   @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus) {
            int width = textView.getMeasuredWidth();
            int height = textView.getMeasuredHeight();
        }
    }

表一

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值