美文网首页
View的工作原理

View的工作原理

作者: 小甜李子 | 来源:发表于2018-03-26 11:57 被阅读0次

    ViewRoot和DecorView

    在正式了解View的三大流程(measure,layout,draw)之前,我们先认识以下ViewRoot和DecorView

    ViewRoot对应于ViewRootImpl类,它是连接WindowManager与DecorView是纽带,View的三大流程都是通过ViewRootImpl来完成的。在ActivityThread中,当Activity被创建的时,会将DecorView添加到Window中,同时创建一个ViewRootImpl与其关联

    View的绘制流程是从ViewRoot的performTraversals方法开始的,经过measure(测量宽高),layout(布局位置),draw(内容绘制)

    PerformTraversals方法会依次调用performMeasure,performLayout与performDraw方法,这三个方法分别完成顶级View的measure,layout,draw过程。其中performMeasure会调用measure方法完成顶级View自身的测量过程,紧接着调用onMeasure方法对所有子元素进行测量,接着子元素重新measure过程,如此反复完成整个view树的遍历。同理performLayout与performDraw也是同样的过程,只不过performDraw的传递过程是在draw中调用dispatchDraw方法,但是本质上都是一样的。

    Measure过程决定了View的测量宽高,完成后可以通过getMeasureWidth/Height获得测量宽高,测量宽高一般与最终宽高一致,但是也有例外情况

    Layout过程决定了View的四个顶点的位置以及最终宽高,完成后可以通过getLeft/Right/Top/Bottom获取四个顶点坐标,可通过getWidth/Height获取View的最终宽高

    Draw过程是对View内容的绘制,在draw完成后View的内容才会最终显示是屏幕上

    DecorView是顶级View(继承自FrameLayout),一般情况下它内部包含一个LinearLayout里面分为上下两部分(具体与Android版本与主题有关),上部分为标题栏,下部分为内容栏(FrameLayout)。SetContentView就是将布局添加到内容栏中,其id就是android.R.id.content。那么我们可以通过如下代码获取ContentView

    ViewGroup décor = getWindow().getDecorView(); // get décor

    View content = décor.findViewById(android.R.id); // get content

    理解MeasureSpec

    MeasureSpec是View进行测量过程的“测量规格”。它里面主要存储32位的int值,高2位代表测量模式,低30位代表测量大小。

    View$MeasureSpec#makeMeasureSpec/getMode/getSize

    MeasureSpec通过将SpecMode与SpecSize打包成一个int值来避免过多的内存消耗,并且提供了打包与解包的方法

    打包:makeMeasureSpec

    解包:getMode与getSize

    SpecMode分为三类:

    1. UNSPECIFIED

    表示父容器不对View有任何限制

    2. EXACTLY

    精确值模式,表示View使用具体宽高

    3. AT_MOST

    最大模式,表示View可以根据自身需求设定宽高,但是不可超过当前的可用值

    MeasureSpec与LayoutParams的关系

    MeasureSpec是用于定义对View的测量规范的,而View的宽高属性定义在LayoutParams中。那么在测量时系统会将LayoutParams中的相关属性在父容器的约束下转换成对应的MeasureSpec(即View的MeasureSpec不是由LayoutParams单独决定的,是由LayoutParams与父容器的MeasureSpec共同决定)。

    而DecorView的MeasureSpec是由窗口尺寸与自身LayoutParams决定

    一旦MeasureSpec确定后,在onMeasure就可以确定View的测量宽高

    ViewRootImpl# measureHierarchy_1214

    LayoutParams中宽高参数与SpecMode

    MATCH_PARENT:精确模式,大小为父容器可用大小

    WRAP_CONTENT:最大模式,不可大于父容器可用大小

    固定大小:精确模式,大小为属性指定的value值

    源码分析:ViewGroup到View的Measure过程

    控件的测量主要两个情况,如果只是一个原始View,那么直接measure过程完成测量,如果是ViewGroup除了自身测量外还会执行onMeasure遍历所有子View的measure过程

    View的measure过程

    View的measure过程是由其measure方法完成,measure是final方法,意味着子类无法重写,在measure方法中会调用View的onMeasure方法

    View#onMeasure

    getDefaultSize方法获取默认大小

    getSuggestedMinimumWidth方法获取建议的最小大小

    ViewGroup并没有覆写View的onMeasure方法,它只是抽象的规范,这需要在具体子类中根据自身规则完成ViewGroup自身的测量

    ViewGroup#measureChildren

    ViewGroup#measureChild

    ViewGroup #getChildMeasureSpec

    例如垂直的LinearLayout会在onMeasure中遍历所有元素,依次调用子元素的measure方法,并且通过mTotalLength存储在垂直方向的所有子元素占据的高度(子元素高度(包括padding) + margin )

    总高度 = 子元素总高度 + 自身padding

    测量完子元素后根据自身lp与子元素总高度决定自身的测量

    获取控件测量宽高的时机

    由于View的测量过程和Activity的生命周期是不一致的,不是同步方式执行的。即我们无法在Activity中某个生命周期时获取View的测量宽高(因为此时View可能还没有测量结束)

    方式1.Activity/View#WindowFocusChanged

    当该方法触发时候证明View已经初始化完毕了,这个时候就可以去获取宽高

    @Overridepublic void onWindowFocusChanged(booleanhasFocus) {

    super.onWindowFocusChanged(hasFocus);

    if(hasFocus && mTextView!=null){

    Toast.makeText(this,"mTextView.getMeasuredHeight():"+ mTextView.getMeasuredHeight(), Toast.LENGTH_SHORT).show();

    }

    }

    方式2.view.post(runnable)

    通过post将一个runnable对象投递到消息队列的尾部,等待Looper执行(此时View已经完成初始化)

    mTextView.post(newRunnable() {

    @Override    public voidrun() {

    mWidth mTextView.getMeasuredWidth(); }});

    方式3.ViewTreeObserver

    使用ViewTreeObserver的众多回调均可以实现该功能,比如使用OnGlobalLayoutListener接口,当View树的状态发生改变或者View树内部的View的可见性发现改变,onGlobalLayout会被回调(多次)

    ViewTreeObserver observer = mTextView.getViewTreeObserver();

    observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)

    @Override    public voidonGlobalLayout() {

    mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);

    mWidth mTextView.getMeasuredWidth();

    }

    });

    方式4.view.measure(int widthSpec,heightSpec);

    这种情况需要根据LayoutParams分情况处理

    match_parent

    无法使用该方式,因为此时无法知道父容器剩余空间

    具体的数值

    int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);

    int heightSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);

    mTextView.measure(widthSpec,heightSpec);

    wrap_content

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

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

    mTextView.measure(widthSpec,heightSpec);

    View的尺寸大小是由30位二进制表示,那么最大即为2^30-1

    注意:View的measure与onMeasure以及layout与onLayout方法在ViewGroup中均没有被覆写,因为这四个方法只是定义了一个流程,而ViewGroup只是布局控件的统一父类,只有在具体的ViewGroup中才会去覆写onMeasure与onLayout方法,比如LinearLayout

    相关文章

      网友评论

          本文标题:View的工作原理

          本文链接:https://www.haomeiwen.com/subject/ofkrqftx.html