iOS - FlexBox 布局之 YogaKit

为什么要了解 FlexBox?

最近时不时的听到关于 FlexBox 的声音,除了在 Weex 以及 React Native 两个著名的跨平台项目里有用到 FlexBox 外,AsyncDisplayKit 也同样引入了 FlexBox 。

先说说 iOS 本身提供给我们 2 种布局方式:

  • Frame,直接设置横纵坐标,并指定宽高。
  • Auto Layout,通过设置相对位置的约束进行布局。

Frame 没什么太多可说的了,直接制定坐标和大小,设置绝对值。

Auto Layout 本身用意是好的,试图让我们从 Frame 中解放出来,摆脱关于坐标和大小的刻板思考方式。转而利用 UI 之间的相对位置关系,设置对应约束进行布局。

但是 Auto Layout 好心并未做成好事,它的语法又臭又长! 至今学习 iOS 两年,我使用到原生 Auto Layout 语法的时候屈指可数。只能靠 Masonry 这样的第三方库来使用它。

Auto Layout 的原理

说完了 Auto Layout 的使用,再来看看它工作原理。

实际上,我们设置 Auto Layout 的约束,就构成一系列的条件,成为一个方程。然后解出 Frame 的坐标和大小。

例如,我们设置一个名为 A 的 UI :

A.center = super.center
A.width  = 40
A.height = 40
复制代码

则: A.frame = (super.center.x-40/2,super.center.y-40/2,40,40)

再设置一个 B:

B.width  =  A.width
B.height =  A.height
B.top    =  A.bottom + 50
B.left   =  A.left
复制代码

则: B.frame = ( A.x , A.y + A.height + 50 , A.width , A.height )

如图:

Cassowary

Auto Layout 内部有专门用来处理约束关系的算法,我一直以为是苹果自家研发的,查阅资料才发现是来自一个叫 Cassowary 的算法。

Cassowary是个解析工具包,能够有效解析线性等式系统和线性不等式系统,用户的界面中总是会出现不等关系和相等关系,Cassowary开发了一种规则系统可以通过约束来描述视图间关系。约束就是规则,能够表示出一个视图相对于另一个视图的位置。

有兴趣的可以进一步了解该算法的实现。

Frame / Auto Layout / FlexBox 的性能对比

在对 Auto Layout 进行一番了解之后,我们很容易得出 Auto Layout 因为多余的计算,性能差于 Frame 的结论。

但究竟差多少呢?FlexBox 的表现又如何呢?

这里根据 从 Auto Layout 的布局算法谈性能 里的测试代码进行修改,对 Frame / Auto Layout / FlexBox 进行布局,分段测算 10 ~ 350 个 UIView 的布局时间。取 100 次布局时间的平均值作为结果,耗时单位为秒。

结果如下图:

虽然测试结果难免有偏差,但是根据折线图可以明显发现,FlexBox 的布局性能是比较接近 Frame 的。

60 FPS 作为一个 iOS 流畅度的黄金标准,要求布局在 0.0166667 s 内完成,Auto Layout 在超过 50 个视图的时候,可能保持流畅就会开始有问题了。

本次测试使用的机器配置如下:

采用 Xcode9.2 ,iPad Pro (12.9-inch)(2nd generation) 模拟器。

测试布局的项目代码上传在 GitHub

FlexBox 是什么?

FlexBox 是一种 UI 布局方式,并得到了所有浏览器的支持。FlexBox 首先是基于 盒装状型 的,Flexible 意味着弹性,使其能适应不同屏幕,补充盒状模型的灵活性。

FlexBox 把每个视图,都看作一个矩形盒子,拥有内外边距,沿着主轴方向排列,并且,同级的视图之间没有依赖。

和 Auto Layout 类似,FlexBox 采用了描述性的语言去进行布局,而不像 Frame 直接用绝对值坐标进行布局。

弹性布局的主要思想是让 Flex Container 有能力来改变 Flex Item 的宽度和高度,以填满可用空间(主要是为了容纳所有类型的显示设备和屏幕尺寸)的能力。

