一、体系
详见:Activity、Window、DecorView、ViewRoot 之间的关系
二、绘制前的准备 DecorView
顶部图中可以看出 ViewRoot 最后一步是绘制,在绘制之前系统会有一些准备,即前面几个步骤:创建PhoneWindow、DecorView、ViewRootmpl。
三、View类中的方法
构造 | View(context) | 单参构造:代码中 new 创建实例的时候调用。 |
View(context, attrs) | 双参构造:xml中使用时调用(xml转java代码的时候反射),attrs是xml中的属性。 | |
View(context, attrs, defStyleAttr) | 三参构造:使用主题Style的时候调用。 | |
绘制过程 | onFinishInflate() | 当View和他的所有子控件被XML布局文件填充完成时被调用。 |
onMeasure() | 当测量View和它的子View的尺寸需求时被调用。 | |
onLayout() | 当View给他的子View分配大小和位置的时候调用。 | |
onSizeChanged() | 当view尺寸发生变化时调用。 | |
事件处理 | onKeyDown() | 按下按键时调用。 |
onKeyUp() | 抬起按键时调用。 | |
onTouchEvent() | 触摸屏幕时调用。 | |
焦点 | onFocusChanged() | 获取到或者失去焦点时调用。 |
onWindowFocusChanged() | 窗口获取或者失去焦点时调用。 | |
attach | onAttachedToWindow() | 当View被连接到一个窗口时调用。 |
onDetachedFromWindow() | 当View从窗口分离时调用。 | |
onWindowVisibilityChanged() | 当View的窗口的可见性发生改变时调用。 |
四、自定义控件的不同方式
复用已有控件 | 复用布局文件 | |
组合现有的控件 | ||
扩展已有的控件 | 继承特定的View(如TextView) | |
继承特定的ViewGroup(如LinearLayout) | ||
完全自定义 | 直接继承 View | 主要是实现 onMeasure() + onDraw(),因为没有子元素需要布局。 |
需要自己实现 wrap_content、padding,不需要自己实现 margin 该属性是由父容器决定的。 | ||
直接继承 ViewGroup | 主要是实现 onMeasure() + onLayout(),因为子元素都绘制好了父容器不需要额外绘制。 | |
需要自己实现 wrap_content、padding、margin。 |
五、绘制流程
从 DecorView 中可以看出 View 的绘制流程开始于ViewRootImpl 对象的 performTraversals(),从视图树根节点(即 DecorView 它是个 ViewGroup)开始由上向下遍历测绘(DecorView→中间层ViewGroup→最底层View)。
七、自定义流程
构造函数只执行一次,而 onMeasure()、onLayout()、onDraw() 随着控件的刷新可能会执行很多次,因此用到的对象(如 Paint)需要定义成全局变量(避免频繁创建销毁造成内存抖动),在生命周期方法中使用前需要重置(避免每次执行都拿到用过的数据)。
7.1 重写构造自定义属性
在 xml 中为控件设置的属性。
- 自定义属性名称如果使用系统已定义的,例如 textSize 会在编译时报错。
7.2 重写测量、布局、绘制
自定义 View 主要是实现 onMeasure() + onDraw(),因为没有子元素需要布局。自定义 ViewGroup 主要是实现 onMeasure() + onLayout(),因为子元素都绘制好了父容器不需要额外绘制。
Measure 过程 | 测量控件的宽高 | performMeasure() → measure() → onMeasure() → 子控件的 Measure 过程。 |
Layout 过程 | 确定控件最终宽高在父容器中的位置 (即四个顶点位置) | performLayout() → layout() → onLayout() → 子控件的 Layout 过程。 |
Draw 过程 | 将控件绘制在屏幕上 | performDraw() → draw() → onDraw() → 子控件的 Draw 过程。 |