View的工作原理

1.ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorViewde纽带,View的三大流程均是通过ViewRoot来完成。

View的绘制流程是从ViewRoot的performTraversals方法开始,他经过measure,layout,draw三个过程来绘制View。

performTraversals会一次调用performMeasure、performLayout、perfornDraw三个方法,这个方法分别完成顶级View的measure、layout、draw,其中performMeasure中会调用measure方法,在measure方法中又回调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了。同理,performLayout和performDrawde传递流程和performMeasure类似,唯一不同的是performDraw的传递过程是在draw方法中的dispatchDraw来实现的

2.MeasureSpec

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode指测量模式,在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的高度

SpecMode分类

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

EXACTLY :父容器已经测量出View所需的精确的大小,此时View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中的match_parent和具体的数值这两种模式

AT_MOST :父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content

3.MeasureSpec和LayoutParams的对应关系

正常情况下,使用MeasureSpec来测量View,也可以给View设置LayoutParams,在View测量的时候,系统会将LayoutParams在父容器的约束下转换成相应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽高。注意:MeasureSpec不是唯一有LayoutParams决定的,LayoutParams需要和父容器一起绝定View的MeaureSpec。对于顶级View(即DecoView)和普通View来说,MeasureSpec的转换过程不同,对于DecrView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同决定,对于普通View,其MeasureSpec有父容器的MeasureSpec和自身LayoutParams来共同决定。

  parentSpecMode EXACTLY AT_MOST UNSPECIFIED
childLayoutParams        
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
measure完成后,通过getMeasuredWidth/Heigth方法就可以正确地获取到View的测量宽高。注意,在某些极端情况下,系统可能需要多次measure才能确定最终的测量,在这种情况下,在onMeasure方法中拿到的宽高可能是不准确的,建议在onLayout方法中去获取View的测量宽高

注意,在Activity中的onCreate,onstart,onResume,中均无正确得到某个View的宽高信息,因为View的meaure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate,onStart,onResume时的某个View已经测量完毕了。解决办法如下

(1)Activity的onWindowFocusChanged方法

on'Window'Focus'C'hanged方法含义,View已经初始化完毕,当Acticity的窗口得到焦点和失去焦点时均会被调用一次

(2)view.post(runnable)

通过post可以将一个runnable投递到消息队列尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了

  eg

protected void onStart(){

    super.onStart();

    view.post(new Runnable(){

        @override

        public void run(){

            int  width = view.getMeasuredWidh();

            int width = view,getMeasuredHeigth();

        }

    });

}

(3)ViewTreeObserver

(4)view.measure(int widthMeasureSpec,int heigthMeasureSpec)

通过手动对View进行测量得到View的宽高,需要根据View的LayoutParams来分

a, match_parent 无法measure出具体的宽高,原因根据是View的measure过程,构造此MeasureSpec需要parentSize,而这是我们无法知道parentSize的大小

b,具体的数值

int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);

int heigthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);

view.measure(widthMeasureSpec,heigthMeasureSpec  );

c,warp_content

int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);

int heigthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);

view.measure(widthMeasureSpec,heigthMeasureSpec  );

4.View的getMeasuredWidth/Height和getWidthHeight的区别

在默认实现中,View的测量宽高和最终宽高是相等的,至不过测量的宽高形成于View的measure过程,而最终的宽高形成于View的layout过程,但是存在特殊情况

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

    super.layout(l,t,r+100,b+100);

}

5draw的setWillNotDraw

public void setWillNotDraw(boolean willNotDraw)

如果一个View不需要绘制任何内容,那么设置这个标记位为true以后,系统会进行相应的优化,默认情况下,View没有启动这个优化标记位,但是ViewGroup会默认启动这个优化标记位,这个标记为的实际开发意义是,当我们的自定义控件继承于ViewGiroup并且本身不具备绘制功能时,就可以开启这个标记为从而便于系统进行后续优化,当然,当明确知道一个ViewGroup需要通过onDraw来绘制内容时,需要显示地关闭WILL_NOT_DRAW这个标记位





你可能感兴趣的:(View的工作原理)