View绘制流程

View的绘制流程

标签(空格分隔): android


初识ViewRoot和DecorView

View的三大流程:View的measure、layout、draw过程
ViewRoot对应于ViewRootImp,在Activity对象完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象和将ViewRootImpl对象和DecorView建立关联。

measure用来测量View的宽高,layout用来确定View在父容器中的放置位置,而draw负责将View绘制在屏幕上。

DecorView是顶级View,上面是标题栏,和下面是内容栏,在Activity中,setContentView所设置的布局文件其实就是加在内容栏之中的,而内容的id是content。DecorView其实是一个FrameLayout,View层都经过DecorView,然后传递给我们的View。

理解MeasureSpec

Mesurespec很大程度决定了一个View的尺寸规格,还会受到父View的影响,因为父容器影响View的MeasureMeasureSpec的创建过程。

MeasureSpec

MeasureSepc代表一个32位int值,高2位是SpecMode,低30位代表SpecSize,SpecMode是测量模式,而SpecSize是指某种模式下的规格大小。
SpecMode和SpecSize也是一个int值,一组SpecMode和SpecSize可以打包为一个MesureSpec,而一个MeasureSpec可以解包为其原始的SpecMode和SpecSize。

SpecMode:

  • UNSPECIFIED:父容器不对View有任何限制,要多大就多大。
  • EXACTLY:父容器已经检验出View所需要的精准大小,View最终的大小就是SpecSize所指定的值。相当于match_parent.
  • AT_MOST:父容器指定大小为SpecSize,View不能超过这个大小。就相当于wrap_content.(LayoutParams)

MeasureSpec和LayoutParams的关系

在View测量时,系统会将LayoutParams在父容器的约束下转化为对应的MeasureSpec,再根据Measure来确定View测量后的宽高。两者共同确定了View的宽高。

  • DecorView,其MeasureSpec是由窗体的尺寸和其自身的LayoutParams来共同确定的。
  • 普通的View是由父容器的MeasureSpec和本身的LayoutParams共同决定的。

LayoutParams的参数:

  • LayouParams.MATCH_PARENT:精确模式,大小是窗口的大小。
  • Layout.WRAP.CONTENT:最大模式,大小不定,但是不能超过窗口大小。
  • 固定大小:精确模式,大小是LayoutParams中指定的大小。

MeasureSpec的创建与父容器的MeasureSpec和子元素本身的LayoutParams,和View的margin和padding有关。

int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0,specSize-padding);
measureSpec
measureSpec

View的工作流程

主要是指measure、layout、draw这三个流程。

measure过程

1、View的measure的过程
view的measure过程是由其measure来完成的,measure是一个final的方法,不能被子类重写。

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
    setMeasureDimension(getDefaulatSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
}
public static int getDefaultsize(int size,int measureSpec){
   int result = size;
   int specMode = MeasureSpec.getMode(measureSpec);
   int specMode = MeasureSpec.getSize(measureSpec);
   switch(specMode){
       case MeasureSpec.UNSPECIFIED:
           result = size;
           break;
       case MeasureSpec.AT_MOST:
       case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
   }
   
}

AT_MOST和EXACTLY:
getDefaultSize返回大小就是measureSpec中的specSize,而这个specSize就是测量后的大小,这里多次提到测量后的大小,是因为最终大小是由layout阶段确定的。
UNSPECIFIED:如果View没有设置背景,那么View的宽度就是mMinWidth。而mMinWith对应于android:minWidth这个属性所指定的值,如果不设置就默认为0,如果设置了背景则返回android:minWidth和背景的最小宽度(最小高度)的最大值。

public int getMinimumWidth(){
     final int intrinsicWidth = getIntrinsicWidth();
     return intrinsicWidth > 0 ? intrinsicWidth:0;
}

直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content的自身大小,否则在使用wrap_content就相当于使用match_parent.

protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
    super.onMeasure(widthMeasureSpec,heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getMode(heightMeasureSpec);
    if(widthSpecMode  == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
       setMeasuredDimension(mWidth,mHeight);
    }else if(widthSpecMode  == MeasureSpec.AT_MOST){
       setMeasuredDimension(mWidth,heightSpecSize);
    }else if(heightSpecMode == MeasureSpec.AT_MOST){
         setMeasuredDimension(WidthSpecSize,mHeight);
    }
}

View指定一个默认的内部宽高并在wrap_content时设置其宽高。

2、ViewGroup中的measure过程
对于ViewGroup来说,除了完成自己的measure过程,还会遍历去调用所有的子类的measure方法,子元素再递归去执行这个过程。它没有重写View的onMeasure的方法,提供了一个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((children.mViewFlags & VISIBILITY_MASK) != GONE){
             measureChild(child,widthMeasureSpec,heightMeasureSpec);
         }
    }
}

measureChild的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec来创建子元素的MeasureSpec,接着将MeasureSpec直接传递给View的measure的方法测量。ViewGroup是抽象类,这是因为不同的ViewGroup子类有不同的布局特性。

一个有趣的问题
在onCreate、onStart、onResume中均无法正确得到某个View的信息,因为无法保证Activity执行生命周期方法时某个View已经测量完毕了,View的measure过程和Activity生命周期方法不是同步的。
解决这个尴尬的问题:
(1)Activity/View#onWindowFocusChanged
onWindowFouusChanged的含义是:View已经初始完毕了,具体来说就是当Activity继续执行和暂停执行时,onWindowFocusChanged均被调用。

public void onWindowFoucusChanged(boolean hasFocus){
     super.onWindowFocusChanged(hasFocus);
     if(hasFocus){
        int width = view.getMeasureWidth();
        int height = view.getMeasureHeight();
     }
}

(2)view.post(runnable)
通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View的初始化已经好了。
(2)ViewTreeObserver
使用ViewTreeObserver的众多回掉都可以完成这一功能。
如OnGlobalLayoutListener

 @Override
    protected void onStart() {
        super.onStart();
        ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                int height = view.getMeasuredHeight();
                int width = view.getMeasuredWidth();
            }
        });
    }

(4)view.measure

  • match_parent:无法得到
  • wrap_content:
int widthMeasure = View.MeasureSpec.makeMeasureSpec((1<<30)-1,View.MeasureSpec.AT_MOST);
        int heightMeasure = View.MeasureSpec.makeMeasureSpec((1<<30)-1,View.MeasureSpec.AT_MOST);
        view.measure(widthMeasure,heightMeasure);

layout过程

Layout的作用是ViewGroup用来确定子元素的位置。

layout的流程:首先通过setFrame方法来设定View的四个顶点的位置,即初始化mLeft、mRight、mTop、mBottom这四个值,View的四个顶点一旦确定,那么View在父容器的位置就确定了,接着调用onLayout方法,它的子元素的位置。

onLayout方法去调用子元素的layout方法,子元素也通过自己的layout方法来确定自己的位置,这样一层一层的传递下去就完成了整个View树的layout过程。

layout方法中会通过setFrame去设置子元素的四个顶点的位置,setChildFrame中的width和height实际上就是子元素的测量宽高。

getWidth和getHeight方法的返回值刚好就是View的测量宽度和高度。

draw过程

(1)绘制背景background.draw
(2) 绘制自己onDraw
(3)绘制children(dispatchDraw)
(4) 绘制装饰(onDrawScrollBars)

view的dra过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历调用所有的子元素的draw方法。从setWillNotDraw方法可以看出,如果一个View不需要绘制任何内容时,设置这个标志位为true,系统会进行相应的优化。当我们自定义控件继承ViewGroup且本身不具备绘制功能时,就可以开启这个标志位,进行后续的优化。

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

推荐阅读更多精彩内容