• View的工作原理(一)——Measure


    一、认识ViewRoot和DecorView

    当Activity对象被创建的时候,会将DecorView添加到Window中,同时创建ViewRootImpl对象(ViewRoot对应于ViewRootImpl类),两者互相建立关系。

    通过ViewRoot调用performTranversals开始绘制View,依次通过measure、layout、draw三个过程。如图

     

    之后依次调用:performMeasure、performLayout、performDraw三个方法

    二、DecorView的内容栏

     

    ①、上面是标题栏,下面是内容栏。 ②、通过setContentView所设置的布局文件加载到内容栏中(R.id.content) ③、如何获得内容栏:ViewGroup content = findViewById(R.id.content); ④、如何得到我们设置的View content.getChildAt(0);

    三、MeasureSpec介绍

    1.performMeasure中会调用measure方法,measure方法绘制自己的宽高,onMeasure方法会对所有子元素进行measure方法,依次类推,完成整个View树的遍历。

    2.measure完成之后,可以通过getMeasureWidth和getMeasureHeight方法获取View测量后的宽高。

    3.先要了解MeasureSpec:系统会将LayoutParams根据父容器的规则转换成对应的MeasureSpec,之后再根据measureSpec来测量出View的宽/高。

    主要属性:SpecMode三种测量模式:

    UNSPECIFIED(未指定,一般用于系统内部),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;

    EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小,对应LayoutParams中的match_parent和具体数值

    AT_MOST(至多),子元素至多达到指定大小的值,对应LayoutParams的wrap_content

    SpecSize:某测量模式下的得到的大小。

    主要方法:(根据源码,会将测量值打包,然后通过标识符解包)

    1.static int getMode(int measureSpec):根据提供的测量值(格式)提取模式(上述三个模式之一) 返回值是类型

    2.static int getSize(int measureSpec):根据提供的测量值(格式)提取大小值(这个大小也就是我们通常所说的大小) 返回值是大小

    3.static int makeMeasureSpec(int size,int mode):根据提供的大小值和模式创建一个测量值(格式) 返回值是标识符,就是上两个方法的measureSpec

    四、使用

    一般View,其MeasureSpec由父容器的MeasureSpec和自身LayoutParams决定

    1、(DecoreView的measureSpec由屏幕决定)先对DecoreView进行MeasureSpec创建:根据DecoreView的LayoutParams和屏幕的大小

    放入MeasureSepc.makeMeasure()中(源码P179页),返回的spec(应该是标识符,通过MeasureSpec的get方法来获取放入的参数)。

    2、ViewGroup的Measure方法

    之后调用ViewGroup的measure()方法获取ViewGroup的范围(该方法是固定的,没办法继承),然后调用onMeasure()方法(该方法ViewGroup和View都没有重写,所以要自己重写该方法)。在onMeasure()中测量子View。

    测量子View的方法有:

    getChildAt(int index).可以拿到index上的子view。 通过getChildCount得到子view的数目,再循环遍历出子view。测量子View的wSpec,hSpec(结合子View的LayoutParams加上parent的Measure)。接着,childView.measure(int wSpec, int hSpec); //使用子view自身的测量方法

    或者调用viewGroup的测量子view的方法:‘

    //某一个子view,多宽,多高, 内部加上了viewGroup的padding值、margin值和传入的宽高wUsed、hUsed  

    measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed); 

    源码解析:详见P180①

    原理:获取childView的LayoutParams,然后调用getChildMeasure(P180 ②)方法获取childMeasureSpec()。

    所以说该方法的主要功能,就是获取ViewGroup的宽高范围,和ViewGroup的padding及childView的margin,然后通过getChildMeasure()测量真实的childView的Spec

    getChildMeasure()方法:

    根据ViewGroup的Measure和padding相减获取可用范围。

    之后根据parent及child的LayoutParams属性,制定childView的MeasureSpec

    表格参照如下:

     

    //所有子view 都是 多宽,多高, 内部调用了measureChild方法(P186 ②)

    measureChildren(int wSpec, int hSpec);作用、获取所有子View除非View为GONE状态,其余都调用measureChild方法

    //某一个子view,多宽,多高, 内部加上了viewGroup的padding值

    measureChild(subView, int wSpec, int hSpec); (P187)

    3、一般View的measure过程

    详见源码(P184①):一般View的onMeasure方法不是遍历子元素(因为本身就是),作用是调用setMeasuredDimension()设置最终大小。

    源码中有两个方法,getDefaultSize()方法:根据measureSpec决定最终的大小。

    getSuggestedMinmumWidth/Height()作用:再根据设置的背景图和minWidth/Height宽高改变View的范围大小。

    根据源码引出wrap_content设置无效,解决方法:(P186 设置默认宽、高)

    4、LinearLayout部分源码探究(P188)

    onMeasure()根据纵横,选择measure方法(这里选择wrap_content和verital时),之后遍历子View利用mTopLength获取总高度,最后LinearLayout通过measure方法选定总宽高。

    引出:在onCreate、onStart、onResume方法中均无法得到某个View的宽高,原因:View的measure过程和Activity的生命周期不同步执行。

    所以解决方法:

    (一)、重写onWindowFocusChanges()方法,在该方法中调用getMeasureWidth/Height()方法。原因:只有当View初始化完毕才会回调该方法

    作用:当Activity的窗口得到或者数去焦点都会被调用一次。

    (二)、view.post(runnable)将一个runnable投递到一个消息队列的尾部。

  • 相关阅读:
    Android SingleTask启动模式与Home键的问题
    Flutter Widget截图
    Flutter 以Dialog Activity形式展现
    Flutter Text或者RichText不换行的问题
    Adnroid提高效率之资源移动
    GO学习之 输入输出
    GO学习之 运算符
    GO学习之 值类型与引用类型
    GO学习之 指针
    GO学习之 变量与变量的基本数据类型
  • 原文地址:https://www.cnblogs.com/rookiechen/p/5426770.html
Copyright © 2020-2023  润新知