• Android UI 绘制过程浅析(二)onMeasure过程


    前言

      View的绘制过程分为 measure、layout、draw三个步骤,接下来对这三个步骤逐一进行研究。

    measure方法的签名

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

      measure方法用来测量View的尺寸。两个参数widthMeasureSpec/heightMeasureSpec声明了Parent View所能提供的宽/高。

      需要注意的是,widthMeasureSpec/heightMeasureSpec 这两个 int 型的参数是复合参数(compound parameters)。jvm中以32bit来存储int,最高两位表示“mode”,后面30位才是具体的数值。在View.java中有下面两个方法

            /**
             * Extracts the mode from the supplied measure specification.
             *
             * @param measureSpec the measure specification to extract the mode from
             * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
             *         {@link android.view.View.MeasureSpec#AT_MOST} or
             *         {@link android.view.View.MeasureSpec#EXACTLY}
             */
            public static int getMode(int measureSpec) {
                return (measureSpec & MODE_MASK);
            }
    
            /**
             * Extracts the size from the supplied measure specification.
             *
             * @param measureSpec the measure specification to extract the size from
             * @return the size in pixels defined in the supplied measure specification
             */
            public static int getSize(int measureSpec) {
                return (measureSpec & ~MODE_MASK);
            }

      看到了吧,使用位操作&来获取最高两位的mode。2^2=4,这里只有用到了3种,它们分别的含义入下

    • UNSPECIFIED-ParentView对子View没有约束,后者可以是任意大小
    • AT_MOST-ParentView规定了尺寸上限,子View的大小不可以超过这个上限
    • EXACTLY-ParentView规定了子View的宽高,子View只能是这个宽高

      measure方法本身是声明为final的,CustomView必须实现自己的onMeasure方法,以便View.measure对此进行回调。在实现自己的CustomView.onMeasure之前,先来看看View中默认的onMeasure方法。

    onMeasure

      在自定义View中,往往需要重写onMeasure方法,来设置View的measured width/height。要注意的一点是,计算完成后,必须调用setMeasuredDimension(int, int)来设置宽高,不然在上层measure方法会抛出IllegalStateException。自定View计算出的宽/高必须满足最小宽/高(通过getSuggestedMinimumWidth()、getSuggestedMinimumHeight()获取)。当然也可以在自定义View的onMeasure方法中直接调用View.onMeasure,如下

    @Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

      然而,View默认的onMeasure实现逻辑非常简单,恐怕不能满足自定义View的需求。

        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }

      View.onMeasure简单粗暴地调用了setMeasuredDimension来设置measuredWidth/measuredHeight。其中使用的getDefaultSize(int, int)方法如下

        public static int getDefaultSize(int size, int measureSpec) {
            int result = size;
            int specMode = MeasureSpec.getMode(measureSpec);
            int specSize = MeasureSpec.getSize(measureSpec);
    
            switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            }
            return result;
        }

      逻辑很简单,一个switch/case语句,根据SpecMode来判断返回。若为UNSPECIFIED,则返回minimumWidth;若为AT_MOST/EXACTLY,返回SpecWidth。

      到此为止,View的measure过程已经完成了。对于ViewGroup,可以猜想,它的onMeasure方法,是把所有子View的onMeasure做一次相加。我们来看一下具体是不是这样做的。

    ViewGroup.measure

      ViewGroup继承了View类,理所应当地,我们试图在ViewGroup中查找是否重写了onMeasure方法——未果。不过我们惊喜地发现了measureChildren这个方法——用来遍历所有children,对每一个child进行measure

        protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
            final int size = mChildrenCount;
            final View[] children = mChildren;
            for (int i = 0; i < size; ++i) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);
                }
            }
        }

      measureChild中,将ViewGroup的padding加入了计算,最后调用child.measure,这里不予深究,也不再贴出代码。

      ViewGroup.java本身并没有重写onMeasure方法,在它的具体实现中,onMeasure根据具体情况,将children的measured size进行求和处理,具体可以参阅FrameLayout/LiearLayout/RelativeLayout 的代码。

    小结

      对于measure过程的分析就到这里,下一篇我们继续探讨onLayout过程。

  • 相关阅读:
    前端开发神器
    React表单明文密文切换,携带禁止浏览器自动回填,简单验证提示功能
    webapp 虚拟键盘隐藏留下空白解决办法
    jQuery常用表单事件执行顺序
    localStorage+cookie实现存取表单历史记录
    js.cookie.js使用方法
    H5超细边框
    JS删除数组中某个元素
    JS获取地址栏参数(支持中文)
    React书写规范
  • 原文地址:https://www.cnblogs.com/maozhige/p/4761560.html
Copyright © 2020-2023  润新知