前言
源码来源:Xilem 0.4 官方示例文件scroll.rs 滚动示例
来源路径:xilem\xilem\examples\scroll.rs
适配版本:Xilem 0.4 / Masonry 0.4
一、官方完整源码
// Copyright 2024 the Xilem Authors// SPDX-License-Identifier: Apache-2.0//! An example showcasing the capabilities of the Portal widget.usemasonry::layout::AsUnit;usewinit::error::EventLoopError;usexilem::peniko::color::AlphaColor;usexilem::style::Style;usexilem::view::{CrossAxisAlignment,GridExt,GridParams,GridTrackSize,MainAxisAlignment,flex_col,flex_row,grid,label,portal,repeat_tracks,sized_box,text_button,};usexilem::{EventLoop,WidgetView,WindowOptions,Xilem};usexilem_core::one_of::Either;#[derive(Debug, Clone, Copy, Default)]/// The layout mode we use.enumBlocksLayout{/// A grid layout.////// We would like this to be the only supported option, but Grid currently/// completely ignores the size of its items, so it doesn't accurately show/// what we want it to show.Grid,/// A flex layout, using a flex for each row each/// inside a single flex column containing all the rows.#[default]Flex,}structAppState{vertical_count:u16,horizontal_count:u16,blocks_layout:BlocksLayout,}implDefaultforAppState{fndefault()->Self{Self{vertical_count:30,horizontal_count:30,blocks_layout:BlocksLayout::default(),}}}fncolor_block(row_idx:u16,col_idx:u16,vertical_count:u16,horizontal_count:u16,)-><AppState<>{letrow_idx=row_idxasf32;letcol_idx=col_idxasf32;letvertical_count=vertical_countasf32;lethorizontal_count=horizontal_countasf32;sized_box(label(format!("r{}c{}",row_idx,col_idx))).dims(50.px()).background_color(AlphaColor::new([row_idx/vertical_count,col_idx/horizontal_count,80./255.,1.0,]))}fngrid_blocks(state:&mutAppState)-><AppState<>{let(vertical_count,horizontal_count)=(state.vertical_count,state.horizontal_count);grid((0..vertical_count).flat_map(|row_idx|{(0..horizontal_count).map(move|col_idx|{color_block(row_idx,col_idx,vertical_count,horizontal_count).grid_params(GridParams::pos(row_idx,col_idx))})}).collect::<_>>(),).columns(repeat_tracks(horizontal_countas_,GridTrackSize::FRACTION,)).rows(repeat_tracks(vertical_countas_,GridTrackSize::FRACTION)).gap(10.px())}fnflex_blocks(state:&mutAppState)-><AppState<>{let(vertical_count,horizontal_count)=(state.vertical_count,state.horizontal_count);flex_col((0..vertical_count).map(|row_idx|{flex_row((0..horizontal_count).map(|col_idx|{color_block(row_idx,col_idx,vertical_count,horizontal_count)})<Vec<_>>(),)}).<_>>(),)}fnblocks(state:&mutAppState)->implWidget<AppState>+<>{matchstate.blocks_layout{BlocksLayout::Grid=>Either::A(grid_blocks(state)),BlocksLayout::Flex=>Either::B(flex_blocks(state)),}}fnapp_logic(state:&mutAppState)->implWidgetView<AppState>+use<>{letblocks_layout_switch=flex_row((label("Switch layout:"),text_button(format!("{:?}",state.blocks_layout),|state:&mutAppState|{letnext=matchstate.blocks_layout{BlocksLayout::Grid=>BlocksLayout::Flex,BlocksLayout::Flex=>BlocksLayout::Grid,};state.blocks_layout=next;},),));letvertical_controls=flex_row((label("Vertical blocks (rows):"),text_button("-",|appstate:&mutAppState|appstate.vertical_count-=1),text_button("+",|appstate:&mutAppState|appstate.vertical_count+=1),));lethorizontal_controls=flex_row((label("Horizontal blocks (columns):"),text_button("-",|appstate:&mutAppState|{appstate.horizontal_count-=1;}),text_button("+",|appstate:&mutAppState|{appstate.horizontal_count+=1;}),));letcontent=flex_col((blocks_layout_switch,vertical_controls,horizontal_controls,blocks(state),)).main_axis_alignment(MainAxisAlignment::Start).cross_axis_alignment(CrossAxisAlignment::Start);portal(content)}fn<(),EventLoopError>{letapp=Xilem::new_simple(AppState::default(),app_logic,WindowOptions::new("Scroll"));app.run_in(EventLoop::with_user_event())?;Ok(())}二、程序核心功能
本示例主要用于演示 滚动组件Portal 组件 的核心能力。
portal 是 Xilem/Masonry 的原生滚动视口容器
- 内部内容超出视口尺寸 → 自动出现垂直/水平滚动条
- 原生支持鼠标滚轮滚动
- 是 Xilem 0.4 官方原生滚动容器
本示例完整演示功能:
- Grid / Flex 双布局动态切换
- 动态增减行列数量
- 批量迭代生成视图
- 位置渐变色彩系统
- 超大内容自动滚动视口
三、关键底层知识点
1. 像素单位 .px() 来源
usemasonry::layout::AsUnit;.px() 是 AsUnit trait 提供的方法,用于将数值转换为布局系统识别的像素单位,所有间距、尺寸设置均依赖该 trait。
2. 官方代码潜在问题
原生示例中行列递减按钮无边界判断:
- 计数变量为 u16 无符号类型
- 数值为 0 时继续递减会触发整数下溢panic崩溃
- 生产环境必须增加最小值保护
四、逐模块深度解析
1. 应用状态 AppState
vertical_count:u16// 方块行数horizontal_count:u16// 方块列数blocks_layout:BlocksLayout// 当前布局模式状态全部为可复制简单值类型,轻量化、高效,完全适配 Xilem 状态驱动更新模型。
2. 渐变方块 color_block
AlphaColor::new([r,g,b,a])颜色通道值域为 0.0 ~ 1.0
- 红色通道:随行数递增渐变
- 绿色通道:随列数递增渐变
- 蓝色通道:固定偏暗
- 透明度:完全不透明
每个方块固定尺寸 50px × 50px ,并标注行列编号文本。
3. Grid 网格布局特性
Xilem 0.4 的 Grid 布局存在固定特性:完全忽略子组件固有尺寸
- GridTrackSize::FRACTION 为比例均分模式
- 所有格子强制瓜分窗口可用空间
- 设置的 50px 固定尺寸不生效
- 窗口越大,方块越大
因此 Grid 适合自适应页面,不适合固定尺寸卡片场景。
4. Flex 嵌套布局特性
采用「外层垂直列 + 每行水平行」嵌套结构:
- 严格保留子组件 50px 固定尺寸
- 不会随窗口拉伸变形
- 更适合批量固定大小卡片列表
官方默认使用 Flex 布局,也因为该布局表现更符合直觉。
5. Either 静态分支视图
matchstate.blocks_layout{BlocksLayout::Grid=>Either::A(grid_blocks(state)),BlocksLayout::Flex=>Either::B(flex_blocks(state)),}Either 是 xilem_core 提供的编译期静态分支:
- 无堆分配、无动态派发、无运行时开销
- 替代`,性能更高
- 用于同一位置渲染两种不同视图
6. Portal 滚动容器原理
- 创建固定可视区域(视口)
- 内部布局内容可无限延伸
- 内容宽/高超出视口 → 自动开启对应方向滚动
- 同时支持水平+垂直双向滚动
- 底层为 Masonry 原生 Widget,性能最优
五、完整运行数据流
- 程序初始化:默认 30×30 矩阵、Flex 布局
- 执行 app_logic 生成完整视图树
- 视图构建为 Element 与 Masonry Widget 并渲染窗口
- 用户点击加减按钮,修改行列数量状态
- 状态变更触发视图重渲染
- 框架 diff 新旧视图,增量更新方块矩阵
- 内容超出窗口范围,Portal 自动激活滚动
- 切换布局按钮,通过 Either 切换 Grid/Flex 渲染分支
六、生产级优化拓展代码
拓展1:修复数值下溢崩溃(生产必加)
letvertical_controls=flex_row((label("Vertical blocks (rows):"),text_button("-",|appstate:&mutAppState|{ifappstate.vertical_count>1{appstate.vertical_count-=1;}}),text_button("+",|appstate:&mutAppState|appstate.vertical_count+=1),));lethorizontal_controls=flex_row((label("Horizontal blocks (columns):"),text_button("-",|appstate:&mutAppState|{ifappstate.horizontal_count>1{appstate.horizontal_count-=1;}}),text_button("+",|appstate:&mutAppState|{appstate.horizontal_count+=1;}),));拓展2:一键重置布局尺寸
letreset_btn=text_button("Reset",|state:&mutAppState|{*state=AppState::default();});拓展3:给滚动区域增加内边距
portal(content).padding(12.px())七、本课核心知识点总结
- portal 是 Xilem 原生双向滚动容器,是页面超长内容、列表、日志面板的核心组件。
- Grid 0.4 为比例自适应布局,忽略子项固定尺寸,适合页面整体排版,不适合固定卡片。
- Flex 嵌套布局精准保留组件尺寸,是固定尺寸列表首选方案。
- Either 实现零开销静态视图分支,是 Xilem 0.4 推荐的分支写法。
- UI 像素单位依赖 Masonry AsUnit 布局特征。
- 无符号数值计数必须做下限保护,避免生产崩溃。
八、课后习题
练习1 填空
- Xilem 0.4 实现滚动视图的核心组件是 ________。
- 高性能视图分支渲染,官方推荐使用 ________。
- Grid 的 FRACTION 轨道会 ________ 子组件固定尺寸。
- .px() 像素单位来自 ________ trait。
填空题参考答案
- portal
- Either
- 忽略
- AsUnit
练习2 判断
- Portal 同时支持水平、垂直双向滚动。(正确)
- Either 属于动态视图装箱,存在运行时开销。(错误)
- Flex 嵌套布局可以保留子组件固定像素尺寸。(正确)
- u16 数值递减无需做边界判断,不会报错。(错误)
练习3 简答
Grid 布局与 Flex 嵌套布局在本场景的核心区别是什么?
参考答案:
Grid 布局使用比例分配空间,完全忽略子组件固定尺寸,方块会随窗口大小缩放;
Flex 嵌套布局保留每个方块固定尺寸,整体结构稳定,适合固定规格的卡片列表。