前言
在Android View的测量,布局,绘制(一)中,描述了View测量,这篇文章,主要针对View的布局进行讲解。
View的布局摆放,主要是在performLayout方法中进行。
##ViewRootImpl
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
...
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); //1
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
注释1host是DectorView,这里调用了host.layout方法,把起始点x=0,y=0传入,然后将测量好的宽高传入。
##View
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;
//这里调用了setFrame进行初始化mLeft,mRight,mTop,mBottom这四个值
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //1
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b); //2
...
}
...
}
注释1 调用了setFrame方法进行初始化mLeft,mRight,mTop,mBottom这四个值。
##View
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
...
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { //1
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position使旧信息无效
invalidate(sizeChanged);
//重新初始化定位
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
...
}
return changed;
}
setFrame方法在进行初始化的时候会对比上一次是否一致,若一致则不会进入注释1的if判断, 若是一致,则会使旧的信息直接失效invalidate(sizeChanged)。接着在对View的上下左右几个点赋值。
注释2
##View
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
onLayout方法,在View类中,只是个空的方法,具体的业务都交给子类是重写。从上面我们知道当前View的子类是DecorView。
##DecorView
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
...
}
接着他调用了父类的onLayout而它的父类是FreamLayout所以,找到最终目标。
##FrameLayout
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
这里请注意,有个核心问题要注意的是同之前所讲的测量流程, 布局也是同样, 每一个不同布局组件她们的实现是不一样的,而在这里我们以FreamLayout举例,在这里他开始调用了一个
layoutChildren方法。
##FrameLayout
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL: //水平居中
/**
* parentLeft + (parentRight - parentLeft - width) / 2 中心点
* 加上左边偏移量,减去右边偏移量得到起点Left坐标
*/
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
//childTop同理
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
这个时候会发现,当前组件在不断的迭代当前的子view,然后开始调用自己layout方法进行定位,所以直接从此处可以看出来,当前布局摆放流程实际上是,先得到顶层, 顶层自己先开始layout进行布局定位,然后调用onLayout调用子view让子view调用自己的layout对自己进行定位以达到定位的所有目的,
总结:
那么其实要清楚了当前的绘制流程和布局流程,需要开发自己自定义的布局其实实际上就只需要添加我门自己的业务代码,不管是FreamLayout,还是LinearLayout等官方提供出来的布局组件, 都是依照这套机制来玩的, 只不过是添加了不同的业务,实现了相对应的效果。
备注:文中Android源码版本9.0
作者:Alan
原创博客,请注明转载处....