布局系统
Ribir 的布局系统采用“约束向下,尺寸向上”的单遍模型。这与 Flutter 的布局模型非常相似,旨在实现高效且灵活的 UI 布局。
核心原则
- 约束向下: 父 Widget 将布局约束(Constraints)向下传递给子 Widget。这些约束定义了子 Widget 可以占用的最小和最大宽度和高度。
- 尺寸向上: 子 Widget 根据接收到的约束计算自己的尺寸,并将最终确定的尺寸(Size)返回给父 Widget。
- 父项设置位置: 在接收子 Widget 的尺寸后,父 Widget 确定子 Widget 在其自身坐标系中的位置。
BoxClamp
布局约束由 BoxClamp 结构表示。它包含四个值:
min_width,max_widthmin_height,max_height
BoxClamp 定义了一个允许的尺寸范围。子 Widget 的最终尺寸必须在此范围内。
- 宽松约束:
min为 0,max为某个有限值。子 Widget 可以是 0 和最大值之间的任何尺寸。 - 严格约束:
min等于max。子 Widget 被强制为特定尺寸。 - 无界约束:
max为无穷大。子 Widget 可以无限扩展(通常出现在滚动容器中)。
布局过程
每个 Widget 都必须在 Render trait 中实现 perform_layout 方法:
fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size
在此方法中,Widget 需要做三件事:
- 布局子项: 遍历其子节点,为每个子节点计算新的
BoxClamp(基于传入的clamp和其自身的布局逻辑),并调用ctx.perform_child_layout(child, child_clamp)。 - 确定位置: 获取子节点返回的
Size,并根据布局逻辑设置子节点的位置ctx.update_position(child, position)。 - 返回尺寸: 计算并返回自身的最终
Size,并且此尺寸必须满足传入的clamp约束。
使用 clamp 属性干预布局
Ribir 提供了一个内置的 clamp 属性,允许您在声明它时直接修改 Widget 接收的父约束。这在后台通过包装 ConstrainedBox 实现。
use ribir::prelude::*;
fn example() -> Widget<'static> {
fn_widget! {
@Container {
size: Size::new(100., 100.),
background: Color::RED,
// 强制约束:不管父级给出什么约束,Container 的宽度必须在 50 到 200 之间
clamp: BoxClamp {
min: Size::new(50., 0.),
max: Size::new(200., f32::INFINITY),
}
}
}.into_widget()
}