尺寸单位与响应式
Listing 28-1 到 28-4 都用 Val::Px 硬编码尺寸——在固定分辨率下没问题,但窗口缩放或换到不同分辨率的屏幕上就会溢出或留白。Val 的六种单位正是为了解决这个问题。
Percent:相对于父容器
Val::Percent(50.0) 意味着"父容器宽度的 50%"。这是最常用的响应式单位——父容器变大,子节点自动跟着变。
Node {
width: Val::Percent(80.0), // 父容器宽度的 80%
height: Val::Percent(100.0), // 和父容器一样高
..default()
}注意:Percent 的参照物是父容器,不是窗口。嵌套层级越深,参照物越小。
Vw / Vh:相对于视口
Val::Vw(n) 是窗口宽度的 n%,Val::Vh(n) 是窗口高度的 n%。无论嵌套多深,参照物始终是窗口本身:
Node {
width: Val::Vw(28.0), // 窗口宽度的 28%
height: Val::Vh(5.0), // 窗口高度的 5%
..default()
}这对 HUD 元素(血条、地图、小地图)特别有用——你希望它们始终占屏幕的固定比例,不管 UI 树嵌套多深。
VMin / VMax:相对于较短/较长边
Val::VMin(30.0) 是窗口较短边的 30%。横屏时参照高度,竖屏时参照宽度。适合需要在不同屏幕方向下保持正方形的元素。
嵌套布局实战
下面的例子把前面学到的 Flexbox 和响应式单位结合起来,构建一个典型的"头-体-脚"三段式布局:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands
.spawn((
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
padding: UiRect::all(Val::Px(16.0)),
row_gap: Val::Px(16.0),
..default()
},
BackgroundColor(Color::srgb(0.12, 0.12, 0.12)),
))
.with_children(|parent| {
// Header
parent.spawn((
Node {
width: Val::Percent(100.0),
height: Val::Px(60.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::srgb(0.25, 0.35, 0.55)),
));
// Body: sidebar + content
parent
.spawn(Node {
width: Val::Percent(100.0),
flex_grow: 1.0,
flex_direction: FlexDirection::Row,
column_gap: Val::Px(16.0),
..default()
})
.with_children(|parent| {
// Sidebar
parent.spawn((
Node {
width: Val::Px(180.0),
height: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
padding: UiRect::all(Val::Px(12.0)),
row_gap: Val::Px(8.0),
..default()
},
BackgroundColor(Color::srgb(0.2, 0.2, 0.25)),
));
// Content area with nested card
parent
.spawn((
Node {
flex_grow: 1.0,
height: Val::Percent(100.0),
padding: UiRect::all(Val::Px(16.0)),
..default()
},
BackgroundColor(Color::srgb(0.18, 0.18, 0.22)),
))
.with_children(|parent| {
parent.spawn((
Node {
width: Val::Px(200.0),
height: Val::Px(120.0),
border_radius: BorderRadius::all(Val::Px(12.0)),
..default()
},
BackgroundColor(Color::srgb(0.35, 0.5, 0.7)),
));
});
});
// Footer
parent.spawn((
Node {
width: Val::Percent(100.0),
height: Val::Px(40.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
},
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
));
});
}Listing 28-5:嵌套布局——Header / Sidebar+Content / Footer(examples/listing-28-05.rs)
几个关键点:
flex_grow: 1.0——Body 区域和 Content 区域都设了flex_grow,让它们吃掉剩余空间。Header 和 Footer 固定高度,Body 自动填满中间。flex_direction: FlexDirection::Row——Body 内部是水平排列:Sidebar 固定 180px 宽,Content 用flex_grow填满。- Sidebar 用
flex_direction: FlexDirection::Column准备好垂直排列菜单项(这里只留了空容器)。
Grid 布局
当需要二维排列(行和列同时控制)时,用 Display::Grid 替代默认的 Display::Flex。Grid 布局通过 grid_template_columns 和 grid_template_rows 定义轨道:
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
commands
.spawn((
Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
display: Display::Grid,
grid_template_columns: vec![
GridTrack::px(200.0),
GridTrack::flex(1.0),
GridTrack::px(150.0),
],
grid_template_rows: vec![
GridTrack::px(60.0),
GridTrack::flex(1.0),
GridTrack::px(40.0),
],
row_gap: Val::Px(8.0),
column_gap: Val::Px(8.0),
padding: UiRect::all(Val::Px(8.0)),
..default()
},
BackgroundColor(Color::srgb(0.1, 0.1, 0.1)),
))
.with_children(|parent| {
// Header: spans all 3 columns
parent.spawn((
Node {
grid_column: GridPlacement::span(3),
..default()
},
BackgroundColor(Color::srgb(0.25, 0.35, 0.55)),
));
// Left sidebar
parent.spawn((
Node {
..default()
},
BackgroundColor(Color::srgb(0.2, 0.2, 0.25)),
));
// Main content (auto-placed in center)
parent.spawn((
Node {
..default()
},
BackgroundColor(Color::srgb(0.18, 0.18, 0.22)),
));
// Right sidebar
parent.spawn((
Node {
..default()
},
BackgroundColor(Color::srgb(0.22, 0.22, 0.28)),
));
// Footer: spans all 3 columns
parent.spawn((
Node {
grid_column: GridPlacement::span(3),
..default()
},
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
));
});
}Listing 28-6:CSS Grid——三行三列的经典仪表盘布局(examples/listing-28-06.rs)
GridTrack 提供几种轨道定义:
| 方法 | 含义 |
|---|---|
GridTrack::px(200.0) | 固定 200px |
GridTrack::flex(1.0) | minmax(0, 1fr)——弹性分配剩余空间 |
GridTrack::fr(1.0) | minmax(auto, 1fr)——内容决定最小尺寸 |
GridTrack::auto() | 由内容决定 |
GridTrack::percent(30.0) | 父容器的 30% |
RepeatedGridTrack::flex(4, 1.0) 是 GridTrack::flex(1.0) 重复 4 次的简写。
grid_column: GridPlacement::span(3) 让一个格子横跨 3 列——Header 和 Footer 就是这么铺满整行的。
何时用 Grid,何时用 Flex
- 一维排列(水平或垂直的一行/一列)→ Flex
- 二维排列(同时控制行和列)→ Grid
- 嵌套混合很常见:外层 Grid 划分大区域,内层 Flex 排列具体内容
小结:Percent 相对于父容器,Vw/Vh 相对于窗口,VMin/VMax 相对于窗口较短/较长边。flex_grow 让子节点分配剩余空间。Grid 布局用 grid_template_columns/rows 定义二维轨道。实际项目中 Flex 和 Grid 经常嵌套使用。