内容简介:根据 measure 测量出来的宽高,确定所有 View 的位置。View 本身的位置是通过它的四个点来控制的:以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删减。
作用
根据 measure 测量出来的宽高,确定所有 View 的位置。
具体分析
View 本身的位置是通过它的四个点来控制的:
以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删减。
layout 的流程
先通过 measure 测量出 ViewGroup 宽高,ViewGroup 再通过 layout 方法根据自身宽高来确定自身位置。当 ViewGroup 的位置被确定后,就开始在 onLayout 方法中调用子元素的 layout 方法确定子元素的位置。子元素如果是 ViewGroup 的子类,又开始执行 onLayout,如此循环往复,直到所有子元素的位置都被确定,整个 View 树的 layout 过程就执行完了。
在上一节 《View的绘制-measure流程详解》 中说过,View 的绘制流程是从 ViewRootViewImpl 中的 performMeasure()
、 performLayout
、 performDraw
开始的。在执行完 performMeasure()
后,开始执行 performLayout
方法:(以下源码有所删减)
//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 方法:
//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
的实现,发现只是一个抽象方法。
//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 ,它是没有子元素的,不需要确定子元素的位置,只需要确定自己的位置就够了。
在 ViewGroup 中是一个抽象方法,意思也很明显了,在控件继承自 ViewGroup 的时候,我们必须重写 onLayout
方法。因为如 LinearLayout
、 RelativeLayout
,他们的布局特性都是不一样的,需要各自根据自己的特性来进行制定确定子元素位置的规则。
下面以 LinearLayout
为例,分析 onLayout 里面的逻辑。
LinearLayout 的 onLayout 逻辑
//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 获取的值和 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); } } } 复制代码
其他我们日常开发使用的布局也是适用于这个结论的。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- View的绘制-measure流程详解
- View的绘制-draw流程详解
- 一文详解如何用 R 语言绘制热图
- Python绘制六种可视化图表详解(建议收藏)
- ViewGroup 默认顺序绘制子 View,如何修改?什么场景需要修改绘制顺序?
- Shader 绘制基础图形
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
疯狂科学家大本营
Bei Er Fei Ao Er / 本书翻译组 译、黄晓庆 周宇煜 张为民 审译 / Science Press / 2012-1-5 / 48.00元
美国最棒的创意工场不是贝尔实验室,不是硅谷,也不是麻省理工学院的媒体实验室,而是由五角大楼领导的绝密军事机构DARPA——国防高级研究计划局。DARPA是由美国前总统艾森豪威尔建立的军事部门,创建的目的是为了回应苏联的太空计划。 虽然DARPA属于政府机构,但是没有冷冰 冰的氛围和官僚做派,那里的科学家偏爱牛仔裤和运动鞋。不过他们最爱的还是在各个领域寻找颠覆性创意。从航空航天、IT,到能源领......一起来看看 《疯狂科学家大本营》 这本书的介绍吧!