android绘制view的过程(自定义view一)

来源:互联网 发布:知乎 低价服务器 编辑:程序博客网 时间:2024/05/12 18:16

How Android Draws Views

When an Activity receives focus, it will be requested to draw its layout. The Android framework will handle the procedure for drawing, but the Activity must provide the root node of its layout hierarchy.

Drawing begins with the root node of the layout. It is requested to measure and draw the layout tree. Drawing is handled by walking the tree and rendering each View that intersects the invalid region. In turn, each View group is responsible for requesting each of its children to be drawn (with the draw() method) and each View is responsible for drawing itself. Because the tree is traversed in-order, this means that parents will be drawn before (i.e., behind) their children, with siblings drawn in the order they appear in the tree.

Drawing the layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in measure(int, int) and is a top-down traversal of the View tree. Each View pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every View has stored its measurements. The second pass happens in layout(int, int, int, int) and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.

When a View's measure() method returns, its getMeasuredWidth() and getMeasuredHeight() values must be set, along with those for all of that View's descendants. A View's measured width and measured height values must respect the constraints imposed by the View's parents. This guarantees that at the end of the measure pass, all parents accept all of their children's measurements. A parent View may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small (i.e., if the children don't agree among themselves as to how much space they each get, the parent will intervene and set the rules on the second pass).

The measure pass uses two classes to communicate dimensions. The View.MeasureSpec class is used by Views to tell their parents how they want to be measured and positioned. The base LayoutParams class just describes how big the View wants to be for both width and height. For each dimension, it can specify one of:

  • an exact number
  • FILL_PARENT, which means the View wants to be as big as its parent (minus padding)
  • WRAP_CONTENT, which means that the View wants to be just big enough to enclose its content (plus padding).

There are subclasses of LayoutParams for different subclasses of ViewGroup. For example, RelativeLayout has its own subclass of LayoutParams, which includes the ability to center child Views horizontally and vertically.

MeasureSpecs are used to push requirements down the tree from parent to child. A MeasureSpec can be in one of three modes:

  • UNSPECIFIED: This is used by a parent to determine the desired dimension of a child View. For example, a LinearLayout may call measure() on its child with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how tall the child View wants to be given a width of 240 pixels.
  • EXACTLY: This is used by the parent to impose an exact size on the child. The child must use this size, and guarantee that all of its descendants will fit within this size.
  • AT_MOST: This is used by the parent to impose a maximum size on the child. The child must guarantee that it and all of its descendants will fit within this size.

当一个Activity获得焦点时,它将被请求去描绘它的布局(layout)。Android框架将会处理这个描绘的过程,但是Activity必须提供它的布局层次的根结点。

描绘(绘图,Drawing)由布局的根结点开始,要求测量和描绘整个布局树。描绘处理是通过遍历整个树,渲染每个与失效区域交叉的视图完成的。(Drawing is handled by walking the tree and rendering each View that intersects the invalid region.译者按:这里"invalid region"直译为失效区域,本质上是指视图的需要重新描绘的一部分,通过重绘失效区域而保持其它部分不变可以让描绘过程更加有效率。) 框架不会描绘不在失效区域的视图。框架会帮助描绘视图的背景。可以通过调用invalidate()强制一个视图描绘。

依次地,每个视图组合(View Group)将负责请求描绘它的每一个子视图(使用draw()方法),每一个视图将负责描绘自身。因为布局树是被按序遍历的,这意味着父视图将会先于它们的子视图描绘(即父视图在子视图之下),而兄弟结点视图将按其在树中出现的次序描绘。

布局的描绘是一个两阶段的过程:测量阶段和布局阶段。(Drawing the layout is a two pass process: a measure pass and a layout pass)测量阶段由measure(int , int)实现,是一个对视图树自上而下的遍历。在这个迭代中,每个视图将尺寸说明沿树向下传递。在测量阶段结束时,每个视图都将保存了其尺寸值。 第二个阶段由layout(int ,int, int, int)实现,也是一个对视图树自上而下的遍历。在这个遍历过程中,每个父视图负责使用在测量阶段计算得到的尺寸大小,定位(放置)其所有的子视图。

