文本对于 Android View 的总结比较简洁易懂,转过来与大家一起分享。
原文地址:http://threezj.com/2015/12/17/Android%20View%E8%AF%A6%E8%A7%A3/
关于 Android View 控件
Android 中控件大致被分为两类 ViewGroup,View。ViewGroup 作为容器管理 View。 Android 视图,是类似于 Dom 树的架构。父视图负责测量定位绘制等操作。我们经常在用的 findViewById 方法代价昂贵的原因,就是因为他负责至上而下遍历整棵控件树,来寻找 View 实例,在重复操作中尽量少用。现在在用的很多控件都是直接或者间接继承自 View 的,如下图:
Android UI 界面架构
每个 Activity 包含一个 PhoneWindow 对象,PhoneWindow 设置 DecorView 为应用窗口的根视图。在里面就是熟悉的 TitleView 和 ContentView,没错,平时使用的 setContentView() 就是设置的 ContentView。

Android 是如何绘制 View 的?
当一个 Activity 启动时,会被要求绘制出它的布局。Android 框架会处理这个请求,当然前提是 Activity 提供了合理的布局。绘制从根视图开始,从上至下遍历整棵视图树,每一个 ViewGroup 负责让自己的子 View 被绘制,每一个 View 负责绘制自己,通过 draw() 方法.绘制过程分三步走:
- Measure
- Layout
- Draw
整个绘制流程是在 ViewRoot 中的 performTraversals() 方法展开的。部分源代码如下:
1 | private void performTraversals() { |
在绘制之前当然要知道 view 的尺寸和绘制。所以先进行 measure 和 layout(测量和定位),如下图:

Measure 过程
1 | public final void measure(int widthMeasureSpec, int heightMeasureSpec) { |
计算 view 的实际大小,获得高宽存入 mMeasuredHeight 和 mMeasureWidth,measure(int, int)传入的两个参数。MeasureSpec 是一个32位 int 值,高2位为测量的模式,低30位为测量的大小。测量的模式可以分为以下三种:
- EXACTLY
精确值模式,当 layout_width 或 layout_height 指定为具体数值,或者为 match_parent 时,系统使用 EXACTLY。
- AT_MOST
最大值模式,指定为 wrap_content 时,控件的尺寸不能超过父控件允许的最大尺寸。
- UNSPECIFIED
不指定测量模式,View 想多大就多大,一般不太使用。
根据上面的源码可知,measure 方法不可被重写,自定义时需要重写的是 onMeasure 方法
1 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
查看源码可知最终的高宽是调用 setMeasuredDimension() 设定的,如果不重写,默认是直接调用 getDefaultSize 获取尺寸的。
使用 View 的 getMeasuredWidth() 和 getMeasuredHeight() 方法来获取 View 测量的宽高,必须保证这两个方法在 onMeasure 流程之后被调用才能返回有效值。
Layout 过程
Layout 方法就是用来确定 view 布局的位置,就好像你知道了一件东西的大小以后,总要知道位置才能画上去。
1 | mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); |
layout 获取四个参数,左,上,右,下坐标,相对于父视图而言。这里可以看到,使用了刚刚测量的宽和高。
1 | public void layout(int l, int t, int r, int b) { |
通过 setFrame 设置坐标。如果坐标改变过了,则重新进行定位。如果是 View 对象,那么 onLayout 是个空方法。因为定位是由 ViewGroup 确定的。
当 layout 结束以后 getWidth() 与 getHeight() 才会返回正确的值。
这里出现一个问题,getWidth/Height() and getMeasuredWidth/Height() 有什么区别?
getWidth(): View 在設定好佈局後整個 View 的寬度。getMeasuredWidth(): 對 View 上的內容進行測量後得到的 View 內容佔據的寬度

Draw 过程
1 | public void draw(Canvas canvas) { |
重点是第三步调用 onDraw 方法。其它几步都是绘制一些边边角角的东西比如背景、scrollBar 之类的。其中 dispatchDraw,是用来递归调用子 View,如果没有则不需要。
onDraw 方法是需要自己实现的,因为每个控件绘制的内容不同。主要用 canvas 对象进行绘制,这里就不说了。
参考资料:
