根据 measure 测量出来的宽高,肯定全部 View 的位置。java
View 自己的位置是经过它的四个点来控制的: ide
如下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删减。源码分析
先经过 measure 测量出 ViewGroup 宽高,ViewGroup 再经过 layout 方法根据自身宽高来肯定自身位置。当 ViewGroup 的位置被肯定后,就开始在 onLayout 方法中调用子元素的 layout 方法肯定子元素的位置。子元素若是是 ViewGroup 的子类,又开始执行 onLayout,如此循环往复,直到全部子元素的位置都被肯定,整个 View 树的 layout 过程就执行完了。布局
在上一节 《View的绘制-measure流程详解》中说过,View 的绘制流程是从 ViewRootViewImpl 中的 performMeasure()
、performLayout
、performDraw
开始的。在执行完 performMeasure()
后,开始执行 performLayout
方法:(如下源码有所删减)post
//ViewRootViewImpl 类
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
final View host = mView;
/*代码省略*/
//开始执行 View 的 layout 方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
/*代码省略*/
}
复制代码
如此,就执行了 View 的 layout 方法:ui
//View 类
public void layout(int l, int t, int r, int b) {
/*代码省略*/
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//肯定 View 的四个点后,调用 setOpticalFrame/setFrame 方法来控制 View 位置。
//方法 1--->setOpticalFrame
//方法 2--->setFrame
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//执行 onLayout 方法
onLayout(changed, l, t, r, b);
/*代码省略*/
}
/*代码省略*/
}
//方法 1---->setOpticalFrame
//View 类
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
//内部最终也是调用 setFrame 方法
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
//方法 2--->setFrame
//View 类
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;
/*代码省略*/
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);
//对 View 的四个点赋值
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
//这里 sizeChange 方法内部调用了 onSizeChanged 方法。
//因此当控件的大小和位置改变的时候会回调 onSizeChanged 方法
//方法 3--->sizeChange
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
/*代码省略*/
}
return changed;
}
//方法 3--->sizeChange
// View 类
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
//执行 onSizeChanged 方法
onSizeChanged (newWidth, newHeight, oldWidth, oldHeight);
if (mOverlay != null) {
mOverlay.getOverlayView().setRight(newWidth);
mOverlay.getOverlayView().setBottom(newHeight);
}
rebuildOutline();
}
复制代码
接下来咱们再看 onLayout
方法,在 View 中找到 onLayout
方法,会发现这是一个空实现的方法,里面什么也没有执行,那么咱们就在 ViewGroup 中找 onLayout
的实现,发现只是一个抽象方法。spa
//View 类
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
复制代码
//ViewGroup 类
@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
复制代码
在 View 类中 onLayout 是一个空实现不难理解,由于若是一个控件继承了 View ,它是没有子元素的,不须要肯定子元素的位置,只须要肯定本身的位置就够了。.net
在 ViewGroup 中是一个抽象方法,意思也很明显了,在控件继承自 ViewGroup 的时候,咱们必须重写 onLayout
方法。由于如LinearLayout
、RelativeLayout
,他们的布局特性都是不同的,须要各自根据本身的特性来进行制定肯定子元素位置的规则。code
下面以 LinearLayout
为例,分析 onLayout 里面的逻辑。orm
//LinearLayout 类
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
//垂直排列
layoutVertical(l, t, r, b);
} else {
//水平排列
layoutHorizontal(l, t, r, b);
}
}
复制代码
layoutVertical
和 layoutHorizontal
执行流程相似,就分析layoutVertical
//LinearLayout 类
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;
int childRight = width - mPaddingRight;
// Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();
/*省略代码*/
for (int i = 0; i < count; i++) {
//遍历子 View
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//获取子元素的宽度
final int childWidth = child.getMeasuredWidth();
//获取子元素的高度
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
//加上 子元素的 topMargin 属性值
childTop += lp.topMargin;
//设置子元素的 位置
//方法 1 ----->setChildFrame
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
/*加上子元素的高度、bottomMargin、偏移量,就是下一个子元素的 初始 top。 如此,子元素就从上到下排列,符合咱们所知的 LinearLayout 的特性*/
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
//方法 1 ----->setChildFrame
//LinearLayout 类
private void setChildFrame(View child, int left, int top, int width, int height) {
//最终又调用了子元素的 layout 方法.
child.layout(left, top, left + width, top + height);
}
复制代码
能够看到,在遍历子元素后,又调用了子元素的 layout 方法。子元素若是是继承 ViewGroup,仍是会调用到子元素的 onLayout 方法,遍历本身的子元素,调用本身子元素的 layout 方法,如此循环递归,就完成了整个 View 树的 layout 流程。
getWidth 获取的值和 getMeasureWidth 获取的值有什么不一样吗? 首先看 getWidth 源码:
//View 类
public final int getWidth() {
return mRight - mLeft;
}
复制代码
而后再看 mRight 和 mLeft 赋值(在咱们分析 layout 的时就有展示):
如下为超精简代码,哈哈:
//View 类
public void layout(int l, int t, int r, int b) {
//setOpticalFrame 最终也是调用了 setFrame 方法
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
}
//View 类
protected boolean setFrame(int left, int top, int right, int bottom) {
//对四个值进行赋值
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
复制代码
而后咱们再回顾调用 layout 的方法:
//LinearLayout 类
void layoutVertical(int left, int top, int right, int bottom) {
for (int i = 0; i < count; i++) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//将获取的 childWidth 和 childHeight 传入进去
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
}
}
//LinearLayout 类
private void setChildFrame(View child, int left, int top, int width, int height) {
//子元素 layout 的方法中传入的 right = left + width
//子元素 layout 的方法中传入的 bottom = left + height
child.layout(left, top, left + width, top + height);
}
复制代码
这下就一目了然了,在 getWidth()
方法中 mRight - mLeft
其实就是等于 childWidth
,也就是 getWidth() = getMeasureWidth()
。
那么,他们两个有不相等的时候吗?有的:
//自定义 View
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//手动改变传入的值
super.onLayout(changed, left, top, right+100, bottom+100);
}
复制代码
若是咱们在自定义 View 中重写 onLayout 方法,并手动改变传入的值, getWidth()
和 getMeasureWidth()
的值天然就不同了。不过这样作好像没什么意义?
还有一种状况就是在某些状况下,View 须要屡次 measure 才能肯定本身的测量宽/高,在前几回测量的时候,其得出的测量宽/高 ( getMeasureWidth()/ getMeasureHeight()) 和最终宽/高 ( getWidth()/ getHeight()) 可能不一致,可是最终他们的值仍是相等的。(这段话摘自刚哥的《Android 开发艺术探索》)
getHeight 和 getMeasuredHeight 过程相似,这里就不分析了。
因此咱们在平常开发中,咱们能够认为二者就是相等的。
另:可能仍是会有疑惑,这里只分析了 LinearLayout,那么其余布局也适用这个结论么?
FrameLayout 类
//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 */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
//从这里看出 FrameLayout 也是适用这个结论的
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
复制代码
RelativeLayout 类
//RelativeLayout 类
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
//Relativelayout 这里传入的是 LayoutParams 中的属性
//st.mRight 和 st.mBottom 赋值很复杂,不过他们也是适用这个结论的,具体能够查看源码分析
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
复制代码
其余咱们平常开发使用的布局也是适用于这个结论的。
《Android开发艺术探索》第四章-View的工做原理