上一篇咱们分析Android View的测量。咱们先回顾一下,View的测量,在ViewRootImpl#performTraverals方法下,先进行对DecorView根布局测量获取MeasureSpec,而后开始执行测量performMeasure(),经过View#measure找到对应View的核心onMeasure(),若是是ViewGroup,先递归子View,将父View的MeasureSpec和子View的LayoutParams做为参数而进行测量,而后逐层返回,不断保存ViewGroup的测量宽高。javascript
好了,咱们短短回顾后,回到ViewRootImpl#performTraverals方法:java
private void performTraversals() {
...
if (!mStopped) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
}
...
}复制代码
源码很是清晰,继续咱们的分析performLayout()。Let's go!ide
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(TAG, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}复制代码
源码挺清晰易懂,咱们着重看到host.layout(),host在上面mView赋值,那就是说host是指向DecorView对象的,方法所带的参数分别是0,0,host.getMeasuredWidth(),host.getMeasuredHeight(),分别表明着View的左上右下四个位置。以前发分析所知DecorView是FrameLayout子类,FrameLayout是ViewGroup子类,而咱们在ViewGroup#layout方法中看到是用final修饰的,那就是说host.layout调用的就是ViewGroup#layout,咱们看一下该方法的源码:oop
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}复制代码
咱们首先利用变量的命名推测,再结合源码的注释来分析,看一下mTransition对象,咱们看到是LayoutTransition类的对象,注释写着用于处理ViewGroup增长和删除子视图的动画效果,那就是layout方法一开始多是判断一些参数来处理动画的过渡效果的,不影响总体的代码逻辑,咱们能够直接看super.layout(l, t, r, b);,那就是说调用的是View#layout方法,并将左上右下四个参数传递过去。布局
public class View implements ···{
···
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);//设置相对于父布局的位置
//判断View的位置是否发生过变化,看有不必进行从新layout
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;
}
}复制代码
一开始的判断,咱们从他们全局变量的注释来理解,说的大概是在测量方法被跳过期,须要在layout()前再次调用measure()测量方法。接着是isLayoutModeOptical(),这里面的注释是这个ViewGroup的布局是否在视角范围里,setOpticalFrame()里面的实现方法通过一些判断计算,一样调用回setFrame(l, t, r, b)方法。动画
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
···
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
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;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
···
return changed;
}复制代码
这方法开始咱们能够跳过,主要是对mLeft 、mTop、mRight、mBottom赋值,咱们稍微看一下方法注释中对left,top,right,bottom解析是各位置的点,且是相对于父布局的,那就是说如今赋值后能够肯定了View本身在父布局的位置了。另外咱们在类方法中查询getLeft()等其余三个点,看看他们返回值对用mLeft等对应值的,这个点咱们后面再说,咱们继续往下分析。this
在setFrame()以后咱们终于能够看到onLayout(),点进去查看View#onLayout方法:spa
public class View implements···{
···
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
···
}
public abstract class ViewGroup extends View implements·· ··· @Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
···复制代码
从上面源码咱们看到View#onLayout与ViewGroup#onLayout都是实现了一个空方法。可是ViewGroup是一个抽象方法,那就是说继承ViewGroup的子类必须重写onLayout()方法。由于上篇咱们分析View的测量一样是不一样的ViewGroup都有不一样的onMeasure(),既然测量都不一样了,onLayout()布局方法就确定不一样了,咱们按照上篇的逻辑,依旧对FrameLayout(DecorView)的onLayout来分析:code
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
final int count = getChildCount();
// parentLeft由父容器的padding和Foreground决定
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);
// 不为GONE
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//获取子View的测量宽高
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;
// 当子View设置水平方向layout_gravity属性
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
// 居中的计算方式
case Gravity.CENTER_HORIZONTAL:
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;
}
// 当子View设置竖直方向layout_gravity属性
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);
}
}
}复制代码
FrameLayout#onLayout方法直接调用layoutChildren方法,里面的实现方法虽然有点长,可是比较好理解,无非加点空间想象力上去就无压力了。orm
咱们梳理一下:首先是获得父布局的左上右下的Padding值,而后遍历子布局,经过子View的layout_gravity属性、子View的LayoutParams属性、父布局的Padding值来肯定子View的左上右下参数,而后调用child.layout方法,把布局流程从父容器传递到子元素。
上面咱们已经分析过View#layout方法,是一个空方法,主要做用是咱们使用的子View重写该方法,例如TextView、CustomView自定义View等等。不一样的View不一样的布局方式。你们有兴趣能够看看他们的实现过程。
View#getWidth()、View#getMeasureWidth()
咱们在分析View#setFrame()分析到这个问题,咱们在今篇View的布局可知,在View#setFrame()执行里对
mLeft、mRight、mTop、mBottom,从命名方式带m,咱们能够知道这是一个全局变量,在View的布局时赋值的。
而View#getMeasureWidth()就要回到咱们上一篇View的测量,在View#onMeasure方法中会调用View#setMeasuredDimension方法,在这方式的实现子View设置自身宽高的,这方法里有View#setMeasuredDimensionRaw方法,咱们看一下它的源码:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}复制代码
简单来讲就是对mMeasuredWidth与mMeasuredHeight赋值,因此在View#getMeasureWidth方法里返回的值,,是咱们进行测量后的值mMeasuredWidth。
他们的值基本状况下是一致的,那么不一致时何时呢?看回咱们本篇中的FrameLayout#onLayout,最后是否是调用了childView#layout方法,FrameLayout咱们不可修改,可是在咱们CustomView自定义View,重写onLayout的时候是能够按照咱们的特殊要求修改的,例如修改成:childView.layout(0,0,100,100);那么View#getWidth()、View#getMeasureWidth()返回的值就会不一致,有兴趣的同窗能够本身去验证一下。
因此他们的值在不特殊修改的状况下返回时同样的,可是他们的意义是彻底不一样的,一个在测量过程、一个在布局过程。你们要稍微留意。
View的布局流程就已经所有分析完了。咱们总结一下:布局流程相对简单一些,上一篇View的测量,咱们能够获得View的宽和高,ViewGroup的layout布局,调用layout方法,肯定在父布局的位置,在onLayout()方法中遍历其子View,调用子View的layout方法并根据子View大小、View的LayoutParams值、父View对子 View位置的限制做为参数传入,完成布局;而View的测量利用测量出来的宽和高来计算出子View相对于父View的位置参数,完成布局。在下篇,咱们将会讲述最后一步,View的绘制。