跨端必备技能之flex
布局,目前主流的跨端开发方案都使用flex
,而其中使用较为广泛的是yoga
。
前置知识
在开始之前有一些需要知道的前置条件,这些对理解yoga
的布局原理非常有帮助。建议想补充完flex
相关的知识在继续看yoga
部分。
Flex
布局的传统解决方案,基于盒状模型,依赖 display
属性 ,position
属性 float
属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。
2009年,W3C 提出了一种新的方案: Flex
。
可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持。
采用 Flex 布局的元素,称为Flex
容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为 Flex
项目flex item
,简称”项目”。
主轴和侧轴(交叉轴)
容器默认存在两根轴:水平的主轴main axis
和垂直的交叉轴cross axis
首先每一根轴都包括 三个东西:维度、方向、尺寸。
- 维度:子项目横着排还是竖着排(x 轴 或 y 轴)。
- 方向:即排列子元素的顺序 顺序还是逆序。
- 尺寸:每一个子元素在主轴方向所占的位置的总和。
flex item & flex-basis
flex item:Flex布局下的子元素。
flex-basis:浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
优先级要高于项目设置的width height。
yoga
YGMeasureMode
当前项目的计算模式。分为3种情况
- YGMeasureModeUndefined:
- YGMeasureModeAtMost:项目的尺寸不是确切的,但是具有最大值,则取最大值
- YGMeasureModeExactly:项目大小的是确定的,可以通过css直接设置,或者经过flex计算得出。
YGNode
对于每一个native
控件,都有一个YGNode
与之对应,形成绑定关系。同时根据native
侧的视图层级结构,来生成yoga
侧的node
的数据结构。node
主要由4部分组成:
- 当前node所在节点树中的信息:父节点,子节点。
- css样式。
- 布局信息。
- 自定函数注入。
YGLayout
YGLayout
主要保存了所有运行时计算布局的信息。其中有几个比较重要的属性:
measuredDimensions
每次计算当前项目的尺寸之后,都会把结果存入当前结构。
computedFlexBasis
这个属性十分重要,所有的flex 布局尺寸:拉伸,压缩,换行,都跟这个属性有关。
假设当前项目A
有两个子项目。flex-direction 为row。
则此时A
的flexBasis
等于B,C
的flexBasis
之和。 而B,C
当前的flexBasis
由项目本身的内容和css样式决定。
cachedMeasurements
在yoga
计算布局时,每次都会缓存计算结果,在下一次计算到来时,优先尝试缓存中的所有项,如果不可用在进行布局计算。缓存的内容如下:1
2
3
4
5
6
7
8
9struct YGCachedMeasurement {
float availableWidth; //最大有效宽度。一般为父容器宽度
float availableHeight; //高度:同上
YGMeasureMode widthMeasureMode; //本次缓存的计算模式
YGMeasureMode heightMeasureMode; //同上
float computedWidth; //经过计算之后的计算宽度
float computedHeight; //同上
}
YGStyle
YGStyle
主要保存了开发者自己设置的css
样式。
下面是上面三者的关系。
布局流程
由于源码过长,笔者将注释过的源码放到了这里
踩坑
文本显示不全(float精度失真问题)
yoga 在处理文本的节点时,采用了下面的方式
1 |
|
造成这样的原因是:
当宽度不是整数(有余数)时。对于文本的内容应该向上取整。
但由于YGFloatsEqual内部,在float比较时,精度为0.0001f。导致某些极端的情况下,文本内容被截断。
如:文本宽度刚好为 3.0000999,此时会被认定为3,导致文本显示不全。
解决办法:
参考rn1
2
3
4
5
6
7// Adding epsilon value illuminates problems with converting values from
// `double` to `float`, and then rounding them to pixel grid in Yoga.
CGFloat epsilon = 0.001;
return (YGSize){
RCTYogaFloatFromCoreGraphicsFloat(size.width + epsilon),
RCTYogaFloatFromCoreGraphicsFloat(size.height + epsilon)
};
在注入的计算函数中,强制向上取整。