Android控件架构

View树结构

ViewGroup 作为父控件可以包含多个 View 控件,并管理其包含的 View 控件。上层控件负责下层控件的测量和绘制,并传递交互事件。常用的 findViewById 就是以深度优先的方式遍历查找对应元素。

UI界面架构图

每个 Activity 都包含一个 Window 对象,Window 的具体实现是 PhoneWindow。 PhoneWindow 将一个 DecorView 作为窗口界面的顶层视图,DecorView 将要显示的具体内容呈现在了 PhoneWindow 上,里面的所有 View 的监听事件,都通过 WindowManagerService 进行接收,并通过 Activity 对象回调对应的 onEventListener。

View 测量模式

  • EXACTLY: 精确模式,layout_width/layout_height 为具体数值时,或为 match_parent 时

  • AT_MOST: 最大模式,layout_width/layout_height 为 wrap_content 时,View 的大小不超过父控件大小即可

  • UNSPECIFIED: 不指定大小的测量模式

View 的默认 onMeasure() 方法只支持 EXACTLY 模式,所以自定义的控件,一般都需要重写 onMeasure() 让自定义 View 支持 wrap_content 属性。

1
2
3
4
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

View 的默认 onMeasure 实现里,最终会调用 setMeasuredDimension 将测量后的宽高设置进去,从而完成测量工作。所以重写 onMeasure 方法之后,最终需要把宽高参数通过 setMeasuredDimension 设置。

ViewGroup 的测量

  • 当 ViewGroup 的大小为 wrap_content 时,ViewGroup 就需要对子 View 进行遍历,获取所有子 View 的大小,从而来决定自己的大小。

View 的绘制

View 绘制时系统会调用 onDraw()方法,重写该方法就可以实现绘制。onDraw()有一个参数Canvas canvas对象。使用这个对象,在onDraw()里就可以进行绘图的操作。但在其他地方,通常需要创建一个 Canvas对象。

1
Canvas canvas = new Canvas(bitmap);

查看 Canvas 的这个构造函数签名可以看到:

1
2
3
4
5
6
7
8
9
10
/**
* Construct a canvas with the specified bitmap to draw into. The bitmap must be mutable.
*
* The initial target density of the canvas is the same as the given bitmap's density.
*
* @param bitmap Specifies a mutable bitmap for the canvas to draw into.
*/
public Canvas(@NonNull Bitmap bitmap) {
...
}

传进去的 bitmap 就是为了 canvas 的绘制。这个 bitmap 用来存储所有绘制在 Canvas 上的像素信息。Canvas.drawXXX 方法都会发生在 bitmap 上。

这样就可以通过不同的 canvas 绘制在同一个 bitmap 上。

View 事件传递

View栗子

对于 ViewGroup 主要有3个相关方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, name + " ViewGroup dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, name + " ViewGroup onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.d(TAG, name + " ViewGroup onTouchEvent");
return super.onTouchEvent(ev);
}

View 主要有2个相关方法:

1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "View dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.d(TAG, "View onTouchEvent");
return super.onTouchEvent(ev);
}

点击一次 MyView 之后的日志:

1
2
3
4
5
6
7
8
9
10
Activity dispatchTouchEvent
A ViewGroup dispatchTouchEvent
A ViewGroup onInterceptTouchEvent
B ViewGroup dispatchTouchEvent
B ViewGroup onInterceptTouchEvent
View dispatchTouchEvent
View onTouchEvent
B ViewGroup onTouchEvent
A ViewGroup onTouchEvent
Activity onTouchEvent

View touch event

赏杯咖啡 🍵 Donate