3.4onLayout流程设计
目录介绍
- 01.Layout流程
- 1.1 ViewGroup的layout()
- 1.2 View的layout()
- 02.onLayout()流程
- 2.1 ViewGroup的onLayout()
- 2.2 View的onLayout()
- 03.View中layout和onLayout
- 04.View的布局流程
- 05.FrameLayout中onLayout解析
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong211/YCBlogs
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!
01.Layout流程
1.1 ViewGroup的layout()
- 源码如下所示
- 从源码中可以看出实际上调用的还是View的layout()方法。
@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); } //内部实际上是调用View的layout() super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutCalledWhileSuppressed = true; } }
1.2 View的layout()
- 测量完View大小后,就需要将View布局在Window中,View的布局主要通过确定上下左右四个点来确定的。
- 其中布局也是自上而下,不同的是ViewGroup先在layout()中确定自己的布局,然后在onLayout()方法中再调用子View的layout()方法,让子View布局。在Measure过程中,ViewGroup一般是先测量子View的大小,然后再确定自身的大小。
public void layout(int l, int t, int r, int b) { // 当前视图的四个顶点 int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; // setFrame() / setOpticalFrame():确定View自身的位置 // 即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //如果视图的大小和位置发生变化,会调用onLayout() if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { // onLayout():确定该View所有的子View在父容器的位置 onLayout(changed, l, t, r, b); ... }
- 上面看出通过 setFrame() / setOpticalFrame():确定View自身的位置,通过onLayout()确定子View的布局。
- setOpticalFrame()内部也是调用了setFrame(),所以具体看setFrame()怎么确定自身的位置布局。
protected boolean setFrame(int left, int top, int right, int bottom) { ... // 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点 // 即确定了视图的位置 mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); }
- 确定了自身的位置后,就要通过onLayout()确定子View的布局。onLayout()是一个可继承的空方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
- 注意要点:
- 如果当前View就是一个单一的View,那么没有子View,就不需要实现该方法。
- 如果当前View是一个ViewGroup,就需要实现onLayout方法,该方法的实现个自定义ViewGroup时其特性有关,必须自己实现。
- 由此便完成了一层层的的布局工作。
02.onLayout()流程
2.1 ViewGroup的onLayout()
- 查看源码如下所示
- ViewGroup中的onLayout方法是抽象方法,当我们的自定义View继承ViewGroup时需要我们自己去实现
/** * 作用:计算该ViewGroup包含所有的子View在父容器的位置() * 注: * a. 定义为抽象方法,需重写,因:子View的确定位置与具体布局有关,所以onLayout()在ViewGroup没有实现 * b. 在自定义ViewGroup时必须复写onLayout()!!!!! * c. 复写原理:遍历子View 、计算当前子View的四个位置值 & 确定自身子View的位置(调用子View layout()) * */ @Override protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
2.2 View的onLayout()
- 查看源码如下所示
- 由于继承View的自定义控件不包含子控件,所以View的onLayout()方法是空实现。
/** * onLayout() * 注:对于单一View的laytou过程 * a. 由于单一View是没有子View的,故onLayout()是一个空实现 * b. 由于在layout()中已经对自身View进行了位置计算,所以单一View的layout过程在layout()后就已完成了 */ protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
03.View中layout和onLayout
- 关于布局有两个重要的方法,View.layout()和View.onLayout(),这两个方法有什么关系?各自的作用是什么呢?他们都是定义在 View 中的,不同的是layout()方法中有很长一段实现的代码,而onLayout()确实一个空的实现,里面什么事也没做。
- 首先我们要明确布局的本质是什么,布局就是为 View 设置四个坐标值,这四个坐标值保存在View的成员变量 mLeft、mTop、mRight、mBottom中,方便View在绘制(onDraw)的时候知道应该在那个区域内绘制控件。而我们看到 layout() 方法中实际上就是为这几个成员变量赋值的,所以到底真正设置坐标的是layout()方法,那onLayout()的作用是什么呢?
- onLayout()都是由ViewGroup的子类实现的,他的作用就是确定容器中每个子控件的位置,由于不同的容器有不容的布局策略,所以每个容器对onLayout()方法的实现都不同,onLayout()方法会遍历容器中所有的子控件,然后计算他们左上右下的坐标值,最后调用child.layout()方法为子控件设置坐标;由于layout()方法中又调用了onLayout()方法,如果子控件child也是一个容器,就会继续为它的子控件计算坐标,如果child不是容器,onLayout()方法将什么也不做,这样下来,只要Activity根窗口mDecor的layout()方法执行完毕,窗口中所有的子容器、子控件都将完成布局操作。
04.View的布局流程
- View的布局流程:
img
05.FrameLayout中onLayout解析
- FrameLayout 的 onLayout 方法比较简单,这里直接通过注释的形式进行说明。
@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(); // 可使用的父 view 的左边界范围,这里的边界是综合考虑了 foreground 和 padding的 final int parentLeft = getPaddingLeftWithForeground(); // 右边界。right 和 left 分别是指左右两边的 X 坐标 // 两者相减,再减去右边的 padding 可以得到父 view 的右边界范围。 final int parentRight = right - left - getPaddingRightWithForeground(); // 与上面类似,就不重复写了 final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); // 遍历每一个 view,设置每一个 view 的位置。 for (int i = 0; i < count; i++) { final View child = getChildAt(i); // 只操作可见 view if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); // width 和 height 分别是 Measure 过后的 宽和高 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: // gravity 是水平居中的情况 // 左坐标的计算可以分为两部分 // 1. 可使用的父 view 的左边界范围 + 放置view的中间位置(父view可用范围 减去 view 宽度后的一半) // 2. 移除右 margin 加上 左margin 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: // 默认情况,加上左 margin 就行。 childLeft = parentLeft + lp.leftMargin; } 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); } } }
其他介绍
01.关于博客汇总链接
02.关于我的博客
- 我的个人站点:
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 开源中国:https://my.oschina.net/zbj1618/blog
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e