Androiod中View的工作原理


个人博客:http://zhangsunyucong.top

前言

这篇文章主要讲解view的工作原理中的三大流程,包括测量流程,布局流程,绘制流程。这些都是自定义控件的基础。下面先对三大流程的职责做简要的概述:

测量流程确定了控件的测量的大小;
布局流程确定了控件在父控件中的四个位置的坐标和控件的实际大小;
绘制流程负责控件的绘制并显示在屏幕上。

view的绘制流程是从哪里开始的?

View的绘制流程是从ViewRoot的performTraversals开始的。在performTraversals经过一堆的逻辑,会分别调用performMeasure,performLayout,performDraw。
然后在view树中,先后调用一下的方法:

performMeasure,measure onMeasure
performLayout,layout, onLayout
performDraw, draw, onDraw, dispatchDraw(绘制子view)

ViewRoot的实现类是ViewRootImpl,在ActivityThread中创建。

MeasureSpec

MeasureSpec是view的测量规格,是一个int数值。在java中的int是32位的,所以MeasureSpec可以利用32位的一个数值来表示view的大少size和规格mode。在ViewRootImpl.java中提供了MeasureSpec组合和分解的方法。MeasureSpec是ViewRootImpl.java中的一个公开静态内部类,源码如下:

ViewRootImpl#MeasureSpec

    public static class 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;

        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);
            }
        }

        ...

        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        ...
    }

在上面,MODE_SHIFT为什么是30?因为它是使用高2位表示mode,低30为表示size。
MODE_MASK为0x3,二进制表示是

0000 0000 0000 0000 0000 0000 0000 0011

它左移30位后为

1100 0000 0000 0000 0000 0000 0000 0000

由MODE_MASK理解组合makeMeasureSpec中的(size & ~MODE_MASK) | (mode & MODE_MASK)

size & ~MODE_MASK,就是

size & ~MODE_MASK = size & 0011 1111 1111 1111 1111 1111 1111 1111size=32时,
100000 & ~MODE_MASK = 100000 & 0011 1111 1111 1111 1111 1111 1111 1111 = 0000 0000 0000 0000 0000 0000 0010 0000 = 32

同理可知mode

最后做与运算,将它们相加

从源码中可以看出,mode有UNSPECIFIED,EXACTLY,AT_MOST。其中UNSPECIFIED等于0,AT_MOST是小于0,EXACTLY等于0。

MeasureSpec的创建和测量流程

MeasureSpec是由父容器的约束和布局参数LayoutParams共同决定的。它在DecorView和普通view中创建也是不一样的。DecorView的父容器是Window,所以DecorView的MeasureSpec由窗口大小和布局参数共同决定的。普通view是由父容器的MeasureSpec和布局参数共同决定的。

(1)在DecorView中

ViewRootImpl#measureHierarchy

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

ViewRootImpl#getRootMeasureSpec
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;
    }

当view设置MATCH_PARENT时,measureSpec的mode是MeasureSpec.EXACTLY,size是windowSize窗口大小。
当view设置WRAP_CONTENT时,measureSpec的mode是MeasureSpec.AT_MOST,size是windowSize窗口大小。
当view设置具体大小时,measureSpec的mode是MeasureSpec.EXACTLY,size是view设置的具体大小。

(2)在普通view中

ViewGroup#measureChildWithMargins

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);
    }

在ViewGroup#getChildMeasureSpec

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

在getChildMeasureSpec中列出了在父容器MeasureSpec和view的布局参数下创建MeasureSpec的各种情况。得到MeasureSpec后,在measureChildWithMargins中将它传递给child的measure方法。在measure方法再传给onMeasure方法。这就是onMeasure方法中两个参数的来源。

View#onMeasure

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

View#setMeasuredDimension中会调用setMeasuredDimensionRaw方法
View#setMeasuredDimensionRaw

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

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

到这里就已经确定的view测量的大小。通过getMeasuredWidth()和getMeasuredHeight()就可以得到它们的值。

View#getSuggestedMinimumWidth

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

这里是说,控件是否设置了背景和最小大小。对应于android:background和android:minWidth。

View#getDefaultSize

    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;
    }

UNSPECIFIED据说一般是用于表示内部正处于测量的状态。在普通view中我们只要关注AT_MOST和EXACTLY。当view设置match_parent和具体大小时,是EXACTLY,wrap_content时是AT_MOST。为什么会是这样?可以看getChildMeasureSpec方法中各种情况。

所以当我们自定义view时,如果不处理AT_MOST情况,即wrap_content时,控件的大少就是父控件的大小。EXACTLY是可以正常被getDefaultSize处理的。

在ViewGroup中是没有重写onMeasure方法的,因为ViewGroup的大小还与ViewGroup的具体的布局特性有关。如LinearLayout和RelativeLayout的onMeasure不一样的。所以自定义ViewGroup时,要重写onMeasure方法。

但是,ViewGroup提供了测量子view的方法的,measureChildren和measureChildWithMargins,measureChild。

ViewGroup#measureChildren

    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);
            }
        }
    }

ViewGroup#measureChild

    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

ViewGroup#measureChildWithMargins

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);
    }

measureChild在measureChildren被循环遍历子view时调用。measureChildren和measureChildWithMargins的区别是,measureChildren是减去父控件的padding,而measureChildWithMargins减去了父控件的padding和view的margin。这直接影响了测量的大小是否包含了padding和margin。也就是view可以设置的最大大小是减去父控件的padding和view的内边距。

