ViewGroup / View 自定义流程

基础概念

测量(onMeasure)

无论是 View 还是 ViewGroup 都需要测量自身的宽高,View 是直接通过 MeasureSpec 参数进行测量,而 ViewGroup 是将 MeasureSpec 参数传递给子元素的测量方法获取宽高后,通过定义的排列方式计算出宽高进行测量。

MeasureSpec

MeasureSpec 是一个 32 位的 int 值,高 2 位代表 SpecMode,即测量模式;低 30 位代表 SpecSize,即该模式下测量大小;值得注意的是,此时的测量大小并非 View 最后的实际宽高,定位过程决定了 View 的实际宽高。MeasureSpec 的数值通常由其父容器的 MeasureSpec 与自身的 LayoutParams 决定。

Group 测量模式 \ LayoutParams 参数 精确值 MATCH_PARENT WRAP_CONTENT
EXACTLY 精确值,EXACTLY Group Size,EXACTLY Group Size,AT_MOST
AT_MOST 精确值,EXACTLY Group Size,AT_MOST Group Size,AT_MOST
UNSPECIFIED 精确值,EXACTLY Group Size,UNSPECIFIED Group Size,UNSPECIFIED
Group 参数 \ View 参数 精确值 MATCH_PARENT WRAP_CONTENT
精确值 EXACTLY EXACTLY AT_MOST
MATCH_PARENT EXACTLY EXACTLY AT_MOST
WRAP_CONTENT EXACTLY EXACTLY AT_MOST
滚动 UNSPECIFIED UNSPECIFIED

测量过程

  1. 自定义 View 需要重写 onMeasure()方法,否则 WRAP_CONTENT 相当于 MATCH_PARENT,ViewGroup 可根据自己的排列需求重写该方法
  2. 通过 MeasureSpec.getMode() 获取测量模式,MeasureSpec.getSize() 获取测量大小,View 根据测量模式计算自身的大小,ViewGroup 需要调用 measureChild() 方法后,获取子元素大小根据定义的排列方式计算出宽高。
  3. 测量完成需要调用 setMeasuredDimension() 方法来设置数据,否则会抛出异常。getMeasureWidth()getMeasureHeight() 所获取的则为测量的值。

定位(onLayout)

定位主要是 ViewGroup 执行的过程,对于子元素的位置进行确定。值得注意的是 onLayout() 是 ViewGroup 测量子元素的重写方式,layout() 是定位自身的方法,不要混淆。

定位过程

  1. 重写 onLayout() 方法
  2. 通过 measureChild() 方法测量子元素的宽高,然后使用 getMeasureWidth() 获取测量宽度,getMeasureHeight() 获取测量高度
  3. 最后通过 child.layout() 方法确定子元素位置。getWidth()getHeight() 所获得则为定位的值。

注:getWidth() 数值是在 Layout 过程测量,getMeasureWidth() 数值是在 Measure 过程测量。本质上两个数值保持一致,但是当重写 Layout 方法,重新定位并且扩展控件大小时则导致不一致。

绘制(onDraw)

绘制过程则是真正的将图像绘制在屏幕上的过程。

绘制过程

  1. drawBackground() 绘制背景
  2. 保存 Canvas 图层为后续淡出做准备(可选)
  3. onDraw() 绘制 View 的内容
  4. dispatchDraw() 绘制子 View
  5. 绘制淡出边缘并恢复 Canvas 图层(可选)
  6. onDrawForeground() 绘制装饰

自定义元素

自定义属性

优化布局

动态添加元素

方法时机

刷新视图