[Android] 如何计算View的Size

** 注:本文参考链接How Android caculates view size

本文例子如下所示:

过程

计算view大小的过程可以分为以下几个步骤:

  1. 确定view想要的size(LayoutParams)
  2. 确定parent view的情况(MeasureSpec)
  3. 在parent view的限制下,根据view的LayoutParams,确定view的大小

参数

LayoutParams

LayoutParams用于view表示想要的size的模式。有以下三种:

  1. FILL_PARENT / MATCH_PARENT (-1)
    view的size和parent view的size一样大
  2. WRAP_CONTENT(-2)
    view的size仅包裹其内容即可
  3. 指定的值(>0)
    view的size为指定的size大小

MeasureSpec

MeasureSpec表示当前view的“约束”(在onMeasure中传入)。约束含模式mode和数值size,当前view根据mode来决定如何看待其中的size。

mode

mode有三种,如下所示:

  1. UNSPECIFIED
    对当前view的size没有约束
  2. EXACTLY
    当前view的bounds(可以理解成“可以使用的范围”)为确定的size,也可以理解成parent view希望当前view(有强制的意味)使用给定的size
  3. AT_MOST
    当前view的大小不可超过指定的size

可以利用MeasureSpec的getMode取出具体值.

size

size为8个byte的int值,mode放在前2个bit中。这里可以利用MeasureSpec的getSize取出size的具体值。

onMeasure过程

view通过onMeasure确定自己的大小。确定自己的大小时,需要的东西有:

  1. 约束MeasureSpec
  2. 自己的LayoutParams
  3. 如果有child view,那么需要获取child view的大小

最终确定当前view的size分别为:

  1. measureWidth
  2. measureHeight

single view

以前面例子中的TextView为例,其onMeasure中传入的MeasureSpec为:

  1. Width
    mode:EXACTLY size:parentContentWidth(LinearLayout的宽-padding)
  2. Height
    mode:AT_MOST size:parentContentHeight(LinearLayout的高-padding)

注:这里先不解释为什么MeasureSpec是这样的。

TextView的LayoutParams为:

  1. Width
    MATCH_PARENT
  2. Height
    WRAP_CONTENT

然后TextView根据约束和LayoutParams确定自己的大小,包括但不限于以下过程:

  1. 设置宽度为parentContentWidth,考虑margin确定字符可用范围
  2. 根据字符高度和字体设置(根据是否超过parentContentWidth确定是否换行)等计算字符所占的高度(没有限制)。
  3. 设置measureWidth和measureHeight

ViewGroup

以前面例子中的LinearLayout为例,假设该LinearLayout为Activity中的root layout其onMeasure中传入的MeasureSpec为:

  1. Width
    mode:EXACTLY size:parentContentWidth(LinearLayout的parent view的宽-padding)
  2. Height
    mode:AT_MOST size:parentContentHeight(LinearLayout的parent view的高-padding)

RelativeLayout的LayoutParams为:

  1. Width
    MATCH_PARENT
  2. Height
    WRAP_CONTENT
  3. Orientation
    vertical

然后LinearLayout根据约束和LayoutParams确定自己的大小,包括但不限于以下过程:

  1. 确定child view的排列方式
  2. 确定TextView的size
  3. 确定ImageView的size
  4. 根据自身的设置,设置measureWidth和measureHeight

要确定child view的size,就要调用child view的onMeasure,传入合适的MeasureSpec,表明parent view对child的约束。

根据parent view的不同module和child view的不同LayoutParams,有如下规则:

  1. 当parent view的mode是EXACTLY时:
child layout mode size
exact size EXACTLY childSize Child wants a specific size.
MATCH_PARENT EXACTLY parentContentSize Child wants to be parent’s size.
WRAP_CONTENT AT_MOST parentContentSize Child wants to determine its own size. It can not be bigger than parent.
  1. 当parent view的mode是AT_MOST时:
child layout mode size
exact size EXACTLY childSize Child wants a specific size
MATCH_PARENT AT_MOST parentContentSize Child wants to be parent’s size, but parent’s size is not fixed. Constrain child to not be bigger than parent.
WRAP_CONTENT AT_MOST parentContentSize Child wants to determine its own size, but it can not be bigger than parent.
  1. 当parent view的mode是UNSPECIFIED时:
child layout mode size
exact size EXACTLY childSize Child wants a specific size.
MATCH_PARENT UNSPECIFIED can not decide yet Child wants to be parent’s size. Child will decide its own size later.
WRAP_CONTENT UNSPECIFIED can not decide yet Child wants to be its own size. Child will decide its own size later.

以例子中的TextView为例:

  1. LinearLayout的width mode为EXACTLY,TextView的width layout param为MATCH_PARENT
  2. LinearLayout的height mode为AT_MOST,TextView的height layout param为WRAP_CONTENT

