flexBox布局之yogakit
本次分享目的:
介绍一种有别于frame手算布局的布局框架,更快速,大大提升产生力
背景:
- 多终端,多尺寸设备的现状使得UI布局越来越重要
随着这几年前端技术的崛起,前端UI布局系统也在其中占据了越来越重要的位置。不管是在移动端、桌面端还是Web端,特别是不同设备的屏幕大小和分辨率千变万化,如何构建良好的布局系统得越来越重要。
- 现状:各平台各自布局方案,维护成本高,无法共享
目前的情况是各个平台都有自己的一套解决方案。iOS平台有自动布局系统,Android有容器布局系统,而Web端有基于CSS的布局系统。多种布局系统共存所带来的弊端是很明显的,平台间的共享变得很困难,而每个平台都需要专人来开发维护,增加了开发成本。
- React Native中实现了一种跨平台的基于CSS的布局系统
React Native里引入了一种跨平台的基于CSS的布局系统,它实现了Flexbox规范。是他的一个子集
- 不断完善发展后形成了一款跨平台的布局引擎:Yoga
随着这个系统的不断完善,Facebook对它进行重启发布,并借助Yoga,不仅可以在React Native里,还能在各个平台上快速地构建UI布局。
Yoga简单介绍:
Yoga是基于C实现的。基于C实现的Yoga比之前Java实现在性能上提升了33%。使用C实现可以更容易地跟其它平台集成。到目前为止,Yoga已经有以下几个平台的绑定:Java(Android)、Objective-C(UIKit)、C#(.NET)。而且已经有很多项目在使用Yoga,比如React Native、Components for Android、weex, luaView等等。
不同于其它的一些布局框架,比如Masonry,它们要么不够强大,要么不支持跨平台。Yoga遵循了Flexbox规范,同时又将布局元素抽象,为各个不同平台暴露出一组标准的接口,这样不同的平台只需实现这些接口就可以了。
Facebook把Yoga开源了,除了完全遵循Flexbox规范,在未来为Yoga加入更多特性,这些特性将超出Flexbox的范畴。
Yoga官方网站:https://facebook.github.io/yoga/
Flexbox布局概念:
Flexbox布局( Flexible Box 或CSS3 弹性布局),是CSS3中的一种新的布局模式。使用Flexbox来布局更容易,可以使用更少的代码,更简单的方式实现更复杂的布局,例如对齐方式,排列方向,排列顺序(这也是Flexbox布局的核心能力所在),弹性盒中的子元素通过在各个方向放置就可以以弹性的尺寸适应父元素的显示区域。独立显示被设定成只针对可见元素,而不是基于代码的声明和导航顺序。
Flexbox布局教学:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
YogaKit使用:
iOS 中使用 YogaKit,它是 C 实现的一个封装。
YogaKit 将 YGLayout 暴露作为 UIView 的一个分类。这个分类在 UIView 中增加了一个 configureLayout(block:) 方法。block 闭包带一个 YGLayout 参数,你可以用这些数据来配置 view的布局属性。
一些基本的布局属性:
justifyContent : item项目 在容器主轴方向上的对齐
flexdirection:决定主轴的方向(即 item的排列方向)。
padding : 添加一个内白
marginXXX: 增加一个边距
width:宽
hight:高
alignItems:item项目在交叉轴上如何对齐
alignSelf:容器自身的对齐位置
通过将每个子 view 配置所需的 Yoga 属性来构建布局。然后,调用根 view 上的 YGLayout 的 applyLayout(preservingOrigin:) 方法。然后布局会被计算并应用到根 view 和 subview 上。
源码文件:
- 其中yogakit目录下是oc是接口
- 核心文件在yoga.m中
- 相比较masonry很轻量级
- 可以和frame布局一起使用
demo:
免去计算滚动视图的高度,
-
免去计算tableview的高度;
YogaKit布局源码解析:
oc布局执行入口:
- (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
执行下面方法:
- (CGSize)calculateLayoutWithSize:(CGSize)size
要求布局必须在主线程。
然后节点布局计算:
void YGNodeCalculateLayout(const YGNodeRef node,
const float parentWidth,
const float parentHeight,
const YGDirection parentDirection)
这是一个包装YGNodelayoutImpl函数。它决定了布局请求是否冗余,可以跳过。输入参数是一样的YGNodelayoutImpl返回参数是true的:
bool YGLayoutNodeInternal(const YGNodeRef node,
const float availableWidth,
const float availableHeight,
const YGDirection parentDirection,
const YGMeasureMode widthMeasureMode,
const YGMeasureMode heightMeasureMode,
const float parentWidth,
const float parentHeight,
const bool performLayout,
const char *reason,
const YGConfigRef config)
最后布局算法实现:
static void YGNodelayoutImpl(const YGNodeRef node,
const float availableWidth,
const float availableHeight,
const YGDirection parentDirection,
const YGMeasureMode widthMeasureMode,
const YGMeasureMode heightMeasureMode,
const float parentWidth,
const float parentHeight,
const bool performLayout,
const YGConfigRef config)
性能比较:
上图是3个布局算法在嵌套情况下的性能比较图。FlexBox的性能接近于原生的Frame。而嵌套情况下的Autolayout的性能很差;
布局算法主要步骤:
- 对特殊节点和情况进行预处理:
- 内容节点:采用 measure 方法,通过回调视图对文本实际计算得到的尺寸确定宽高
- 叶子节点:没有子视图的,直接求解,不进行递归计算
- 对不需要计算布局的节点直接求解
- 确定节点内每个子节点的 flexBasis
- 对节点内所有子节点遍历,对元素分行并计算主轴和交叉轴对齐:
- 将子节点分行
- 计算当前行内元素在主轴上的尺寸,计算当前行剩余可分配空间
- 计算当前行内元素在主轴上的位置,计算当前行内元素在交叉轴上的尺寸
- 计算当前行内元素在交叉轴上的位置
- 计算节点的多行对齐,更新元素在交叉轴上的位置
- 计算节点的最终尺寸和位置
- 计算绝对定位子节点的尺寸和位置
- 设置子节点的 trailing 位置
预处理:
内容节点:
对 Label
、TextField
等文本节点和 ImageView
等由内容决定的节点直接通过外部传入的 measure
回调拿到尺寸。
measure
方法可以通过协议让具体的视图来实现。
叶子节点
对没有孩子的叶子节点,由于不需要递归计算内部子节点的布局,因此可以直接通过指定的模式算出估计尺寸,跳过接下来的流程。
非布局节点
同样,在对布局树的多次递归过程中,对于只需知道子节点尺寸而不需要知道位置的情况,会把 performLayout
标志位置为 NO
来跳过计算量消耗较大的计算位置的流程。
确定 flexBasis
这一步确定容器中每个子元素的在主轴上的 flexBasis
值。flexBasis
是每个元素的在未扩展和压缩前的基准尺寸,父容器用来计算主轴的剩余空间,然后根据扩展和压缩比例系数为每个 Flex 子元素调整尺寸。由于绝对定位子节点不参与 Flex 布局,因此不需要计算 flexBasis
值,在这一步中,会先把绝对定位子节点存储在链表中,在 Flex 布局完成后再单独计算所有绝对定位节点的布局。
缓存机制
CSSLayout 算法中的缓存分为两个层次,即把渲染树中所有节点的布局结果和估计结果都缓存起来,只有当两棵渲染树计算条件完全匹配时才会触发;另一种复用要求较低的,只把中间的估算结果缓存起来,内部缓存最近 16 次的计算结果, 在渲染树增量更新、插入节点等部分更新情况下避免重复估算尺寸,但对节点的布局仍需计算。
相关参考资料:
https://facebook.github.io/yoga/