安卓自定义view(三) - 布局
上一篇文章中我们了解了view的测量过程,可能你会觉得很绕,比较难理解。不要怕,view的测量过程是拦路虎,只要把测量过程弄懂了,后面的layout和draw就很简单了。
view在布局中做了两件事
第一件事:在layout中使用setFrame(left,top,right,bottom)设置view的布局
第二件事:在onLayout中遍历每一个子view,根据本view的布局,子view 的测量大小,padding,还有重力等一系列因素确定子view的布局,调用子view的layout方法。
viewGroup需要layout设置自身布局,也需要onLayout确定子view的布局。而view只需要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;
boolean changed = isLayoutModeOptical(mParent)
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //重要!!!设置自身的布局
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
protected boolean setFrame(int left, int top, int right, int bottom) {
//省略...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
//省略...
return changed;
}
layout中最重要的就是使用setFrame设置了自身的布局,在setLayout中,初始化了mLeft,mTop,mRight,mBottom,这时我们就可以使用getLeft,getRight等来获得最终的宽和高。
layout中除了设置自身的宽高,还调用了onLayout方法。ViewGroup是一个抽象类,onLayout并没有实现,我们拿LinearLayout的onLayout举例
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) { //判断orientation
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
LinearLayout的onLayout中首先判断了orientation,然后调用不同的layout方法
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// Where right end of child should go
final int width = right - left; // 子view最大宽度
int childRight = width - mPaddingRight; // 子view最右边
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight; // 子view最大可用空间
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; // 垂直方向的绘制,以垂直方向的重心为主,不过并没有什么实际用处
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; // 横向重心为辅
// 从ViewRootImpl.performLayout()方法传进来的参数里,left和top都是0,bottom是measuredHeight,right是measuredWidth
// 所以下面三种情况,最后的结果都是childTop = mPaddingTop
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength; // 底部对齐
break;
// mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; // 垂直居中
break;
case Gravity.TOP:
default:
childTop = mPaddingTop; // 顶部对齐
break;
}
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i); // 0
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity; // 子view的重心
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection(); // 当前布局的展示方向,从左往右还是从右往左
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); // 水平方向上的重心
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
// 根据子view水平方向上的重心,设置它的左端点
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) { // 有分割线,top往下移
childTop += mDividerHeight;
}
childTop += lp.topMargin; // 算上外间距
setChildFrame(child, childLeft, childTop + getLocationOffset(child), // getLocationOffset()和getNextLocationOffset()方法都返回0
childWidth, childHeight); // 调用child.layout()方法
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
// 更新子view的高度
i += getChildrenSkipCount(child, i); // 0
}
}
}
在layoutVertical方法中,根据每一个子view的测量大小,viewGroup的大小,还有子view的重心,水平等参数确定每一个子view的位置,调用子view的layout方法完成布局。
总结
Viewgroup布局过程
1.在layout中使用setFrame设置自身位置
2.在onLayout中计算每一个子view的位置,调用每一个子view的layout方法,依次传递下去
view的布局过程
1.在layout中使用setFrame设置自身位置