编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1App启动流程梳理
  • 1.2ActivityThread分析
  • 1.3Context设计思想
  • 2.1Activity基础介绍
  • 2.2Activity启动流程
  • 2.3Activity布局创建
  • 2.4Activity布局绘制
  • 2.5Service基础介绍
  • 2.6Service启动流程
  • 2.7Receiver广播基础
  • 2.8Receiver深入原理
  • 2.9ContentProvider分析
  • 2.10Fragment实践
  • 2.11Intent深入思考
  • 3.1Paint和Canvas
  • 3.2View的绘制基础
  • 3.3onMeasure流程设计
  • 3.4onLayout流程设计
  • 3.5onDraw流程设计
  • 3.6View工作原理
  • 3.7View刷新设计流程
  • 3.8自定义View控件
  • 3.9自定义ViewGroup控件
  • 4.1Handler基础使用
  • 4.2消息机制流程分析
  • 4.3Handler深度解析
  • 4.4Message深度理解
  • 4.5MessageQueue解析
  • 4.6Looper深度解析
  • 4.7理解Handler同步屏障
  • 4.8ThreadLocal分析
  • 4.9ThreadLocal场景
  • 5.1View事件设计思考
  • 5.2View滑动冲突处理
  • 5.3View事件源码分析
  • 5.4View事件总结案例
  • 6.1DecorView设计思想
  • 6.2视图的载体View
  • 6.3视图管理者Window
  • 6.4窗口管理服务WMS
  • 6.5布局解析者Inflater
  • 7.1AsyncTask深入介绍
  • 7.2HandlerThread设计
  • 7.3IntentService设计
  • 8.1IPC通信方式介绍
  • 8.2AIDL进程间通信
  • 8.7Binder通信机制设计
  • /zh/android/basic/9.1注解设计思想和原理.html
  • 9.2APT技术设计详解
  • 9.3APT多种案例实践

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
      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.关于博客汇总链接

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.喜马拉雅音频汇总
  • 5.其他汇总

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
贡献者: yangchong211
上一篇
3.3onMeasure流程设计
下一篇
3.5onDraw流程设计