最重要的是, FlexBox 布局与方向无关,常规的布局设计缺乏灵活性,无法支持大型和复杂的应用程序(特别是涉及到方向转变,缩放、拉伸和收缩等)。

FlexBox 组成

采用 FlexBox 布局的元素,称为 Flex Container

Flex Container 的所有子元素,称为 Flex Item

下面会讲一下 FlexBox 里面的一些概念,方便之后进行 FlexBox 的使用。

Flex Container

前面提到了,FlexBox 的一个特点,就是视图之间,是没有依赖的。

Flex Item 的排布,就依赖于 Flex Container 的属性设置,而不用相互之间进行设置。

所以先说一下 Flex Containner 的属性设置。

Flex Direction

FlexBox 有一个 主轴(main axis) 和 侧轴(cross axis)的概念。侧轴垂直于主轴。

它们可以是水平,也可以是垂直。

主轴默认为 Row , 侧轴默认为 Column

Flex Direction 决定了 Flex Containner 内的主轴排布方向。

主轴默认为 Row (从左到右):

同时,也可以设置 RowRevers(从右至左):

Column(从上到下):

ColumnRevers(从下到上):

Flex Wrap

Flex Wrap 决定在轴线上排列不下时,视图的换行方式。

Flex Wrap 默认设置为 NoWrap,不会换行,一直沿着主轴排列到屏幕之外:

设置为 Wrap ,则空间不足时,自动换行:

设置 WrapReverse,则换行方向与 Wrap 相反:

这是一个非常有用的属性。比如典型的九宫格布局,iOS 如果不是用 UICollectionView 做,那么就需要保存 9个实例,然后做判断,计算 frame ,可维护性实在不高。使用UICollectionView 可以很好的解决布局,但很多场景并不能复用,做起来也不是特别简单。

FlexBox 布局的话,用 Flex Wrap 属性设置 Wrap 就可以直接搞定。

移动平台上相似的方案,比如 Android 的 Linear Layout 和 iOS 的 UIStackView ,但却远没有 FlexBox 强大。

Display

Display 选择是否计算它,默认为 Flex. 如果设置为 None 自动忽略该视图的计算。

在根据逻辑显示 UI 时,比较有用。

比如我们现有的业务,需要显示的腾讯身份标示。按照一般做法,多个 icon 互相连成一排,根据身份去设置不同的距离,同时隐藏其他 icon ,比较的麻烦。iOS 最好的办法是使用 UIStackView ,这又有版本兼容等问题。而使用 FlexBox 布局,当不是某个身份时,只要设置 Display 为 None,就不会被纳入 UI 计算当中。

Justify Content

Justify Content 用于定义 Flex Item 在主轴上的对齐方式:FlexStart(主轴起点对齐),FlexEnd(主轴终点对齐),Center(居中对齐)。

还有SpaceBetween(两端对齐):

设置两端对齐,让 Flex Item 之间的间隔相等。

SpaceAround(外边距相等排列):

让每个 Flex Item 四周的外边距相等

Align Items

Align Items 定义 Flex Item 在侧轴上的对齐方式。

Align Items 可以和主轴对齐方式  Justify Content 一样,设置FlexStart ,FlexEnd,Center,SpaceBetween,SpaceAround 。

Align Items 还可以设置 Baseline(基线对齐):

如图所示,它是基于 Flex Item 的第一行文字的基线对齐。

如果 Baseline 和 Flex Item 的行内轴与侧轴为同一条,则该值与 FlexStart 等效。 其它情况下,该值将参与基线对齐。

Align Items 还可以设置为 Stretch:

Stretch 让 Flex Item 拉伸填充整个Flex ContainerStretch会使Flex Item的外边距在遵照对应属性限制下,尽可能接近所在行或列的尺寸。

如果 Flex Item 未设置数值,或设为 auto,将占满整个Flex Container的高度

Align Content

Align Content 也是侧轴在 Flex Item 里的对齐方式,只不过是以一整个行,作为最小单位。

注意,如果Flex Item只有一根轴线(只有一行的Flex Itme),该属性不起作用。

调整为 FlexWrap 为 Wrap,效果才显示出来:

Flex Item

在上面说完了 Flex Container 的属性,终于说到了 Flex Item.  Flex Container 里的属性,都是作用于自己包含的 Flex ItemFlex Item 的属性,都是作用于自己本身,.

AlignSelf

AlignSelf 可以让单个 Flex Item 与其它 Flex Item 有不一样的对齐方式,覆盖 Align Items属性。

默认值为auto,表示继承Flex ContainerAlign Items属性。如果它本身没有Flex Container,则等同于Stretch

FlexGrow

FlexGrow 可以设置分配剩余空间比例。即如何扩大。

FlexGrow 默认值为 0,如果没有去定义 FlexGrow,该布局是不会拥有分配剩余空间权利的。

例如:

整体宽度 100 , sub1 宽为 10 ,sub2 宽为 20 ,则剩余空间为 70。

设置 FlexGrow 就是分配这 70 宽度的比例。

再说比例值的问题:

如果所有 Flex Item 的 FlexGrow 属性都为 1 ,如果有剩余空间的话,则等分剩余空间。

如果一个 Flex Item 的 FlexGrow 属性为 2,其余 Flex Item 都为 1 ,则前者占据的剩余空间将比其他 Flex Item 多 1 倍。

FlexShrink

与 FlexGrow 处理空间剩余相反,FlexShrink 用来处理空间不足的情况。即怎么缩小。

FlexShrink 默认为1,即如果空间不足,该项目将缩小

如果所有 Flex Item 的 FlexShrink 属性都为 1,当空间不足时,都将等比例缩小。

如果一个 Flex Item 的 FlexShrink 属性为 0 ,其余 Flex Item 都为1,则空间不足时,FlexShrink 为 0 的前者不缩小。

FlexBasis

FlexBasis 定义了在分配多余的空间之前, Flex Item 占据的 main size(主轴空间)。浏览器根据这个属性,计算主轴是否有多余空间。

FlexBasis 的默认值为 auto,即 Flex Item 的本来大小。

想了解更多 FlexBox 属性,可以参考 A Complete Guide to Flexbox

FlexBox 的实现 -- Yoga

最开头已经介绍过,FlexBox 布局已经应用于几个知名的开源项目,它们用到的就是来自于 Facebook 的 Yoga.

Yoga 是由 C 实现的 Flexbox 布局引擎,性能和稳定性已经在各大项目中得到了很好的验证,但不足的是 Yoga 只实现了 W3C 标准的一个子集。

下面将针对 Yoga iOS 上的实现 YogaKit 做一些讲解。

基于上面对 FlexBox 布局的基本了解,作一些简单的布局。

YGLayout

整个 YogaKit 的关键,就在于  YGLayout 对象当中。通过  YGLayout 来设置布局属性。

在 UIView+Yoga.h 的文件里:

/**
 The YGLayout that is attached to this view. It is lazily created.
 */
@property (nonatomic, readonly, strong) YGLayout *yoga;

/**
 In ObjC land, every time you access `view.yoga.*` you are adding another `objc_msgSend`
 to your code. If you plan on making multiple changes to YGLayout, it‘s more performant
 to use this method, which uses a single objc_msgSend call.
 */
- (void)configureLayoutWithBlock:(YGLayoutConfigurationBlock)block
    NS_SWIFT_NAME(configureLayout(block:));

可以看到一个名为 yoga 的 YGLayout 只读对象,和 configureLayoutWithBlock:(YGLayoutConfigurationBlock)block 方法,并且还使用了  NS_SWIFT_NAME() 来定义在 Swift 里的方法名。

这样我们就可以直接使用 UIView 的实例对象,来直接设置它对应的布局了。

isEnabled

YGLayout.h 里是这么定义 isEnabled 的。

/**
 The property that decides during layout/sizing whether or not styling properties should be applied.
 Defaults to NO.
 */
@property (nonatomic, readwrite, assign, setter=setEnabled:) BOOL isEnabled;

isEnabled 默认为 NO,需要我们在布局期间设置为 YES,来开启 Yoga 样式.

applyLayoutPreservingOrigin:

对于这个方法,头文件里是这么解释的:

/**
 Perform a layout calculation and update the frames of the views in the hierarchy with the results.
 If the origin is not preserved, the root view‘s layout results will applied from {0,0}.
 */
- (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
    NS_SWIFT_NAME(applyLayout(preservingOrigin:));

简单来说,就是用于执行 layout 计算的。所以,一旦在布局代码完成之后,就要在根视图的属性 yoga 对象上调用这个方法,应用布局到根视图子视图

布局演示

下面通过实例来介绍如何使用 Yoga 进行 FlexBox 布局。

居中显示

[self configureLayoutWithBlock:^(YGLayout * layout) {
                layout.isEnabled = YES;
                layout.justifyContent =  YGJustifyCenter;
                layout.alignItems     =  YGAlignCenter;
            }];

[self.redView configureLayoutWithBlock:^(YGLayout * layout) {
                layout.isEnabled = YES;
                layout.width=layout.height= 100;
            }];

[self addSubview:self.redView];

[self.yoga applyLayoutPreservingOrigin:YES];

效果如下:

我们真正的布局代码,只用设置 Flex Container 的 justifyContent 和 alignItems 就可以了.

嵌套布局

让一个 view 略小于其 superView,边距为10:

    [self.yellowView configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;
                layout.margin = 10;
                layout.flexGrow = 1;
            }];
    [self.redView addSubview:self.yellowView];

效果如下:

布局代码只用设置, View 的 margin 和 flexGrow.

等间距排列

纵向等间距的排列一组 view:

            [self configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;

                layout.justifyContent =  YGJustifySpaceBetween;
                layout.alignItems     =  YGAlignCenter;
            }];

            for ( int i = 1 ; i <= 10 ; ++i )
            {
                UIView *item = [UIView new];
                item.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
                                                  saturation:( arc4random() % 128 / 256.0 ) + 0.5
                                                  brightness:( arc4random() % 128 / 256.0 ) + 0.5
                                                       alpha:1];
                [item  configureLayoutWithBlock:^(YGLayout *layout) {
                    layout.isEnabled = YES;

                    layout.height     = 10*i;
                    layout.width      = 10*i;
                }];

                [self addSubview:item];
            }

效果如下:

只要设置 Flex Container 的 layout.justifyContent = YGJustifySpaceBetween,就可以很轻松的做到。

等间距,自动设宽

让两个高度为 100 的 view 垂直居中,等宽,等间隔排列,间隔为10.自动计算其宽度:

          [self configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;
                layout.flexDirection  =  YGFlexDirectionRow;
                layout.alignItems     =  YGAlignCenter;

                layout.paddingHorizontal = 5;
            }];

            YGLayoutConfigurationBlock layoutBlock =^(YGLayout *layout) {
                layout.isEnabled = YES;

                layout.height= 100;
                layout.marginHorizontal = 5;
                layout.flexGrow = 1;
            };

            [self.redView configureLayoutWithBlock:layoutBlock];
            [self.yellowView configureLayoutWithBlock:layoutBlock];

            [self addSubview:self.redView];
            [self addSubview:self.yellowView];

效果如下 :

我们只要设置 Flex Container 的 paddingHorizontal ,以及 Flex Item的marginHorizontal,flexGrow 就可以了。并且可以复用  Flex Item 的 layout 布局样式。

UIScrollView 排列自动计算 contentSize

