本篇文章翻译自 How Android Draws Views

当一个Activity接收到焦点,它将会被请求来绘制它的布局。Android框架将会处理绘制的过程,但是Activity必须提供其布局层次的根节点。

绘制开始于布局的根节点。它被请求去测量和绘制布局树。绘制是通过遍历树并且渲染每一个与无效区域相交的View来处理的。依次地,每一个ViewGroup负责请求它的每一个子节点来进行绘制(用draw()方法)并且每一个View负责绘制自己本身。由于是按顺序遍历树,所以这也就意味着父节点会在会在他们的子节点之前(也就是在其背后)进行绘制,而兄弟节点将会按照他们在数中出现的顺序进行绘制。

框架不会绘制无效区域以外的View对象,并且它会负责为你绘制View的背景

你可以通过调用invalidate()方法来强制一个View绘制。

绘制布局的过程有两个步骤:一个测量步骤和一个布局步骤。测量步骤在measure(int, int)方法中实现并且自顶向下遍历整个View树。在遍历过程中,每一个View将尺寸规则叠加到树中。在测量步骤结束时,每一个View保存了它的测量值。第二个步骤发生在layout(int, int, int, int)方法里并且也是自顶向下的。在这个步骤中,每一个父节点负责使用在测量步骤中计算得出的尺寸来定位其所有的子节点。

当一个View对象的measure(int, int)方法返回时,它的getMeasuredWidth()getMeasuredHeight()方法的值必须被设定好,连同该View对象的所有后代节点的那两个方法的值也需要被设定好。一个View对象的测量宽度和测量高度的值遵守该对象父节点施加的约束。这保证了在测量步骤结束时,所有的父节点接受子节点的所有的测量值。一个父节点View可以对其子节点不止一次调用measure()方法。例如,父节点可以使用未指定尺寸测量每一个子节点一次以发现他们想要多大的尺寸,如果所有子节点未限制的尺寸太大或者太小则再使用实际的数值对他们再调用一次measure()方法(即,如果子节点不同意他们每个所获取的空间,则父节点将会干涉并且在第二个步骤中设定规则)。

要触发一次布局,可以调用requestLayout()方法。这个方法通常由View自身调用,当它认为他认为它已不再适合当前的界限。

测量步骤使用了两个类来传递尺寸。ViewGroup.LayoutParams类被View对象用来告诉他们的父节点,他们希望如何被测量和定位。ViewGroup.LayoutParams基类描述了View想要多大的宽度和高度。对于每一个尺寸,它可以指定下面中的一个:

  • 一个确切的数字
  • MATCH_PARENT,它意味着View想要与它的父节点一样大(减去padding)
  • WRAP_CONTENT,它意味着View想要足以装入它的内容的大小(减去padding)

对于ViewGroup的不同子类,对应有不同的ViewGroup.LayoutParams的子类。比如,RelativeLayout有它自己的ViewGroup.LayoutParams的子类,其包含了可以水平和垂直居中View子节点。

MeasureSpec对象用来把需求从父节点到子节点叠加到树中。一个MeasureSpec可以是下面三种模式中的一个:

  • UNSPECIFIED:被父节点用于确定一个View子节点的期望尺寸。比如,一个LinearLayout可以使用高度为UNSPECIFIED与宽度为EXACTLY 240的参数在其子节点上调用measure()方法,以此来发现该View子节点的高度是多少。
  • EXACTLY:被父节点用于将一个确切的尺寸加到子节点上。该子节点必须使用这个尺寸,并且确保其所有的后代都在这个尺寸内。
  • AT MOST:被父节点用于将一个最大的尺寸加到子节点上。该子节点必须确保它和它所有的后代都在这个尺寸之内。