View的绘制流程

View绘制中主要流程分为measure,layout,draw三个阶段

    measure:根据父view传递的MeasureSprc进行计算大小

    layout:根据measure子View所得到的布局大小和布局参数,将子View放在合适的位置上

    draw:把View对象绘制到屏幕上

  Window,ViewRootlmpl,DecorView之间的联系

    一个Activity包含一个Window,Window是抽象基类,是Activity和整个View系统交互的接口,只有一个子类实现类PhoneWindow,提供了一系列窗口的方法.比如设置背景,标题等.

    一个PhoneWindow对应一个DecorView跟一个ViewRootlmpl,DecorView是ViewTree里面的顶层布局,是继承FrameLayout,包含两个子View 一个id=statusBarBackground的View和LineaLayout\

    LineaLayout里面包含title和content title就是平常使用的titleBar或者ActionBar

    Contenty也是继承FrameLayout activity通过setContext()加载布局的时候加载到这个View上

    ViewRootlmpl是建立DecorView和Window之间的联系

这三个阶段的核心入口是在ViewRootlmpl类的preformTraversals()方法中

private void performTraversals() {

    ......

    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);

    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

    ......

  mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);

    ......

    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

    ......

    mView.draw(canvas);

    ......

}

核心还是这三个步骤,就是判断根据之前的状态判断是否需要重新 measure,是否需要重新 layout ,是否需要重新 draw。

    MeasureSpeac:封装了从父View 传递给到子View的布局需求。每个MeasureSpec代表宽度或高度的要求。每个MeasureSpec都包含了size(大小)和mode(模式)

    MeasureSpeac:一个32位二进制的整数型,前面2位代表的是mode,后面30位代表的是size。mode 主要分为3类

        EXACTLY:父容器已经测量出子View的大小。对应是 View 的LayoutParams的match_parent 或者精确数值。

AT_MOST:父容器已经限制子view的大小,View 最终大小不可超过这个值。对应是 View 的LayoutParams的wrap_content

UNSPECIFIED:父容器不对View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。

  封装了从父 View 传递给到子 View 的布局需求:View 的 MeasureSpec 并不是父 View 独自决定,它是根据父 view 的MeasureSpec加上子 View 的自己的 LayoutParams

  View 测量流程是父 View 先测量子 View,等子 View 测量完了,再来测量自己。在ViewGroup 测量子 View 的入口就是 measureChildWithMargins

