android 布局过程

436 阅读4分钟

布局流程

开发者意见->父view意见->自己去测量->父view拿到实际尺寸位置,调用layout方法

  1. 运行前,开发者在xml文件写入对view的要求

  2. 父view在自己的onMeasure中根据开发者在xml中写对子view的要求,和自己的可用空间得出对子view的要求

  3. 子view在自己的onMeasure中,根据自己的特性算出自己期望的尺寸

    ① 如果是ViewGroup,还会调用每个子view的measure进行测量

  4. 父view在子view计算出期望尺寸后,得出子view的实际尺寸和位置

  5. 子view在自己的layout方法中,将父view传进来的自己的实际尺寸和位置保存

    ① 如果是VIewGroup,还会在onLayout里面调用每个子view的layout把它们的尺寸位置传递给他们

具体开发中的使用

继承已有的view,简单改写尺寸
  1. 重写onMeasure

  2. getMeasuredWidth和getMeasuredSize获取到测量出的尺寸

  3. 计算出最终需要的尺寸

  4. setMeasureDimension(width,height)把结果保存

对自定义view完全进行自定义尺寸计算
  1. 重写onMeasure

  2. 计算出自己的尺寸

  3. 用resolveSize或则resolveSizeAndState修正结果

    内部实现:

    ① 首先用MeasureSpec.getMode(measureSpec)和MeasureSpec.getSize(MeasureSpec)去出对自己的尺寸限制类型和限制尺寸

    ② 如果measureSpec的mode是EXACTLY,表示父view对子view的尺寸做出了精的限制,直接用measureSpec的size

    ③ 如果measureSpec的mode是AT_MOST,表示父view对子view的尺寸只限制了上限,要具体看情况

    1. size不大于spec限制的size,便是没有超限,选用计算出的size

    2. size大于spec限制的size,超限了,选用spec的size

    3. mode是UNSPECIFIED,表示父view对子view没有任何尺寸限制,直接选用计算的size

  4. 使用setMeasuredDimension(width,height)保存结果

自定义layout:重写onMeasure和onLayout
  1. 重写onMeasure

    ① 遍历每个子view,用measureChildWidthMargins测量子view

    1. 需要重写generateLayoutParams并返回MarginLayoutParams才能使用measureChildWithMargins方法

    2. 有些子view需要重新测量 当换行时需要

    3. 测量完成后,得出子View的实际位置和尺寸,并暂时保存

      ① measureChildWidthMargins的内部实现

      1. 通过getChildMeasureSpec方法算出子view的widthMeasureSpec和heightMeasureSpec,然后调用child.measure方法让子view自我测量

      2. getChildMeasureSpec方法内部是现实结合开发者设置的LayoutParams中的width和heigth与父view自己的可用空间,得出对子view的限制,并使用MeasureSpec.makeMeasureSpec来求得结果

      ② 测量出所有子view的位置和尺寸后,计算出自己的尺寸,并用setMeasuredDimension保存

  2. 重写onLayout

    遍历每个子view,调用他们的layout方法来将位置和尺寸传给他们

杂谈

  1. 有些父view会对子view进行纠正,如constraintLayout就会强行纠正你自定view的长和宽

  2. 一般没人重写layout方法

  3. onLayout负责递归对子view进行赋值,和layout方法不一样

  4. 测量的过程也可以换一种方法来理解

    ① 如果开发者写了具体值(例如 layout_width="24dp"),就不用再考虑父View 的剩余空间了,直接用 LayoutParams.width / height 来作为子 View的限制 size,而限制 mode 为 EXACTLY,因为冲突导致界面不正确,开发者可以通过修改 xml 文件来解决,所以开发者的意见是第一位

    ② 如果开发者写的是 MATCH_PARENT,即要求填满父控件的可用空间,那么由于自己的可用空间和自己的两个 MeasureSpec 有关,所以需要根据自己的 widthMeasureSpec 或 heightMeasureSpec 中的 mode 来分情况判断:

    1. 如果自己的 spec 中的 mode 是 EXACTLY 或者 AT_MOST,说明自己的尺⼨寸有上限,那么把 spec 中的 size 减去自己的已用宽度或高度,就是自己可以给子 View 的 size;至于 mode,就用 EXACTLY(注意:就算自己的 mode 是 AT_MOST,传给子 View 的也是EXACTLY);
    2. 如果自己的 spec 中的 mode 是 UNSPECIFIED,说明自己的尺⼨寸没有上限,那么让子 View 填满自己的可用空间就无从说起,因此选用退让方案:给子 View 限制的 mode 就设置为 UNSPECIFIED,size 写 0 就好;

    ③ 如果开发者写的是 WRAP_CONTENT,即要求子 View 在不超限制的前提下自我测量,那么同样由于自己的可用空间和自己的两个 MeasureSpec 有关,所以也需要根据自己的 widthMeasureSpec 和 heightMeasureSpec中的 mode 来分情况判断:

    1. 如果自己的 spec 中的 mode 是 EXACTLY 或者 AT_MOST,说明自己的尺寸有上限,那么把 spec 中的 size 减去自己的已用宽度或高度,就是自己可以给⼦子 View 的尺寸上限;至于 mode,就用AT_MOST;
    2. 如果自己的 spec 中的 mode 是 UNSPECIFIED,说明自己的尺⼨寸没有上限,那么也就不必限制子 View 的上限,因此给子 View 限制的mode 就设置为 UNSPECIFIED,size 写 0 就好。