Android View的工作原理

一、ViewRoot 和 DecorView

ViewRoot 

1.ViewRoot的实现类: ViewRootImpl 类

2.作用:连接WindowManager 和 DecorView 的纽带,View 三大流程均是通过ViewRoot来完成的。

当Activity对象被创建完毕后,会将DecorView 添加到Window中,同时会创建ViewRootImpl 对象,并将ViewRootImpl对象和DecorView建立关联。

DecorView 

1.DecorView:继承ViewGroup

2.包含一个LinearLayout,里面有一个title和content。 可以通过ViewGroup content = (ViewGroup)findViewById(android.R.id.content) 获取content, 通过content.getChildAt(0)可以获得设置的View。



二、View的绘制流程

View的绘制流程是从ViewRoot 的 performTraversals方法开始的,它经过measure、layout 和 draw 三个过程才能最终绘制一个View出来。

performTraversals 会依次调用performMeasure 、 performLayout、 performDraw三个方法,这三个方法分别完成顶级View的measure、layout 和 draw 这三大流程。 其中在performMeasure中会调用measure方法,在measure方法中会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。 同理, performLayout 和 performDraw的传递流程和performMeasure 是类似的,唯一不同的是performDraw 的传递过程是在draw方法中通过dispatchDraw来实现的。

measure过程决定了View的宽 和 高,Measure 完成以后, 可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取到View 测量后的宽和高。

layout过程决定了View的四个顶点的坐标和实际的View的宽高,完成以后,可以通过getTop, getBottom, getLeft, getRight来拿到View的四个顶点的位置,并且通过getWidth 和 getHeight 方法来拿到View的最终宽高。

draw过程则决定了View的显示,只有draw方法完成以后View的内容才能在屏幕上呈现。

三、理解MeasureSpec

MeasureSpec很大程度上决定了一个View的尺寸规格,父容器会影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams 根据父容器所施加的规则转换成对应的MeasureSpec, 然后再根据这个MeasureSpec来测量出View的宽高。下面看一下MeasureSpec内部的一些常量定义:

        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {}

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        /**
         * Creates a measure specification based on the supplied size and mode.
         *
         * The mode must always be one of the following:
         * <ul>
         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
         * </ul>
         *
         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
         * implementation was such that the order of arguments did not matter
         * and overflow in either value could impact the resulting MeasureSpec.
         * {@link android.widget.RelativeLayout} was affected by this bug.
         * Apps targeting API levels greater than 17 will get the fixed, more strict
         * behavior.</p>
         *
         * @param size the size of the measure specification
         * @param mode the mode of the measure specification
         * @return the measure specification based on size and mode
         */
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
         * will automatically get a size of 0. Older apps expect this.
         *
         * @hide internal use only for compatibility with system widgets and older apps
         */
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * 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}
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            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);
        }

MeasureSpec 代表一个32 位int 值, 高2位代表SpecMode,指测量模式; 低30 位代表SpecSize, 指在某种测量模式下的规格大小。需要注意的是这里提到的MeasureSpec是指所代表的int值,而非其本身。

SpecMode 有三类,每一类都表示特殊含义:

UNSPECIFIED

父容器不对View有任何限制,要多大给多大,一般用于系统内部。

EXACTLY

父容器已经检测出View所需要的精确大小,View的最终大小是SpecSize所指定的值。 对应于LayoutParams中的match_parent 和 具体的数值这两种模式。

AT_MOST

父容器指定一个可用大小即SpecSize, View 的大小不能大于这个值。 对应于LayoutParams 中的wrap_content.

MeasureSpec 和 LayoutParams的对应关系

对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定; 对于普通View,其MeasureSpec由父容器的MeasureSpec 和自身LayoutParams 来共同决定。

对于DecorView 来说,在ViewRootImpl中的measureHierarchy方法中有如下一段代码,展示了DecorView的MeasureSpec的创建过程。

 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

接着看getRootMeasureSpec方法的实现。

 /**
     * Figures out the measure spec for the root view in a window based on it's
     * layout params.
     *
     * @param windowSize
     *            The available width or height of the window
     *
     * @param rootDimension
     *            The layout params for one dimension (width or height) of the
     *            window.
     *
     * @return The measure spec to use to measure the root view.
     */
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

通过上面的代码,DecorView 的MeasureSpec的产生过程就很明确了,遵循如下规则,根据LayoutParams中的宽高的参数来划分。

LayoutParams.MATCH_PARENT :  精确模式,大小就是窗口的大小

LayoutParams.WRAP_CONTENT : 最大模式,大小不定, 但是不能超过窗口的大小

固定大小: 精确模式, 大小为LayoutParams中指定的大小






二、自定义View

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值