View的layout()
下面的方法解释均翻译于官方文档
/**
* layout机制有两个阶段
* 第一阶段:测量,在这个阶段,每个父view都会调用layout来为子view指定位置,最经典的做法就是使用存于measure pass()的子view测量值
* 第二阶段:分配一个大小和位置给此view和它所有的子view
* View的衍生类不应该重写这个方法,若该类有子view应该重写onLayout(),并在这个方法调用每个子view的layout()
* 上下左右的位置都是相对于该view的父控件来说的。
*/
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
/**
* onMesure()
* 测量视图和它的内容来确定宽高,注意这个方法是由measure()唤醒,并且在子类重写来为它的内容提供更精准有效的测量值。
* 基类测量的实现默认为背景的大小,除非MeasureSpec提供更大的size。子类应该重写onMeasure()为自己的内容提供更好的测量值
* 规定:当年重写这个方法时必须调用setMeasuredDimension()来保存这个view的宽高值。若是没保存成功就会触发measure()抛出IllegalStateException异常。调用父类的onMeasure()是个比较好的办法。
* 如果这个方法在子类被重写,那它应该确保测量的宽高至少是视图的最低宽高。
* @param widthMeasureSpec
* @param heightMeasureSpec
* 两个参数代表这个控件父view的宽高,并与MeasureSpec有紧密联系
*/
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
}
/**
* setFrame()
* 为这个view分配大小和位置。此方法在layout()里调用
* 上下左右的位置都是相对于父view的
* 大小与位置与前一次有改变就会返回true
*/
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
/**
* onLayout()
* 当这个view需要为子view分配大小和位置时会在layout()里调用它。
* 所以有子view的衍生类应该重写这个方法并调用它的每个子view的layout()。
* 5个参数的解释跟setFrame()一样。
*/
onLayout(changed, l, t, r, b);
}
}
总结:大致的layout过程是先调用setFrame()为这个view分配大小和位置,然后回调onLayout函数,对于View来说onLayout只是一个空实现,一般只有当它是ViewGroup需要为子view分配大小和位置时才重写它。然后它会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子视图在父视图中显示的位置,但这不是必须的,我们可以自己传入4个参数调用layout()来安排子view具体位置。
其实具体measure()是什么时候执行的还是不清楚,找了好久也没找着,不过目前来说这个不重要就先放放吧。
MeasureSpec
上面说onMeasure(widthMeasureSpec,heightMeasureSpec)时就说到这两个参数跟MeasureSpec有密切联系,我们先就大致知道怎么用他们就行。
1. MeasureSpec.getMode(widthMeasureSpec)有三个值
- EXACTLY:测量的布局大小是精确的,比如match_parent或具体dp值。
- AT_MOST:子布局可以根据自己的大小选择任意大小。比如wrap_content,但它最大不能超出父控件大小 。
- UNSPECIFIED:父布局没有给子布局任何限制,子布局可以任意大小。这种情况大多出现与ListView 。
2. MeasureSpec.getSize(widthMeasureSpec)
得到测量大小,以像素为单位。