当 Activity 获取到焦点后,将会请求绘制它的布局。Android 框架负责处理绘制流程,而且该 Activity 必须提供它布局层次的根节点。
绘制从布局的根节点开始,它会请求测量和绘制布局树。通过遍历布局树并渲染每个在有效区域内的视图来处理绘图。ViewGroup 负责依次请求每个子视图去绘制(调用 draw() 方法),View 负责绘制它自己。因为是按顺序遍历树,意味着父视图会在子视图之前被绘制,同级视图会按被添加到树的顺序依次绘制。
绘制布局之前先经过两个过程:measure(测量) 和 layout(布局)。测量过程在 View 的 measure(int, int)
中实现,并且是根据视图树自上而下的遍历。在递归过程,每个 View 会它的尺寸规格沿树向下传递。测量结束时,每个视图已经保存了自己的测量值。第二个过程发生在 layout(int, int, int, int)
,也是由上而下的。在这个过程,父视图的负责根据测量过程计算的结果来放置它的子视图。
框架不会绘制在无效区域的 View 对象,以及 View 的背景。你可以通过调用 invalidate()方法强制 View 绘制。
当 View 对象的 measure()方法返回时,它的 getMeasuredWidth()和 getMeasuredHeight()方法肯定是可以获取到有效值的,以及该 View 对象下的子 View 也一样。View 对象测量的宽高会受父节点的约束。这保证在测量过程结束时,所有父视图都能接受它们子视图的测量值。父视图可能会调用子视图的 measure(int,int)方法一次以上。例如,父视图可以在不指定尺寸情况下先测量每个子视图想要多大尺寸,如果所有子视图测量值的和太大或太小,那么父视图会用精确数值再次调用 measure()(也就是说,如果子视图对它们获得的尺寸不满意,那么父视图将会干涉并根据情况设置第二种测量规则)。
调用 requestLayout()方法会触发 View 的 layout 流程。这个方法通常由 View 自己调用,当它认为当前布局需要重新调整的时候。
测量过程使用两个类来传递尺寸。View 对象使用 ViewGroup.LayoutParams类告诉父视图它们想如何被测量和放置。基本的 ViewGroup.LayoutParams类描述了 View 想要多大的宽和高。它可以指定为以下其中之一:
- 精确的数字
- MATCH_PARENT,意思是这个 View 需要和它父视图一样大(减去padding)
- WRAP_CONTENT,意思是这个 View 需要足够包裹内容的尺寸(加上padding)
ViewGroup的不同子类也分别有各自的 ViewGroup.LayoutParams子类,如FrameLayout.LayoutParams,GridLayout.LayoutParams,LinearLayout.LayoutParams,LinearLayoutCompat.LayoutParams,RecyclerView.LayoutParams,RelativeLayout.LayoutParams。例如, RelativeLayout有它自己的 ViewGroup.LayoutParams子类,包含让子 View 水平居中和垂直居中的能力。
MeasureSpec对象用于将尺寸规格从父视图沿树向下传递到子视图。它可以是是以下三种模式之一:
UNSPECIFIED: 未指定尺寸,这是父节点用于确定子视图所需尺寸。例如,一个 LinearLayout
可以调用它子视图的 measure(int widthMeasureSpec, int heightMeasureSpec)方法,并设置 heightMeasureSpec
为 UNSPECIFIED, widthMeasureSpec为 EXACTLY + 240,计算出当宽为 240 像素时该子视图需要多高。EXACTLY: 为子视图指定一个确切的尺寸。子视图必须使用这个尺寸,并确保所有子视图在这个尺寸之内。
AT MOST: 为子视图指定一个最大尺寸。子视图必须确保它和它的所有子视图在这个尺寸之内。