Android自定义View基础——View的工作原理

在了解View工作原理之前,需要先了解一些基础概念:


Android自定义View基础——View的工作原理_第1张图片
Activity层次图.png
  • DecorView:顶层视图,将要显示的具体内容呈现在PhoneWindow上,是当前Activity所有View的根节点。
  • ViewRootImpl:连接WindowManager和DecorView的纽带,View的绘制流程正是从通过ViewRootImpl来完成的。

(图片来源自:Android窗口机制)

一、View的绘制流程是从哪里开始

这可能得从Android的启动过程说起,但是此处不深究Android的启动过程,只是抽丝剥茧的取出其中与View的绘制过程关系较为直接的来说:
当Activity初始化Window和将布局添加到DecorView之后,ActivityThread类会调用handleResumeActivity会通过WindowManager将DecorView视图添加到窗口上,大致调用过程为:

ActivityThread.handleResumeActivity()
| WindowManager.addView()
|| WindowManagerGlobal.addView()
||| ViewRootImpl.setView() -> requestLayout()  -> scheduleTraversals()
|||| TraversalRunnable.run() -> ViewRootImpl.doTraversal()
||||| ViewRootImpl.performTraversals()

关键点就是ViewRootImpl.performTraversals(),展开一看究竟

private void performTraversals(){
    ......
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ......
    performDraw();
    ......
}

从上面的代码可以看出,performTraversals()依次调用了performMeasure、performLayout 和 performDraw方法,正是测量、布局、绘制。所以,View的绘制流程是从ViewRootImpl的performTraversals开始的。
performMeasure()会调用顶级View的measure()方法,measure又去调用onMeasure(),onMeasure中会对所有子元素进行measure过程;接着子元素会重复父容器的measure过程,如此反复至完成整个View树的遍历。layout和draw同理。

Android自定义View基础——View的工作原理_第2张图片
View绘制流程.png

二、View绘制流程之measure

1. 理解MeasureSpec

在搞清楚measure过程之前,得先明白MeasureSpec的概念,因为在measure过程中,MeasureSpec将会贯穿其中。

MeasureSpec是一个32位int值,高2位是SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)

SpecMode的3种模式:

  • UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部。
  • EXACTLY(精确模式):父容器已测出View所需要的精确大小,View的尺寸就是父容器给的确切值。对应LyaoutParams中的match_parent或具体数值。
  • AT_MOST(最大模式):父容器给出可用大小,View的大小不能超过该值,具体要看View的具体实现。对应LayoutParams中的wrap_content。

普通View的MeasureSpec的创建规则对照表:

parentSpecMode / chlidLayoutParams EXACTLY AT_MOST UNSPECIFIED
dp/px EXACTLY childSize EXACTLY childSize EXACTLY childSize
match_parent EXACTLY parentSize AT_MOST parentSize UNSPECIFIED 0
wrap_content AT_MOST parentSize AT_MOST parentSize UNSPECIFIED 0

其中竖列为chlidLayoutParams,横为parentSpecMode。
表格的推演由来,可参考:ViewGrop.getChildMeasureSpec()

2. measure过程

measure过程由measure完成,measure是final方法,不能被重写,所以重点看里面调用的onMeasure。容器类型的View,需要先测量子View。

  • 单个View的测量:
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 设置宽高的测量值        
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

    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;
        // AT_MOST & EXACTLY的返回值是一样的
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

从getDefaultSize可以看出,默认AT_MOST和EXACTLY的返回值是一样的,所以直接继承View的控件需要重写onMeasure设置specMode=wrap_content时的大小,不然wrap_content效果=match_parent。

  • ViewGroup测量:

ViewGroup除了测量自身,还需要去调用所有子元素的measure方法来测量子元素,子元素在递归去执行该过程。

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

    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);
        // 调用子View的 measure方法
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

在measure时,遍历所有子元素,调用子元素的measure方法。虽然ViewGroup的子类容器布局不一定使用measureChildren方法,但是主要逻辑是一样的,都是遍历子View进行测量。

三、View绘制流程之layout

layout的作用是用来确定容器自身以及子元素的位置,首先调用layout()确定自身位置,layout中又会调用onLayout来确定子元素位置。

来一张图看layout的流程:

Android自定义View基础——View的工作原理_第3张图片
layout过程.png

四、View绘制流程之draw

draw作用是将View绘制到屏幕上。

绘制顺序如下:

  • 绘制背景:background.draw(canvas)
  • 绘制自己:onDraw(canvas)
  • 绘制children:dispatchDraw(canvas)
  • 绘制装饰:onDrawScrollBars(canvas)

子View的绘制是通过dispatchDraw方法来往下传递的,它会遍历调用所有子元素的draw方法,所有都绘制完成后,在执行自身的onDrawScrollBars。

    protected void dispatchDraw(Canvas canvas) {
        ......
        for (int i = 0; i < childrenCount; i++) {
            while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                final View transientChild = mTransientViews.get(transientIndex);
                if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                        transientChild.getAnimation() != null) {
                    // 调用 child.draw方法
                    more |= drawChild(canvas, transientChild, drawingTime);
                }
                ......
    }

你可能感兴趣的:(Android自定义View基础——View的工作原理)