protected void measureChildWithMargins(View child,

        int parentWidthMeasureSpec, int widthUsed,

        int parentHeightMeasureSpec, int heightUsed) {

    //获取子View的LayoutParam

    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    //通过父View的MeasureSpec和子View的margin,父View的padding计算,算出子View的MeasureSpec

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

    //通过计算出来的MeasureSpec,让子View自己测量。

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {

    int specMode = MeasureSpec.getMode(spec);

    int specSize = MeasureSpec.getSize(spec);

    //计算子View的大小

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

    int resultSize = 0;

    int resultMode = 0;

    switch (specMode) {

    // 父View是EXACTLY的

    case MeasureSpec.EXACTLY:

        //子View的width或height是个精确值,则size为精确值,mode为 EXACTLY

        if (childDimension >= 0) {

            resultSize = childDimension;

            resultMode = MeasureSpec.EXACTLY;

        //子View的width或height是MATCH_PARENT,则size为父视图大小,mode为 EXACTLY

        } else if (childDimension == LayoutParams.MATCH_PARENT) {

            // Child wants to be our size. So be it.

            resultSize = size;

            resultMode = MeasureSpec.EXACTLY;

        //子View的width或height是WRAP_CONTENT,则size为父视图大小,mode为 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;

    // 2、父View是AT_MOST的

    case MeasureSpec.AT_MOST:

        //子View的width或height是个精确值,则size为精确值,mode为 EXACTLY

        if (childDimension >= 0) {

            // Child wants a specific size... so be it

            resultSize = childDimension;

            resultMode = MeasureSpec.EXACTLY;

        //子View的width或height是MATCH_PARENT,则size为父视图大小,mode为 AT_MOST

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

        //子View的width或height是MATCH_PARENT,则size为父视图大小,mode为 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;

    // 父View是UNSPECIFIED的

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

}

当父View的mode是EXACTLY的时候:说明父View的大小是确定的

子View的宽或高是MATCH_PARENT:

子View的宽或高是WRAP_CONTENT:子View是包裹布局,说明子View的大小还不确定,所以子View最大不能超过父View的大小mode=AT_MOST。

子View的宽或高是具体数值:子viewd大小已经固定了,子View的大小就是固定这个数值,mode=EXACTLY。

当父View的mode是AT_MOST的时候:说明父View大小是不确定的。

子View的宽或高是MATCH_PARENT:父View大小是不确定的,子View是填充布局情况,也不能确定大小,所以View大小不能超过父View的大小,mode=AT_MOST

子View的宽或高是WRAP_CONTENT:子View是包裹布局,大小不能超过父View的大小,mode=AT_MOST。

子View的宽或高是具体数值:子viewd大小已经固定了,子View的大小就是固定这个数值,mode=EXACTLY。

需要注意一点就是,此时的MeasureSpec并不是View真正的大小,只有setMeasuredDimension之后才能真正确定View的大小。


  measure:主要功能就是测量设置 View 的大小。该方法是 final 类型,子类不能覆盖,在方法里面会调用 onMeasure(),我们可以复写 onMeasure() 方法去测量设置 View 的大小。

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

                /*-----------省略代码---------------*

              onMeasure(widthMeasureSpec, heightMeasureSpec);

                  /*-----------省略代码---------------*/

    }

在 onMeasure( ) 方法中(onMeasure( ) 方法就是执行测量设置 View 代码的核心所在)

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

    }

getSuggestedMinimumWidth():(这里返回的建议最小值就是我们xml 布局中用的属性 minWidth或者是背景大小)

protected int getSuggestedMinimumWidth() {

        //返回建议 View 设置最小值宽度

        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());

    }

getDefaultSize:主要作用就是根据View的建议最小值,结合父View传递的measureSpec,得出并返回measureSpec

(并不是父View 独自决定,它是根据父 view 的MeasureSpec加上子vIew的自己的LayoutParams,通过相应的规则转化而得到的大小)       

    public static int getDefaultSize(int size, int measureSpec) {

        int result = size;

        //获取父View传递过来的模式

        int specMode = MeasureSpec.getMode(measureSpec);

        //获取父View传递过来的大小

        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {

        case MeasureSpec.UNSPECIFIED:

            result = size;//View的大小父View未定,设置为建议最小值

            break;

        case MeasureSpec.AT_MOST:

        case MeasureSpec.EXACTLY:

            result = specSize;

            break;

        }

        return result;

    }

setMeasuredDimension 作用就是将测量好的宽跟高进行存储。在onMeasure() 必须调用这个方法,不然就会抛出 IllegalStateException 异常

layout()

  作用描述:

measure() 方法中我们已经测量出View的大小,根据这些大小,我们接下来就需要确定 View 在父 View 的位置进行排版布局,这就是layout 作用。

对 View 进行排版布局,还是要看父 View,也就是 ViewGroup。


draw()

经过前面两部的测量跟布局之后,接下来就是绘制了,也就是真正把 View 绘制在屏幕可见视图上。draw()作用就是绘制View 的背景,内容,绘制子View,还有前景跟滚动条

draw 过程中一共分成7步,其中两步我们直接直接跳过不分析了

  第一步:drawBackground(canvas): 作用就是绘制 View 的背景。

  第二步:onDraw(canvas) :绘制 View 的内容。View 的内容是根据自己需求自己绘制的,所以方法是一个空方法,View的继承类自己复写实现绘制内容。

  第三步:dispatchDraw(canvas):遍历子View进行绘制内容。在 View 里面是一个空实现,ViewGroup 里面才会有实现。在自定义 ViewGroup 一般不用复写这个方法,因为它在里面的实现帮我们实现了子 View 的绘制过程,基本满足需求。

  第四步:onDrawForeground(canvas):对前景色跟滚动条进行绘制。

  第五步:drawDefaultFocusHighlight(canvas):绘制默认焦点高亮

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容