上一篇我们分析Android View的测量。我们先回顾一下,View的测量,在ViewRootImpl#performTraverals方法下,先进行对DecorView根布局测量获取MeasureSpec,然后开始执行测量performMeasure(),通过View#measure找到对应View的核心onMeasure(),如果是ViewGroup,先递归子View,将父View的MeasureSpec和子View的LayoutParams作为参数而进行测量,然后逐层返回,不断保存ViewGroup的测量宽高。

好了,我们短短回顾后,回到ViewRootImpl#performTraverals方法:

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!

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,我们看一下该方法的源码:

    @Overridepublic 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 finishesmLayoutCalledWhileSuppressed = 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的位置是否发生过变化,看有没必要进行重新layoutif (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 bitint 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 positioninvalidate(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等对应值的,这个点我们后面再说,我们继续往下分析。

在setFrame()之后我们终于可以看到onLayout(),点进去查看View#onLayout方法:

public class View implements···{···protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}···
}public abstract class ViewGroup extends View implements·····@Overrideprotected 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来分析:

    @Overrideprotected 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);// 不为GONEif (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方法,里面的实现方法虽然有点长,但是比较好理解,无非加点空间想象力上去就无压力了。

我们梳理一下:首先是得到父布局的左上右下的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的绘制。

转载于:https://juejin.im/post/58b38e79b123db0052d0f2ef

你需要知道的Android View的布局相关推荐

  1. 每一位Android开发者应该知道的Android体系架构和开发库

    Android的体系架构鼓励组件重用,允许开发者发布共享Activity.Service并且访问其他应用程序的数据,还可以根据开发者制定的安全限制进行管理.话虽如此,今天我将分享一些关于Android ...

  2. 知乎android客户端drawer布局探究

    最近我的个人项目react-online-news用到drawer布局. https://github.com/xiaoshenke/React-Online-News 欢迎star fork. 在d ...

  3. android byte转string_唠点儿你不一定知道的Android小知识

    本文作者 作者:郑卫超 链接: https://vchao.blog.csdn.net/article/details/104349868 本文由作者授权发布. 1. YYYY 和 yyyy 不一样 ...

  4. 你应该知道的Android 7.0

    Android 7.0 Nougat 为用户和开发者引入多种新功能. 要详细了解 Android 7.0 的消费者功能,请访问 www.android.com. 多窗口支持 在 Android 7.0 ...

  5. 每个人都应该知道的Android Studio快捷方式

    Android Studio has a lot of shortcuts to offer. Today, we will be sharing some useful shortcuts whic ...

  6. 你应该知道的Android签名知识

    前言 最近帮测试做了一点关于签名的需求,今天就和各位同学简单聊一聊关于签名的那些事儿. 如果问到 Android 为什么需要签名?大家都可能想到官网的解释: Android 系统要求所有 APK 必须 ...

  7. 关于Android封装一个全局的BaseActivity你需要知道的

    关于Android封装一个全局的BaseActivity你需要知道的 1.前言 2.特点 3.代码及说明 3.1.优缺点 3.2.代码 3.3.注意点 4.总结 5.最后 1.前言 对于一个Andro ...

  8. 【错误记录】布局组件加载错误 ( Attempt to invoke virtual method ‘xxx$Callback android.view.Window.getCallback()‘ )

    文章目录 一.报错信息 二.解决方案 一.报错信息 使用环境 : Kotlin 编写的 Activity 使用了 kotlin-android-extensions 扩展插件 , 在 build.gr ...

  9. android view强制重绘_android view 相关方法 layout draw 布局 重绘 | 学步园

    http://blog.csdn.net/az44yao/article/details/8208087 ViewGroup用onLayout实现view的自由移动 http://qq18715568 ...

最新文章

  1. python 画出决策边界_决策边界可视化,让你的分类合理有序
  2. textField textView输入限制
  3. Zookeeper应用场景理解
  4. 07_UI基础_UITableView实战- 支付宝口碑
  5. 安装SQL2000 提示 以前的某个程序安装已在安装计算机上创建挂起的文件
  6. java开发工程师招聘软件,面试题附答案
  7. 用python画统计图表_Python数据科学(九)- 使用Pandas绘制统计图表
  8. 安卓页面去掉顶部标题
  9. 如何导出已有的谷歌插件,又如何把导出的插件安装到360浏览器中,又如何对插件小修小改?...
  10. Widows下TortoiseGit登录密码错误
  11. 西门子PLC中各个组织块OB作用
  12. [APK签名] .pk8、.x509.pem转化为.keystore签名
  13. rapidxml解析xml文档
  14. 电压负反馈放大电路(基于三极管)
  15. mysql查询之左连接查询与右连接查询
  16. OpenCV打开摄像头截图
  17. 弓形锯床主传动及工作机构设计
  18. 使用Ubuntu自带Disks工具扩展(扩容 )Vmware中ubuntu 20.04的硬盘空间
  19. Unity的Inspector
  20. 2008年4月28日A股市场存在筑底异样

热门文章

  1. 基于JSP实现校园二手交易平台
  2. markdownpad 2 pro版本(注册码)
  3. HTML中Css详细介绍
  4. HDU 1022[Train Problem I] 栈的应用
  5. 代码管理之SVN服务器及Git的创建与使用
  6. 【Nginx】epoll事件驱动模块
  7. 关于设计模式的创建型、结构型和行为型
  8. 如何向列表中添加数据值(管理员篇)
  9. tts代表_Text-to-Speech (TTS) Synthesis语音合成----控制语言合成
  10. java中属性文件读取案例_java相关:Spring中属性文件properties的读取与使用详解