当一个视图的measure()方法返回时,它的getMeasuredWidth()和getMeasuredHeight()值必须被设置,并且连同其所有子视图也要一起被设置。一个视图的测量宽度和测量高度值必须遵从其父视图施加的约束。这保证了在测量阶段结束时,所有的父视图都接受了其所有子视图的尺寸。


一个父视图有可能对其子视图多次调用measure()方法。例如,如果所有子视图的非约束尺寸的总和太大了或太小了,父视图可能使用未确定尺寸(unspecified dimensions)测量每个子视图一次来发现它们想要多大的空间,然后使用实际数值再次调用measure()。(即如果子视图们不能在它们每个能占用多大空间上达成一致,那么父视图将介入调停,在第二次调用时设定规则) 

 

测量阶段使用两个类来传递尺寸。视图用View.MeasureSpec类来告知其父视图他们想要如何被测量和定位。基本的LayoutParams类仅描述了视图想要多大的宽度和高度。 对每个维度尺寸,可以设置为以下值之一:

1)一个具体的数值
2)FILL_PARENT, 意味着视图想要和它的父视图一样大(要减去padding填充尺寸)
3)WRAP_CONTENT, 意味着视图想要刚好足够装入其内容的大小(要加上padding填充尺寸)

对不同的ViewGroup的子类,还有一些相应的LayoutParams的子类。例如, RelativeLayout有自己的LayoutParams的子类,其包含了将其子视图水平和垂直方面居中的能力。

MeasureSpecs用来将测量要求由树中的父视图向下传给子视图。一个MeasureSpec可以为以下三种模式之一:


1)UNSPECIFIED:  父视图用来确定子视图要求的尺寸。例如,一个LinearLayout可能对其高度设置为UNSPECIFIED,而宽度设置为EXACTLY 240的子视图调用measure(),来确定该子视图在给定240像素宽度下,想要多高的尺寸。
2)EXACTLY: 父视图用来强加子视图一个具体的尺寸值。 子视图必须使用该尺寸,并且保证其所有后代视图要适配该尺寸。
3)AT_MOST: 父视图用于强加子视图一个最大的尺寸值。子视图必须保证自身和其所有后代视图要适配该尺寸。

 

 

另外,可通过调用requestLayout()来初始化一个布局,该方法一般是由一个视图针对自身来调用,当其相信已不再适配当前的界限时。


总结来说:
1、Activity被渲染出来时必须要系统其root的根结节是什么。哪个view group。
2、然后系统从此根开始一层一层的自顶向下的遍历各个view(注意ViewGroup是继承自view),先画出draw方法父级的view使用ondraw方法。再画出各个顺序的兄弟子节点。
3、但是View的绘制draw过程时要经过两个阶段的一个是测量阶段measure与布局阶段layout。这两个阶段都是自上而下的遍历view执行的,也就是先执行父级的measure与layout。(当然父级viewgroup中的measure与layout方法内部也需要调用其子view中measure与layout,从这个角度来讲,也可以说是先执行完子view的measure与layout后,再执行完父级的measure与layout,实现上也是系统流程先调用父级的measure与layout。)
4、View.MeasureSpec与LayoutParams类,被使用在measure测量阶段,以便告诉其父级它所需要如何测量定位位置(MeasureSpe)和它的高与宽(LayoutParams)
5、measure方法、layout方法、draw方法内部分别会调用onMeasure方法、onLayout方法、ondraw方法。所以一般只需要重写onMeasure方法、onLayout方法与onDraw方法即可。当然若自定义view中采用inflater方法另外加载出一个view,此父级也是wrap高与宽,其子节点甚至也是wrap时则可以加载此view后,同可以通过MeasureSpe与layoutParams测量出width与height后,再直接调用measure方法。

以上自个人总结,如有错误请大家,不吝赐教。谢谢。