在 UIScrollView 顺序排列一些 view,并自动计算 contentSize

            [self configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;
                layout.justifyContent =  YGJustifyCenter;
                layout.alignItems     =  YGAlignStretch;
            }];

            UIScrollView *scrollView = [[UIScrollView alloc] init] ;
            scrollView.backgroundColor = [UIColor grayColor];
            [scrollView configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;

                layout.flexDirection = YGFlexDirectionColumn;
                layout.height =500;
            }];
            [self addSubview:scrollView];

            UIView *contentView = [UIView new];
            [contentView configureLayoutWithBlock:^(YGLayout * _Nonnull layout) {
                layout.isEnabled = YES;
            }];

            for ( int i = 1 ; i <= 20 ; ++i )
            {
                UIView *item = [UIView new];
                item.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
                                                  saturation:( arc4random() % 128 / 256.0 ) + 0.5
                                                  brightness:( arc4random() % 128 / 256.0 ) + 0.5
                                                       alpha:1];
                [item  configureLayoutWithBlock:^(YGLayout *layout) {
                    layout.isEnabled = YES;

                    layout.height     = 20*i;
                    layout.width      = 100;
                    layout.marginLeft = 10;
                }];

                [contentView addSubview:item];
            }

            [scrollView addSubview:contentView];
            [scrollView.yoga applyLayoutPreservingOrigin:YES];
            scrollView.contentSize = contentView.bounds.size;

效果如下:

布置 UIScrollView 主要是使用了一个中间 contentView,起到了计算 scrollview 的 contentSize 的作用。这里要注意的是,要在scrollview调用完 applyLayoutPreservingOrigin: 后进行设置,否则得不到结果。

UIScrollView 的用法,目前在网上也没找到比较官方的示例,完全是笔者自己摸索的,欢迎知道的大佬指教。

上面所用的示例代码,已经上传至 GitHub

总结

FlexBox 的确是一个非常适用于移动端的布局方式,语意清晰,性能稳定,现在移动端 UI 视图越来越复杂,尤其是在所有浏览器都已经支持了 FlexBox 之后,作为移动开发者有必要了解新的解决方式。

大家在熟练使用 YogaKit 的方式之后,也可以尝试自己封装一套布局代码,加快开发效率。

参考:

Flex 布局教程:语法篇

FlexBox 布局模型

YogaKit

Yoga Tutorial: Using a Cross-Platform Layout Engine

从 Auto Layout 的布局算法谈性能

原文地址:https://www.cnblogs.com/baitongtong/p/11778738.html

时间: 2024-10-14 21:07:26

iOS - FlexBox 布局之 YogaKit的相关文章

【前端攻略】最全面的水平垂直居中方案与flexbox布局

