layout()与onLayout()
layout()与onLayout()两个方法是在view的layout(布置)过程中最重要的两个方法。onLayout()在layout()方法内部被调用。首先看看layout()方法的实现:
layout()方法的实现
public void layout(int l, int t, int r, int b) {
//确保测量工作已经完成
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//保存旧的位置坐标
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//重点步骤,changed表示该view的尺寸或位置是否发生了改变
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果changed为true或是需要重新布置(比如measure过程结束)
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//调用onLayout()方法
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
//调用onLayoutChange()回调方法
onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
方法比较长,因此去掉了一些和本文关系不大的内容。每块代码都给出了说明。下面重点看看这一句:
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
事实上,无论调用的是setOpticalFrame(l, t, r, b)还是setFrame(l, t, r, b),最终都会进入下面的方法中:
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
//如果left,right,top,bottom有一个发生了变化,则重新开始布置。实质上是判断view的位置或尺寸有没有发生变化。
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
//设置changed为true
changed = true;
//计算旧的宽高
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
//计算新的宽高
int newWidth = right - left;
int newHeight = bottom - top;
//判断尺寸是否发生了变化
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
//重绘旧位置
invalidate(sizeChanged);
//更新位置坐标
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
//最终会调用onSizeChanged()回调方法
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
//重绘新位置
invalidate(sizeChanged);
}
return changed;
}
可以看到,该方法是通过更新了mLeft,mTop,mRight,mBottom,并对之前和之后的位置进行重绘来达到改变view位置的目的。事实上,在该方法调用后,getWidth()与getHeight()才能返回真实的宽高。原因如下:
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
这里顺便说明一下它们与getMeasuredWidth(),getMeasuredHeight()的区别。从名字上也能看出来,这两个返回的是measure过程中得到的宽高。我们知道,measure过程是先于layout过程发生的。如果我们在调用layout(int l, int t, int r, int b)的过程中,让r-l == getMeasuredWidth(),b-t == getMeasuredHeight(),那么两组方法的返回值就完全相等,否则就会产生区别。
layout()方法的内容差不多就这些了,总结一下就是:调用layout(int l, int t, int r, int b),会将view放置到左上角坐标(l,t),右下角坐标(r,b)的位置上(注意这里的坐标都是相对于父view而言的,不是绝对坐标),并调用onLayout(boolean changed,int l, int t, int r, int b)方法。
下面看看onLayout()方法:
onLayout()方法的实现
//View类中的onLayout()方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
可以看到,View类中的onLayout()方法是个空方法。这也是在暗示我们,对于View而言,不需要去实现onLayout()方法。接下到ViewGroup里去看看:
//ViewGroup中的onLayout()方法
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
ViewGroup中的onLayout()是一个abstract的方法。这意味着,如果要实现一个ViewGroup,就必须实现它的onLayout()方法。到这里结论已经很明显了:onLayout()方法是用来对子View进行layout的。一般来说,我们需要做的就是根据上一步measure的结果,结合我们想要实现的ViewGroup的特点,对所有子view调用layout()方法,将它们放置到合适的位置上。
requestLayout()
在某些情况下,可能会需要重新对View进行layout(比如添加了新的子view时),此时可以使用requestLayout()方法,调用它会重新执行该view的measure以及layout()方法。简单看一下它的实现:
public void requestLayout() {
//标记当前view,表示该view需要重新进行layout
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
//调用父view的requestLayout()方法
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
只保留了重点部分。实质上就是先给自己打上需要layout的标记,再调用父view的该方法。这样一步步向上提交,最终会到达ViewRootImpl处。ViewRootImpl会对每一个打上标记的view及其子View重新进行measure,layout,draw。