所以TextView的onMeasure会被传入:

  1. Width
    mode:EXACTLY size:parentContentWidth(LinearLayout的宽-padding)
  2. Height
    mode:AT_MOST size:parentContentHeight(LinearLayout的高-padding)

TextView可以根据上述约束计算自己的大小。ImageView同理。最后LinearLayout根据ImageView和TextView的大小计算自己的大小。注意这里没有weight的设置,所以onMeasure只运行一次。

如上述有错,请留言告知。

[Android] 一种优化view inflate耗时的方法

注:此方法会有内存泄漏(context)泄漏。

做项目的过程中,发现一种一种优化view inflate耗时的方法,思路是预先将view inflate出来备用,用的时候直接取出来,同时再生成下一个(要考虑屏幕方向)。代码如下所示:

用法大概如下所述:
1. 在适当的地方调用push方法。
2. 在使用view的时候,使用类似如下的代码:

[翻译]Android中View是如何绘制的

原文地址How Android Draws Views

Activity获得焦点时,会接收到绘制自身布局(layout)的请求。绘制布局的过程由Android内部框架负责,但需要Activity提供它的布局树的根节点信息。

绘制从布局(树)的根节点开始。(根节点)会接收到测量(measure)和绘制(draw)布局树的请求。绘制是通过遍历树节点并渲染和无效区域(invalid region)相交的视图来完成的。每一个ViewGroup向其子View发起绘制请求(通过调用其draw()方法),同时子View负责绘制自己。因为采用顺序方法来遍历布局树,所以父节点会先于子节点绘制,兄弟节点之间按顺序绘制。

系统框架仅对在无效区域的视图进行绘制。系统框架负责视图背景(View background)相关的绘制工作。

无效区域(invalid region):无效(invaild)意味着此区域所含的信息已经过期了,需要在下一个绘制循环(next draw())中被重绘。

你可以通过调用invalidate()方法来强制绘制一个View。绘制布局通过两个过程来完成:测量过程(measure pass)和布局过程(layout pass)。

  • 测量过程在measure(int, int)方法中实现,是一个自顶向下(top-down)遍历View树的过程。在递归过程中,每个View沿着树向下传达自身的尺寸规格(dimension specifications)。
  • 布局过程在layout(int, int, int, int)中实现,同样地也采用了自顶向下的遍历方法。遍历过程中,父节点负责利用在测量过程中获得的尺寸规格来决定其子节点的位置。

当某个View的measure()方法返回时,它以及它的所有子节点的getMeasuredWidth()getMeasuredHeight()方法的值就已经设置好了。一个View长和宽的测量值必须满足其父节点对其的约束。这保证了当某次测量结束前,所有父节点对子节点的约束被满足。

一个父View可能对其子View调用多次measure()方法。举个例子:父节点可能首先会通过一次没有明确尺寸约束(unspecified dimensions)的测量过程来获取每个子View想获得的视图大小。如果最后得到的数值过大或者过小,那么父节点会再次对其子View调用measure()方法,并使用实际的计算结果作为输入参数(即如果子View不同意首次测量结果,父View会进行第二次带约束条件的测量)。

如果要初始化一个布局过程,请调用requestLayout()方法。当一个View认为自己不再适合当前边界时,此方法会被调用。

测量过程使用了两个类来传递尺寸信息:ViewGroup.LayoutParamsMeasureSpec

ViewGroup.LayoutParams

子View中的ViewGroup.LayoutParams对象负责告知父View它想如何被测量和放置。ViewGroup.LayoutParams仅描述了View在长和宽上请求的大小。其值可以是以下所示的其中一种:

  • 一个确定的数字
  • MATCH_PARENT,意思是View请求跟其父View一样大(减去内边距后)
  • WRAP_CONTENT,意思是View请求其自身尺寸刚好能包含其内容即可(加上内边距后)

对于不同的ViewGroup子类,有不同的ViewGroup.LayoutParams子类与之对应。例如,RelativeLayout有专门的ViewGroup.LayoutParams子类,使其能够将子View水平或者垂直居中放置。

MeasureSpec

MeasureSpec对象在父子节点间传输尺寸约束时使用。MeasureSpec可以是以下三种类型中的一种:

  • UNSPECIFIED:用于父节点了解子节点所需尺寸的过程。例如,一个LinearLayout会使用高为UNSPECIFIED,宽为240作为参数调用measure()方法,以找出在240宽度下,子View所需要的总高度。
  • EXACTLY: 用于父节点强制指定一个尺寸值。其子节点必须使用这个尺寸值,而且要保证它所有的子节点适应这个值。
  • AT MOST: 用户父节点指定给一个尺寸值的上限。子节点必须保证其本身和子节点在此尺寸值范围内。