最近又遇到许多垂直居中的问题,这是Css布局当中十分常见的一个问题,诸如定长定宽或不定长宽的各类容器的垂直居中,其实都有很多种解决方案.而且在Css3的flexbox出现之后,解决各类居中问题变得更加容易了.搜了搜园子内关于flexbox的文章觉得很多不够详尽,故想借介绍flexbox的同时好好总结一番各类垂直居中的方法. 由简至繁: 行内元素的水平居中     要实现行内元素(<span>.<a>等)的水平居中,只需把行内元素包裹在块级父层元素(<div>.<l

flexbox布局的兼容性

http://ayqy.net/blog/flexbox布局的兼容性/ 写在前面 flex布局早在2009年就有了,而现在是2015年6月8日,使用最新的flex语法会发现支持程度并不好,即使是在“高端”浏览器上也是如此,比如Chrome.Firefox.Safari.Android.IOS Safari下支持程度各不相同 网上现有的代码中充斥着各种版本,在Chrome下运行一般都没有问题,Firefox一般也还好,但Android与IOS Safari下就显得非常无力了.之所以会出现这样的局面

3、手把手教React Native实战之flexbox布局

flexbox是Flexible Box的缩写,弹性盒子布局  主流的浏览器都支持 flexbox布局是伸缩容器(container)和伸缩项目(item)组成 Flexbox布局的主体思想是元素可以改变大小以适应可用空间,当可用空间变大,Flex元素将伸展大小以填充可用空间,当Flex元素超出可用空间时将自动缩小.总之,Flex元素是可以让你的布局根据浏览器的大小变化进行自动伸缩. 按照伸缩流的方向布局 伸缩容器有主轴和交叉轴组成! 主轴既可以是水平轴,也可以是垂直轴 flexbox目前还处于

CSS3弹性盒模型flexbox布局基础版

原文链接:http://caibaojian.com/using-flexbox.html 最近看了社区上的一些关于flexbox的很多文章,感觉都没有我这篇文章实在,最重要的兼容性问题好多人都没有提出解决方案.另外本人2014年5月还废寝忘食的翻译了国外的<CSS3弹性盒模型flexbox完整教程>和<CSS3弹性盒模型flexbox布局实例>,这么好的文章没有人来发现,实在是遗憾. 文章写作背景 查询Can I use上的使用情况,发现最新的浏览器基本支持这个属性,IE10开始

3、手把手教React Native实战之flexbox布局(RN基础)

flexbox是Flexible Box的缩写,弹性盒子布局  主流的浏览器都支持 flexbox布局是伸缩容器(container)和伸缩项目(item)组成 Flexbox布局的主体思想是元素可以改变大小以适应可用空间,当可用空间变大,Flex元素将伸展大小以填充可用空间,当Flex元素超出可用空间时将自动缩小.总之,Flex元素是可以让你的布局根据浏览器的大小变化进行自动伸缩. 按照伸缩流的方向布局 伸缩容器有主轴和交叉轴组成! 主轴既可以是水平轴,也可以是垂直轴 flexbox目前还处于

Flexbox布局(转)

Flexbox布局( Flexible Box 或CSS3 弹性布局),是CSS3中的一种新的布局模式,是可以自动调整子元素的高和宽,来很好的填充任何不同屏幕大小的显示设备中的可用显示空间,收缩内容防止内容溢出,确保元素拥有恰当的行为的布局方式.使用Flexbox来布局更容易,可以使用更少的代码,更简单的方式实现更复杂的布局,例如对齐方式,排列方向,排列顺序(这也是Flexbox布局的核心能力所在),弹性盒中的子元素通过在各个方向放置就可以以弹性的尺寸适应父元素的显示区域.由于子元素的显示顺序和

HTML5移动开发之Flexbox布局讲解与使用技巧

现在来详细介绍一下Flexbox布局语法跟使用.大家可能非常关心以下几个问题: 1 .什么是Flexbox布局? 2. Flexbox布局主要用于什么场景? 3. Flexbox布局它的语法是什么? 4. 如何实际使用Flexbox布局? 5. Flexbox布局它的缺陷是什么? 下面就这五个问题给大家分析分析.希望给那些想尝试用Flexbox的行内人有所帮助. 在恰当的地方能够使用恰当的布局方法,如果有说的不到位,希望大家指正,一起进步. 1 .什么是Flexbox布局? 1.1 W3C解释

任务十:Flexbox 布局练习

面向人群: 有一定HTML及CSS基础的同学 难度: 中 重要说明 百度前端技术学院的课程任务是由百度前端工程师专为对前端不同掌握程度的同学设计.我们尽力保证课程内容的质量以及学习难度的合理性,但即使如此,真正决定课程效果的,还是你的每一次思考和实践. 课程多数题目的解决方案都不是唯一的,这和我们在实际工作中的情况也是一致的.因此,我们的要求不仅仅是实现设计稿的效果,更是要多去思考不同的解决方案,评估不同方案的优劣,然后使用在该场景下最优雅的方式去实现.那些最终没有被我们采纳的方案,同样也可以帮

flexbox布局

flexbox布局(RN基础) flexbox是Flexible Box的缩写, 弹性盒子布局 主流的浏览器都支持flexbox布局是伸缩容器(container)和伸缩项目 (item)组成Flexbox布局的主体思想是元素可以改变大小以适应可用空间, 当可用空间变大, Flex元素将伸展大小以填充可用空间, 当Flex元素超出可用空间时将自动缩小. 总之, Flex元素是可以让你的布局根据浏览器的大小变化进行自动伸缩.按照伸缩流的方向布局伸缩容器有主轴和交叉轴组成! 主轴既可以是水平轴, 也