☰
【xilem0.4基础语法学与练】第47课 Portal/Scroll滚动官方示例深度解析和拓展
2026/10/12 2:16:46 网站建设 项目流程

前言

源码来源: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 官方原生滚动容器

本示例完整演示功能:

  1. Grid / Flex 双布局动态切换
  2. 动态增减行列数量
  3. 批量迭代生成视图
  4. 位置渐变色彩系统
  5. 超大内容自动滚动视口

三、关键底层知识点

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,性能最优

五、完整运行数据流

  1. 程序初始化:默认 30×30 矩阵、Flex 布局
  2. 执行 app_logic 生成完整视图树
  3. 视图构建为 Element 与 Masonry Widget 并渲染窗口
  4. 用户点击加减按钮,修改行列数量状态
  5. 状态变更触发视图重渲染
  6. 框架 diff 新旧视图,增量更新方块矩阵
  7. 内容超出窗口范围,Portal 自动激活滚动
  8. 切换布局按钮,通过 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())

七、本课核心知识点总结

  1. portal 是 Xilem 原生双向滚动容器,是页面超长内容、列表、日志面板的核心组件。
  2. Grid 0.4 为比例自适应布局,忽略子项固定尺寸,适合页面整体排版,不适合固定卡片。
  3. Flex 嵌套布局精准保留组件尺寸,是固定尺寸列表首选方案。
  4. Either 实现零开销静态视图分支,是 Xilem 0.4 推荐的分支写法。
  5. UI 像素单位依赖 Masonry AsUnit 布局特征。
  6. 无符号数值计数必须做下限保护,避免生产崩溃。

八、课后习题

练习1 填空

  1. Xilem 0.4 实现滚动视图的核心组件是 ________。
  2. 高性能视图分支渲染,官方推荐使用 ________。
  3. Grid 的 FRACTION 轨道会 ________ 子组件固定尺寸。
  4. .px() 像素单位来自 ________ trait。

填空题参考答案

  1. portal
    ​
  2. Either
    ​
  3. 忽略
    ​
  4. AsUnit

练习2 判断

  1. Portal 同时支持水平、垂直双向滚动。(正确)
  2. Either 属于动态视图装箱,存在运行时开销。(错误)
  3. Flex 嵌套布局可以保留子组件固定像素尺寸。(正确)
  4. u16 数值递减无需做边界判断,不会报错。(错误)

练习3 简答

Grid 布局与 Flex 嵌套布局在本场景的核心区别是什么?

参考答案:

Grid 布局使用比例分配空间,完全忽略子组件固定尺寸,方块会随窗口大小缩放;
Flex 嵌套布局保留每个方块固定尺寸,整体结构稳定,适合固定规格的卡片列表。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询