美文网首页
移动端布局原理

移动端布局原理

作者: choosingSl | 来源:发表于2020-06-27 10:57 被阅读0次

    1. 背景

    • 布局是界面展示的重要一环,前端展示:布局 + 渲染
    • 需要适配多种屏幕
    • 既可以在 iPhone 上使用,又可以在 iPad 上使用
    • 可以在横屏下使用
    • 在不同的屏幕尺寸下,有不同的布局方式(比如屏幕小,就一行放一个,屏幕大一行放两个)
    • 良好的布局方式,可以加快代码的编码,提升扩展性,减轻cpu的压力,增加页面展示渲染速度,提升用户体验。
    • 文字,颜色,字体大小甚至各个视图控件布局都能够在发版之后能够修改以弥补一些前期考虑不周

    2.各种布局方式比较

    2.0 autoresizing

    iOS6 之前的自动布局方式,主要解决旋转和缩放问题

    autoresizingMask是UIView的属性,使用起来比较简单。如果你的应用的界面比较简单,就可以使用autoresizing进行自动布局

    typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
        UIViewAutoresizingNone                 = 0,
        UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
        UIViewAutoresizingFlexibleWidth        = 1 << 1,
        UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
        UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
        UIViewAutoresizingFlexibleHeight       = 1 << 4,
        UIViewAutoresizingFlexibleBottomMargin = 1 << 5
    };
    

    属性值 | 含义
    UIViewAutoresizingNone 不进行自动布局,不随父视图的改变而改变
    UIViewAutoresizingFlexibleLeftMargin 自动调整与父视图左边距,保持右边距不变
    UIViewAutoresizingFlexibleWidth 自动调整本身的宽度,保持和父视图的左右边距不变
    UIViewAutoresizingFlexibleRightMargin 自动调整与父视图右边距,保持左边距不变
    UIViewAutoresizingFlexibleTopMargin 自动调整与父视图上边距,保持下边距不变
    UIViewAutoresizingFlexibleHeight 自动调整本身的高度,保持上边距和下边距不变
    UIViewAutoresizingFlexibleBottomMargin 自动调整与父视图的下边距,保持上边距不变

    UIView的 autoresizesSubviews属性为YES时,上述属性才能生效,默认为YES。

    2.1 autoLayout 原理

    Auto Layout ,是苹果公司提供的一个基于约束布局,动态计算视图大小和位置的库,并且已经集成到了 Xcode 开发环境里

    Auto Layout 的原理就是对线性方程组或者不等式的求解。

    2.1.1 布局算法 Cassowary

    布局算法 Cassowary:
    它通过将布局问题(相等关系和不等关系)抽象成线性等式(https://zh.wikipedia.org/wiki/%E7%BA%BF%E6%80%A7%E6%96%B9%E7%A8%8B%E7%BB%84)和不等式(https://www.shuxuele.com/algebra/graphing-linear-inequalities.html)约束来进行求解;Cassowary 开发了一种规则系统,通过约束来描述视图间的关系。约束就是规则,这个规则能够表示出一个视图相对于另一个视图的位置。

    2.1.2 Auto Layout 的生命周期)

    Layout Engine布局引擎系统

    Constraints Change 表示的就是约束变化,添加、删除视图时会触发约束变化。Activating 或 Deactivating,设置 Constant 或 Priority 时也会触发约束变化。Layout Engine 在碰到约束变化后会重新计算布局,获取到布局后调用 superview.setNeedLayout(),然后进入 Deferred Layout Pass。Deferred Layout Pass 的主要作用是做容错处理。如果有些视图在更新约束时没有确定或缺失布局声明的话,会先在这里做容错处理。

    接下来,Layout Engine 会从上到下调用 layoutSubviews() ,通过 Cassowary 算法计算各个子视图的位置,算出来后将子视图的 frame 从 Layout Engine 里拷贝出来。在这之后的处理,就和手写布局的绘制、渲染过程一样了。所以,使用 Auto Layout 和手写布局的区别,就是多了布局上的这个计算过程。

    关于界面的渲染我感觉有必要在这里说下 Render Loop.
    Render Loop 是一个每秒钟跑120次的一个进程,是为了确保所有的内容都能为每一个frame做好准备。Render Loop 一共包括三个步骤来更新约束,布局和渲染。

    首先,每一个需要接收到更新约束的 view 会从子 view 向上传递,直到 window; 然后,每一个接收到的 view 开始 layoutsubviews,和更新约束是从相反的方向开始,layout 从 window 开始到每一个子 view 进行 layout。最后,每一个需要渲染的 view,和 layout 相同,从父 view 向子view 开始渲染。如下图:

    Render Loop 布局和渲染
    1. 更新约束:从子视图向外层逐级更新约束,直到 window;
    2. Layout 调整:从外部向内,逐级视图获得自身的 Layout,每一个接收到的 view 开始 layoutsubviews;
    3. 渲染与展示:与 Layout 相同,呈现顺序从外向内,使得视图呈现出来;

    Render Loop目的是为了避免重复的工作。
    举一个例子:一个UILable 需要一个约束来描述它的大小,但是有很多属性会影响他的大小,设置它的font,text size等等都会受到影响。当一个属性改变的时候,可能text其他属性也会被重新赋值
    ,很有可能调用一堆属性的setter方法,这样效率会很低。
    只需要调用updateConstraints 并指定好要更新的属性,Render Loop会帮助你计算好它的frame并完成渲染,从而避免多次设置的重复工作。

    Render Loop的方法调用

    在使用 Auto Layout 进行布局时,可以指定一系列的约束,比如视图的高度、宽度等等。而每一个约束其实都是一个简单的线性等式或不等式,整个界面上的所有约束在一起就明确地(没有冲突)定义了整个系统的布局。

    在涉及冲突发生时,Auto Layout 会尝试 break 一些优先级低的约束,尽量满足最多并且优先级最高的约束。

    • setNeedsLayout:告知页面需要更新,但是不会立刻开始更新。执行后会立刻调用layoutSubviews。
    • layoutIfNeeded:告知页面布局立刻更新。所以一般都会和setNeedsLayout一起使用。如果希望立刻生成新的frame需要调用此方法,利用这点一般布局动画可以在更新布局后直接使用这个方法让动画生效。
    • layoutSubviews:系统重写布局。
    • setNeedsUpdateConstraints:告知需要更新约束,但是不会立刻开始。
    • updateConstraintsIfNeeded:告知立刻更新约束。
    • updateConstraints:系统更新约束。
    约束内部原理
    1. 尽量不要删除所有的约束(Avoid removing all constraints);
    2. 若是一个静态约束,仅做一次添加操作即可;
    3. 仅改变需要改变的约束;
    4. 尽量不要做删除视图的操作,反之用 hide() 方法替代;

    Q:到底应该使用苹果自己的布局还是第三方工具比如 Texture ?
    A:使用苹果公司的技术得到的技术升级是持续的,而第三方不再维护的可能性是很高的。苹果公司经过一番努力,终于在 iOS 12 上用到了 Cassowary 算法的界面更新策略,使得 Auto Layout 的性能得到了大幅提升;你的代码一行不变就能享受到耗时从指数级下降到线性的性能提升。而很多第三方库,会随着 iOS 系统升级失去原有的优势。

    Q: A依赖B,B依赖C,C依赖D,BC有可能不存在,所以D的依赖有可能是ABC;
    A: B、C不存在即将其设置隐藏,高度为0或0.1; 表象不存在,深层存在,高或宽为0

    2.1.2 约束优先级

    约束优先级 – 为什么约束需要优先级?因为有的时候2个约束可能会有冲突。 比如:有一个UIView 距离父UIView的左右距离都是0,这是2个约束,此时再给此UIView加一个宽度约束,比如指定宽度为100,那么就会产生约束冲突了

    content Hugging/content Compression Resistance就是2个UIView自带的模糊约束。
    而这两个约束存在的条件则是UIView必须指定了 Intrinsic Content Size

    compression resistance
    为抗压缩约束,防止视图被压缩,约束默认的优先级为750(UILayoutPriorityDefaultHigh)。设置的API为:

    - (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis
    

    axis参数表示方向,水平或者垂直

    content hugging
    约束防止视图被拉伸,约束的默认优先级为250(UILayoutPriorityDefaultLow)。设置的API为:

    - (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis
    

    官方文档给出的视图与intrinsic content size:

    View Intrinsic content size
    UIView and NSView No intrinsic content size.
    Sliders Defines only the width
    Labels, buttons, switches, and text fields Defines both the height and the width.
    Text views and image views Intrinsic content size can vary.

    链接:https://www.jianshu.com/p/8acbe2885731

    2.1.3 Mansony

    对autoLAyout的一层封装,使用函数式编程更加方便

         action                make      update    remake
     已有某类型约束,再添加   不更新     更新       更新
     没有某类型约束,再添加   更新       更新       更新
     是否删除已有约束        不删除     不删除     全删
    

    2.2 flex-box 原理(web 布局方式)

    盒装模型: 盒子的尺寸=内容尺寸(width+height) + 内边距(padding) + 边框粗细(border) + 外边距(margin)

    网页布局(layout)是 CSS 的一个重点应用。

    2009年,W3C 提出了一种新的方案----Flex 布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。

    Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。

    flex-box 是web上的一种布局方式,也叫弹性布局

    Flexbox解决了什么?

    1. 方向性 (传统布局方向是从左到右,从上至下)
    2. 弹性伸缩 (传统尺寸定义是通过像素等来精确定义)
    3. 元素对齐(可以做到插拔)

    任何一个容器都可以指定为 Flex 布局。

    采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。

    领域特定语言(英语:domain-specific language、DSL)指的是专注于某个应用程序领域的计算机语言

    2.3 Android的五大容器布局体系

    Android中有六大布局,分别是:

    2.3.1. LinearLayout(线性布局),

    屏幕适配的使用 用的比较多的就是LinearLayout的weight(权重属性)

    线性布局的话是我们用的最多的一个布局方式,一种好的布局习惯是利用LinearLayout的weight布局参数和RelativeLayout相对布局来完成界面的布局

    线性布局是按照水平或垂直的顺序将子元素(可以是控件或布局)依次按照顺序排列,每一个元素都位于前面一个元素之后。线性布局分为两种:水平方向和垂直方向的布局。分别通过属性 android:orientation="vertical" 和 android:orientation="horizontal" 来设置。 android:layout_weight 表示子元素占据的空间大小的比例。

    2.3.2. RelativeLayout(相对布局),

    RelativeLayout 继承于 android.widget.ViewGroup,其按照子元素之间的位置关系完成布局的,作为 Android 系统五大布局中最灵活也是最常用的一种布局方式,非常适合于一些比较复杂的界面设计。

    2.3.3. TableLayout(表格布局)

    表格布局,适用于多行多列的布局格式,每个 TableLayout 是由多个 TableRow 组成,一个 TableRow 就表示 TableLayout 中的每一行,这一行可以由多个子元素组成。实际上 TableLayout 和 TableRow 都是 LineLayout 线性布局的子类。但是 TableRow 的参数 android:orientation 属性值固定为 horizontal,且 android:layout_width=MATCH_PARENT,android:layout_height=WRAP_CONTENT。所以 TableRow 实际是一个横向的线性布局,且所以子元素宽度和高度一致。

    2.3.4. FrameLayout(帧布局),

    FrameLayout 是最简单的布局了。所有放在布局里的控件,都按照层次堆叠在屏幕的左上角。后加进来的控件覆盖前面的控件。
    在 FrameLayout 布局里,定义任何空间的位置相关的属性都毫无意义。控件自动的堆放在左上角,根本不听你的控制。但是控件本身是可以控制自己内部的布局的。

    2.3.5. AbsoluteLayout(绝对布局),

    绝对布局中将所有的子元素通过设置 android:layout_x 和 android:layout_y 属性,将子元素的坐标位置固定下来,即坐标 (android:layout_x, android:layout_y) ,layout_x 用来表示横坐标,layout_y 用来表示纵坐标。屏幕左上角为坐标 (0,0),横向往右为正方,纵向往下为正方。实际应用中,这种布局用的比较少,因为 Android 终端一般机型比较多,各自的屏幕大小。分辨率等可能都不一样,如果用绝对布局,可能导致在有的终端上显示不全等。

    2.3.6. GridLayout(网格布局)

    GridLayout 使用虚细线将布局划分为行,列和单元格,同时也支持在行,列上进行交错排列 (虚线不存在的,是我们想象出来的)

    一般使用 GridLayout 的流程如下

    先定义组件的对其方式 android:orientation 水平或者竖直,设置多少行与多少列
    设置组件所在的行或者列,记得是从 0 开始算的,不设置默认每个组件占一行一列
    设置组件横跨几行或者几列;设置完毕后,需要在设置一个填充:android:layout_gravity = "fill"

    3. 性能比较

    frame > flexBox > autoLayout/masony

    4. 总结

    • 简单布局 可以直接frame解决
    • 追求性能和动态化;可以使用flexBox
    • 一般嵌套的布局autoLayout/masony 解决即可

    5. 参考

    相关文章

      网友评论

          本文标题:移动端布局原理

          本文链接:https://www.haomeiwen.com/subject/jbymfktx.html