View的工作原理(一):MeasureSpec

measure基本流程

  1. View的绘制流程是从ViewRootperformTraversals方法开始的。它经过measure、layout和draw三个过程才能最终将一个View绘制出来。
    • measure:测量View的宽和高
    • layout:确定View在父容器的位置
    • draw:将View绘制在屏幕上。
  2. performTraversals的工作流程:
    image

    1. 父容器在onMeasure中,会遍历调用子View的measure方法。
    2. 所有子View的measure完成后,才会进入layout过程,其调用过程与measure类似。
    3. layout过程完成后,最后进入draw过程,其过程与measure类似,不过performDraw的传递是在draw方法中通过dispatchDraw方法实现的。
  3. 只有在Measure完成后,才能通过getMeasureWidthgetMeasureHeight获取View测量后的宽高,它们在几乎所有的情况下等同于View的最终宽高,但特殊情况除外。

    • 同理,Layout完成后,才能拿到View的四个顶点的位置,并通过getWidthgetHeight获取View的最终宽和高。
  4. DecorView是顶级View。

    • 一般情况下,它内部会包含一个竖直方向的线性布局,上面是标题栏,下面是内容区。
    • 在Activity中setContentView 所设置的布局文件,实际上是被加到内容区。
    • 内容区的id是可以获得的,其ID是android.R.id.content,对第一个子View就是我们所设置的View。
    • DecorView是一个FramLayout,View层的所有事件都先经过DecorView,然后才逐级分发。

MeasureSpec

  1. 大致看看源码:

    public static class MeasureSpec {
        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}
    
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        public static final int AT_MOST     = 2 << MODE_SHIFT;
    
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << View.MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
    }
    @MeasureSpecMode
    public static int getMode(int measureSpec) {
        //noinspection ResourceType
        return (measureSpec & MODE_MASK);
    }
    
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }

    Android源码中大量使用了位运算,比如Intent的各种FLAG。它们拥有更高的效率和更低的内存开销。

    SpecMode有三类:

    • EXACTLY: layout_width/layout_height指定为具体数值或match_parent,父容器就会指定一个精确的SpecSize。
    • AT_MOSTlayout_width/layout_height指定为wrap_content,父容器将指定一个可用大小的SpecSize,View的大小不能大于这个值。具体的值要看不同View的具体实现。
    • UNSPECIFIED:父容器不对View有任何限制。开发人员在绘制自定义View的时候会用到。
  2. MeasureSpecLayoutParams

    • 针对DecorView,其MeasureSpec由窗口的尺寸和其自身LayoutParams共同决定。

      • LayoutParams.MATCH_PARENT: Window can’t resize. Froce root view to be windowSize. EXACTLY模式,(强制)大小就是窗口的大小。
      • LayoutParams.WRAP_CONTENT: Window can resize. Set max size for root view. AT_MOST模式,将为root view设置最大尺寸,但不能超过窗口大小。
      • 固定大小(比如具体dp值): Window wants to be an exact size.Force root view to be that size. EXACTLY模式,(强制)root view尺寸就是指定的大小。
    • 而普通的View,则是由父容器的MeasureSpec和自身LayoutParams共同决定,此外还与父容器的pading和自身的margin有关。

  3. 普通View的MeasureSpec的创建规则, 查看getChildMeasureSpec源码即可理解:

    父Spec View尺寸 View的SpecSize View的SpecMode
    EXACTLY 固定值>=0 固定值 EXACTLY
    EXACTLY MATCH_PARENT 父SpecSize-padding EXACTLY
    EXACTLY WRAP_CONTENT 父SpecSize-padding AT_MOST
    AT_MOST 固定值>=0 固定值 EXACTLY
    AT_MOST MATCH_PARENT 父SpecSize-padding AT_MOST
    AT_MOST WRAP_CONTENT 父SpecSize-padding AT_MOST
    UNSPECIFIED 固定值>=0 固定值 EXACTLY
    UNSPECIFIED MATCH_PARENT 0 UNSPECIFIED
    UNSPECIFIED WRAP_CONTENT 0 UNSPECIFIED

猜你喜欢

转载自blog.csdn.net/cangely/article/details/80202172