也许你并不知道Flexbox是什么,但是想必你肯定听说过React Native、Weex、和Texture(AsyncDisplayKit),Flexbox就是这些知名布局库所采用的布局思路。甚至苹果官方在iOS9的时候推出的UIStackView,采用的也是FlexBox思路来实现布局的。
目前看来,iOS系统提供的布局方式有两种:
通过Masonry和SnapKit这些第三方库,自动布局的易用性也有了很大提升。并且在iOS12以后,苹果公司也已经解决了自动布局在性能方面的问题(详见Auto Layout浅析)。
那么在这种情况下,我们为什么还要关注其他布局思路呢?原因如下:
与自动布局类似,Flexbox也是使用的描述性的语言来布局。使用Flexbox布局的视图元素叫Flex容器(flex container),其子视图元素会自动成为容器成员,叫做Flex项目(flex item)。Flexbox布局的主要思想是,通过 Flex 容器设定的属性来改变内部 Flex 项目的宽高,并调整 flex 项目的位置来填充 flex 容器的可用空间。
如图所示,一个flex容器默认存在两根轴,水平的主轴(main axis)和垂直的交叉轴(cross axis)。主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end,交叉轴的开始位置叫做cross start,结束位置叫做cross end。
项目默认是沿主轴排列的,单个项目占据的主轴空间叫做mainSize,占据的交叉轴空间叫做crossSize。
目前我的工程还是纯原生开发,因此不能使用ReactNative或者Weex来体验Flexbox布局,不过倒是可以使用Texture来通过flexbox思路进行页面布局。
Texture 如何使用 Flexbox 思路进行布局?
Texture框架的布局方案考虑的是十分长远的,并且也已经十分成熟,虽然学习起来费些力气,但是性能上远好于苹果的自动布局。
Texture框架的布局中,Texture考虑到布局的扩展性,提供了一个基类ASLayoutSpec。这个基类提供了布局的基本能力,使得Texture可以通过它扩展实现多种布局思路,比如Inset、Overlay、Ratio、Relative、Absolute等布局思路,也可以继承自ASLayoutSpec来自定义你的布局算法。
ASLayoutSpec的子类及其具体的功能如下:
ASLayoutSpec子类实现了各种布局思路,ASLayoutSpec会制定各种布局相通的协议方法,遵循这些协议后,可以保证这些子类能够使用相同的规则去实现更丰富的布局。
通过ASLayoutSpec遵循的ASLayoutElement协议,可以知道ASLayoutSpec提供的基本能力有哪些。ASLayoutElement协议定义如下:
@protocol ASLayoutElement <ASLayoutElementExtensibility, ASTraitEnvironment, ASLayoutElementAsciiArtProtocol>
#pragma mark - Getter
@property (nonatomic, assign, readonly) ASLayoutElementType layoutElementType;
@property (nonatomic, strong, readonly) ASLayoutElementStyle *style;
- (nullable NSArray<id<ASLayoutElement>> *)sublayoutElements;
#pragma mark - Calculate layout
/**
* 要求节点根据给定的大小范围返回布局
*/
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize;
/**
* 在子 layoutElement 上调用它来计算它们在calculateLayoutThatFits: 方法里面实现的布局
*/
- (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)parentSize;
/**
* 重写此方法以计算 layoutElement 的 布局.
*/
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize;
/**
* 重写此方法允许你接收 layoutElement 的大小。使用这些值可以计算最终的约束大小,但这个方法要尽量少用。
*/
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
restrictedToSize:(ASLayoutElementSize)size
relativeToParentSize:(CGSize)parentSize;
- (BOOL)implementsLayoutMethod;
@end
通过上面的代码可以看出,协议定义了layoutThatFits: 和 calculateLayoutThatFits: 等回调方法。其中,layoutThatFits 回调方法用来要求节点根据给定的大小范围返回布局,重写calculateLayoutThatFits方法用以计算layoutElement的布局。定义了统一的协议方法,能让ASLayoutSpec统一透出布局计算能力,统一规范的协议方法,也有利于布局算法的扩展。
接下来我们来看看ASLayoutSpec的子类中,应用最广泛的ASStackLayoutSpec。它和iOS中自带的UIStackView类似,布局思路参照了Flexbox,比如horizontalAlignment、alignItems、flexWrap等属性很容易和Flexbox对应上。
下面是一段ASStackLayoutSpec示例代码,我们可以通过示例了解如果通过Texture使用Flexbox布局思路开发界面:
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constraint
{
// 创建一个纵轴方向的 ASStackLayoutSpec 视图容器 vStack
ASStackLayoutSpec *vStack = [[ASStackLayoutSpecalloc] init];
// 设置两个子节点,第一个节点是标题,第二个正文内容
[vStack setChildren:@[titleNode, bodyNode]];
// 创建一个横轴方向的 ASStackLayoutSpec 视图容器 hstack
ASStackLayoutSpec *hstack = [[ASStackLayoutSpecalloc] init];
hStack.direction = ASStackLayoutDirectionHorizontal;
hStack.spacing = 5.0; // 设置节点间距为 5
// 在 hStack 里添加 imageNode 和 vStack 节点
[hStack setChildren:@[imageNode, vStack]];
// 创建一个 ASInsetLayoutSpec 容器,设置四周边距为 5,将 hStack 作为其子节点
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(5,5,5,5) child:hStack];
return insetSpec;
}
上面这段代码,首先会创建一个纵轴方向的ASStackLayoutSpec视图容器vStack;然后,为vStack设置两个子节点,第一个子节点是标题,第二个子节点是正文内容;接下来,创建一个横轴方向的ASStackLayoutSpec视图容器hstack,在hstack里添加imageNode和vStack节点;最后,创建一个ASInsetLayoutSpec容器,设置四周边距为5,将hStack作为其子节点。
上面示例代码对应的视图效果如下:
除了Texture用到Flexbox的布局思想以外,ReactNative和Weex也用到了Flexbox布局思路,这两个框架对Flexbox布局思想的实现,通过一个叫Yoga的C++库。
除了React Native、Weex外,Yoga还为很多其他的开源框架提供支持,比如Litho、ComponentKit等。
Yoga布局库是对Texture布局思想的实现,是有C/C++语言编写的,依赖少、编译后的二进制文件也小,基于此,Yoga可以用于多平台,可以很方便地集成到Android和iOS上。
除了Flexbox思路的布局ASStackLayoutSpec以外,Texture中还有wrapper、inset、overlay、ratio、relative、absolute等针对不同场景的布局思路,同时还支持自定义布局算法。
接下来我们就聊聊Flexbox布局的算法是怎样的。了解Flexbox布局算法设计,一方面能够让你更好地理解flexbox布局;另一方面,你也可以借此完整地了解一个布局算法是怎么设计的,使得你以后也能够设计出适合自己业务场景的布局算法。
Flexbox算法
Flexbox算法的主要思想是:让flex容器能够改变其flex项目的宽高和顺序,以填充可用空间,flex容器可以通过扩大flex项目来填充可用空间,或者缩小flex项目来使其不超出可用空间。
Flexbox算法的实现是非常复杂的,不是一两句话就能说明白的,可以参考如下网址,或者点击“阅读原文”窥其一二:
https://www.cnblogs.com/yunqishequ/p/10006872.html
总结:
1,iOS原生的Frame布局语法太繁琐;AutoLayout虽然通过masonry减少了使用难度,但是性能不太好;Flexbox布局思路目前流行广泛,大有一统天下之趋势,使用一个统一的布局思想能够大大提高开发效率。基于以上几点,本人倾向于在项目中使用Flexbox布局。
2,如果你目前使用的是RN、Weex等,那么恭喜你已经在使用Flexbox布局。如果你是原生开发,那么可以通过Texture或者UIStackView来使用Flexbox布局。
以上