综上所述,在view中,就可以确定view的大小,提供了默认的onMeasure方法,但是默认的onMeasure方法不能正确处理AT_MOST(Wrap_content)的情况。在ViewGroup中,因为ViewGroup的具体大小和ViewGroup的布局特性有关,自定义ViewGroup要重写该方法。

布局流程

View#layout

    public void layout(int l, int t, int r, int b) {
        ...

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            ...
        }

        ...
    }

在layout中分别调用了setFrame或者setOpticalFrame和onLayout。

setFrame或者setOpticalFrame中,赋值给mLeft,mTop,mRight,mBottom,确定了view的四个顶点,通过它们的get方法可以得到相应的值。这就确定了view在父控件中的位置坐标和view的宽和高。

View#onLayout是一个空实现。因为view只需要确定自己在父控件的位置即可。onLayout是用于在ViewGroup中确定子view的位置的。而onLayout的实现同样是与具体的ViewGroup的布局特性有关的。需要在自定义ViewGroup实现。

绘制流程

public void draw(Canvas canvas) {
       ...

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);
            ...
        }  
        ...
    }    

draw过程就是主要就是,上面源码所说的那几个步骤。

1、如果需要背景,绘制背景
2、onDraw中,绘制自身
3、dispatchDraw中,绘制子view
4、onDrawForeground中绘制装饰

在自定义ViewGroup时,可以在dispatchDraw中遍历子view进行绘制。

实例

布局

<?xml version="1.0" encoding="utf-8"?>
<com.example.hyj.ht_test.widget.custom.CustomViewGroup
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

   <com.example.hyj.ht_test.widget.custom.CustomViewGroup1
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:padding="10dp"
        android:layout_height="wrap_content">

       <com.example.hyj.ht_test.widget.custom.CustomView
           android:id="@+id/btn"
           android:background="@color/common_color"
           android:layout_margin="10dp"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"/>


    </com.example.hyj.ht_test.widget.custom.CustomViewGroup1>

</com.example.hyj.ht_test.widget.custom.CustomViewGroup>

CustomViewGroup1

public class CustomViewGroup1 extends ViewGroup {

    private Context mContext;

    private static final String TAG = "CustomViewGroup";

    ....构造函数...

    public static class CustomLayoutParams extends MarginLayoutParams {

        public CustomLayoutParams(MarginLayoutParams source) {
            super(source);
        }

        public CustomLayoutParams(android.view.ViewGroup.LayoutParams source) {
            super(source);
        }

        public CustomLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public CustomLayoutParams(int width, int height) {
            super(width, height);
        }
    }

    /**
     * 生成默认的布局参数
     */
    @Override
    protected CustomLayoutParams generateDefaultLayoutParams() {
        return new CustomLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
    }

    /**
     * 生成布局参数
     * 将布局参数包装成我们的
     */
    @Override
    protected android.view.ViewGroup.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams p) {
        return new CustomLayoutParams(p);
    }

    /**
     * 生成布局参数
     * 从属性配置中生成我们的布局参数
     */
    @Override
    public android.view.ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new CustomLayoutParams(getContext(), attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //measureChildren(widthMeasureSpec, heightMeasureSpec);

        int parentWidth = 0;
        int parentHeight = 0;
        int childCount = getChildCount();
        if(childCount > 0) {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                measureChildWithMargins(child, widthMeasureSpec, 0,
                        heightMeasureSpec, 0);
                CustomLayoutParams clp = (CustomLayoutParams) child.getLayoutParams();
                if (child.getVisibility() != View.GONE) {
                    parentWidth = getPaddingLeft() + getPaddingRight() +
                            child.getMeasuredWidth() + clp.leftMargin + clp.rightMargin ;
                    parentHeight = getPaddingTop() + getPaddingBottom() +
                            child.getMeasuredHeight() + clp.topMargin + clp.bottomMargin;
                }
            }
        }
        setMeasuredDimension(parentWidth, parentHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        int childCount = getChildCount();
        int paddingTop = 0;
        if(childCount > 0) {
            for(int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if(child.getVisibility() != View.GONE) {
                    int measuredHeight = child.getMeasuredHeight();
                    int measuredWidth = child.getMeasuredWidth();
                    CustomLayoutParams clp = (CustomLayoutParams) child.getLayoutParams();
                    paddingTop = paddingTop + child.getPaddingTop();
                    child.layout(getPaddingLeft() + clp.leftMargin,
                            getPaddingTop() + clp.topMargin,
                            getPaddingLeft() + clp.leftMargin + measuredWidth,
                            getPaddingTop() + clp.leftMargin + measuredHeight);
                }
            }
        }
    }


}

CustomViewGroup是默认的实现可以

public class CustomView extends View {

    public CustomView(Context context) {
        super(context);
        init();
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getSize(600, widthMeasureSpec),
                getSize(600, heightMeasureSpec));
    }

    private int getSize(int size, int measureSpec) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
        if(specMode != MeasureSpec.EXACTLY) {
            result = size;
        } else {
            result = specSize;
        }
        return result;
    }

}

其实这些代码是《Android的MotionEvent事件分发机制》中用的代码基础上加的.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值