Skip to content

滚动与焦点

滚动:Overflow 与 ScrollPosition

Bevy UI 的滚动不需要额外插件——它内置于布局系统。给一个 Node 设置 overflow: Overflow::scroll_y()(垂直滚动)或 Overflow::scroll()(双向滚动),超出的内容就会被裁切,并且可以通过 ScrollPosition 组件控制滚动偏移。

rust
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
    let font = assets.load("fonts/FiraSans-Bold.ttf");

    commands.spawn(Camera2d);
    commands.spawn((
        Node {
            width: percent(100),
            height: percent(100),
            flex_direction: FlexDirection::Column,
            align_items: AlignItems::Center,
            justify_content: JustifyContent::Center,
            ..default()
        },
        children![(
            Node {
                flex_direction: FlexDirection::Column,
                width: px(250),
                height: px(200),
                overflow: Overflow::scroll_y(),
                ..default()
            },
            BackgroundColor(Color::srgb(0.1, 0.1, 0.1)),
            Children::spawn(SpawnIter((0..30).map(move |i| {
                (
                    Node {
                        min_height: px(28),
                        padding: UiRect::axes(px(12), px(4)),
                        align_items: AlignItems::Center,
                        ..default()
                    },
                    children![(
                        Text(format!("第 {} 行", i + 1)),
                        TextFont {
                            font: FontSource::Handle(font.clone()),
                            font_size: FontSize::Px(16.0),
                            ..default()
                        },
                        TextColor(Color::srgb(0.85, 0.85, 0.85)),
                    )],
                )
            }))),
        )],
    ));
}

Listing 29-5:可滚动列表——Overflow::scroll_y + Children::spawn(SpawnIter)

关键点:

  1. OverflowOverflow::scroll_y() 只允许垂直滚动,Overflow::scroll() 允许双向,Overflow::scroll_x() 只允许水平。默认是 Overflow::visible(不裁切)。
  2. ScrollPositionScrollPosition(Vec2::new(x, y)) 控制当前滚动偏移。你可以直接修改它来程序化滚动。
  3. 内容溢出:子节点的总高度超过容器高度时,滚动才有意义。这里 30 行 × 28px = 840px,容器只有 200px,所以可以滚动。
  4. SpawnIterChildren::spawn(SpawnIter(iter)) 是生成大量同类子节点的惯用法——传入一个迭代器,每项是一个 Bundle。注意必须传迭代器本身,不要先 .collect::<Vec<_>>()

Bevy 的滚动默认支持鼠标滚轮(通过 bevy_inputMouseWheel 事件和 ui_focus_system 自动处理)。触摸滚动需要 bevy_winit 的触摸事件支持。

滚动条控件

bevy_ui_widgets 还提供了 Scrollbar 控件——一个可以绑定到任意滚动容器的独立滑块。它的用法比手动实现滚动条复杂一些(需要 Grid 布局、CoreScrollbarThumb 标记、CoreScrollbarDragState 等),官方示例 scrollbars.rs 有完整参考。本章不展开。

焦点与键盘导航

bevy_input_focus crate 提供了 UI 焦点管理:

  • InputFocus:Resource,存储当前获得焦点的实体;
  • InputFocusVisible:Resource,控制焦点环(focus ring)是否可见;
  • TabNavigationPlugin:提供 Tab 键导航支持;
  • InputDispatchPlugin:将键盘事件分发给焦点实体。

焦点系统对无障碍访问至关重要。bevy_ui_widgets 的 Button、Slider、Checkbox 都内置了键盘处理(Enter/Space 触发按钮,方向键控制滑条),但前提是有焦点系统将键盘事件路由到正确的实体。

使用焦点系统只需两步:

rust
app.add_plugins((InputDispatchPlugin, TabNavigationPlugin));

然后给可聚焦的实体添加 TabIndex(0) 组件(来自 bevy::input_focus::tab_navigation)。Tab 键会在 TabIndex 不为 -1 的实体间循环切换焦点。

对于简单的桌面应用,焦点系统不是必须的——鼠标交互不依赖它。但如果你的目标平台包括移动端或需要无障碍支持,焦点系统是基础设施。