
前言源码来源xilem/examples/to_do_mvc.rs适配版本Xilem 0.4 / Masonry 0.4一、完整源码// Copyright 2024 the Xilem Authors// SPDX-License-Identifier: Apache-2.0//! A to-do-list app, loosely inspired by todomvc.usexilem::kurbo::Axis;usexilem::masonry::layout::{AsUnit,Length};usexilem::masonry::theme::{DEFAULT_GAP,ZYNC_800};usexilem::masonry::widgets::DashFit;usexilem::style::Styleas_;usexilem::view::{FlexExt,FlexSpacer,MainAxisAlignment,button,checkbox,divider,divider_h,flex_col,flex_row,label,text_button,text_input,};usexilem::winit::error::EventLoopError;usexilem::{EventLoop,EventLoopBuilder,InsertNewline,WidgetView,WindowOptions,Xilem};structTask{description:String,done:bool,}#[derive(PartialEq, Eq, Copy, Clone)]enumFilter{All,Active,Completed,}structTaskList{next_task:String,filter:Filter,tasks:VecTask,}implTaskList{fnadd_task(mutself){ifself.next_task.is_empty(){return;}self.tasks.push(Task{description:std::mem::take(mutself.next_task),done:false,});}}fnapp_logic(task_list:mutTaskList)-implWidgetViewTaskListuse{letheader_textlabel(todos).text_size(80.);letinput_boxtext_input(task_list.next_task.clone(),|task_list:mutTaskList,new_value|{task_list.next_tasknew_value;},).text_size(16.).placeholder(What needs to be done?).insert_newline(InsertNewline::OnShiftEnter).on_enter(|task_list:mutTaskList,_|{task_list.add_task();});letinput_lineflex_row((input_box.flex(1.0),button(label(Add task.to_string()).text_size(16.),|task_list:mutTaskList|{task_list.add_task();},),));lettaskstask_list.tasks.iter().enumerate().filter_map(|(i,task)|{if(task_list.filterFilter::Activetask.done)||(task_list.filterFilter::Completed!task.done){None}else{letcheckboxcheckbox(task.description.clone(),task.done,move|data:mutTaskList,checked|{data.tasks[i].donechecked;},).text_size(16.);letdelete_buttontext_button(Delete,move|data:mutTaskList|{data.tasks.remove(i);}).padding(5.px());Some(flex_row((checkbox,FlexSpacer::Flex(1.),delete_button)).padding(DEFAULT_GAP).border(ZYNC_800,1.px()),)}}).collect::Vec_();letfilter_tasks|label,filter|{// TODO: replace with combo-buttonscheckbox(label,task_list.filterfilter,move|state:mutTaskList,_|state.filterfilter,)};lethas_tasks!task_list.tasks.is_empty();letfooterhas_tasks.then(||{flex_row((filter_tasks(All,Filter::All),filter_tasks(Active,Filter::Active),filter_tasks(Completed,Filter::Completed),)).main_axis_alignment(MainAxisAlignment::Center)});flex_col((header_text,FlexSpacer::Fixed(DEFAULT_GAP),input_line,divider(Axis::Horizontal,label(Tasks)),tasks,FlexSpacer::Fixed(3.px()),divider_h().thickness(2.px()).dash_fit(DashFit::Stretch).dash_pattern([5.px(),5.px()]),FlexSpacer::Fixed(3.px()),footer,)).gap(Length::px(4.)).padding(50.px())}pub(crate)fnrun(event_loop:EventLoopBuilder)-Result(),EventLoopError{letdataTaskList{// Add a placeholder task for Android, whilst thenext_task:My Next Task.into(),filter:Filter::All,tasks:vec![Task{description:Buy milk.into(),done:false,},Task{description:Buy eggs.into(),done:true,},Task{description:Buy bread.into(),done:false,},],};letappXilem::new_simple(data,app_logic,WindowOptions::new(To Do MVC));app.run_in(event_loop)}// Boilerplate code: Identical across all applications which support Androidfnmain()-Result(),EventLoopError{run(EventLoop::with_user_event())}二、程序核心功能本案例是TodoMVC风格待办清单MVC架构示例是Xilem综合实战✅ 文本输入框、回车添加任务✅ 任务勾选标记完成/未完成✅ 删除单条任务✅ 任务筛选全部 / 待完成(Active) / 已完成(Completed)✅ 空任务时自动隐藏底部筛选栏✅ 分割线、虚线分割线、布局间距、边框样式本课核心主题集合列表渲染、数据过滤、条件视图、文本输入控件、MVC思想Model TaskList Task Filter 纯数据View app_logic 构建UI视图Controller按钮、输入框、复选框回调修改Model三、逐模块深度解析1. 数据模型 ModelstructTask{description:String,// 任务描述done:bool,// 是否完成}#[derive(PartialEq, Eq, Copy, Clone)]enumFilter{All,Active,Completed,}structTaskList{next_task:String,// 输入框临时文本filter:Filter,// 当前筛选条件tasks:VecTask,// 任务数组}TaskList::add_task() 把输入框内容移入tasks列表清空输入框。 std::mem::take 高效转移字符串所有权避免clone。2. 输入框 text_inputtext_input(task_list.next_task.clone(),|task_list:mutTaskList,new_value|{task_list.next_tasknew_value;},).text_size(16.).placeholder(What needs to be done?).insert_newline(InsertNewline::OnShiftEnter).on_enter(|task_list:mutTaskList,_|{task_list.add_task();});双向绑定输入实时更新 next_taskplaceholder 占位提示文字.on_enter 按下回车执行添加任务InsertNewline::OnShiftEnter ShiftEnter才换行普通Enter提交任务3. 列表渲染 filter_map 动态生成任务UI重点lettaskstask_list.tasks.iter().enumerate().filter_map(|(i,task)|{// 根据filter判断是否渲染当前任务if(task_list.filterFilter::Activetask.done)||(task_list.filterFilter::Completed!task.done){None// 不渲染}else{// 生成一行任务UI复选框 弹性占位 删除按钮letcheckboxcheckbox(...);letdelete_buttontext_button(Delete,move|data:mutTaskList|{data.tasks.remove(i);});Some(flex_row((checkbox,FlexSpacer::Flex(1.),delete_button)).padding(DEFAULT_GAP).border(ZYNC_800,1.px()),)}}).collect::Vec_();enumerate 拿到索引i用于删除/修改对应taskfilter_map 遍历任务按筛选条件动态决定是否生成视图返回None就不渲染该条目checkbox Xilem原生复选框组件绑定勾选状态每行任务flex_rowFlexSpacer把删除按钮推到最右侧每条任务增加内边距边框视觉卡片效果4. 筛选器封装函数 filter_tasksletfilter_tasks|label,filter|{checkbox(label,task_list.filterfilter,move|state:mutTaskList,_|state.filterfilter,)};用闭包快速生成筛选单选复选框选中对应Filter切换全局过滤规则。5. 条件渲染 footerlethas_tasks!task_list.tasks.is_empty();letfooterhas_tasks.then(||{flex_row((filter_tasks(All,Filter::All),filter_tasks(Active,Filter::Active),filter_tasks(Completed,Filter::Completed),)).main_axis_alignment(MainAxisAlignment::Center)});bool.then() 只有存在任务的时候才渲染底部筛选栏无任务自动隐藏footer。6. 外层 flex_col 整体布局与分割线flex_col((header_text,FlexSpacer::Fixed(DEFAULT_GAP),input_line,divider(Axis::Horizontal,label(Tasks)),tasks,FlexSpacer::Fixed(3.px()),divider_h().thickness(2.px()).dash_fit(DashFit::Stretch).dash_pattern([5.px(),5.px()]),FlexSpacer::Fixed(3.px()),footer,)).gap(Length::px(4.)).padding(50.px())divider 带文字标题的分割线divider_h 纯水平虚线 dash_pattern 定义虚线样式7. 数据流7.1 初始加载预设3条任务7.2 用户输入文本实时更新 next_task7.3 点击Add task / Enter调用 add_task 追加任务7.4 勾选复选框修改 task.done7.5 Delete按钮按索引删除任务7.6 切换筛选选项修改 filter 触发列表重新过滤渲染7.7 状态变更 → app_logic重新执行生成新视图树Xilem diff更新UI四、本课核心知识点总结MVC架构在Xilem的实践数据、视图、事件回调分离。列表动态渲染 iter().enumerate().filter_map() 根据数据动态生成视图数组是Xilem渲染列表的标准写法。条件视图渲染 bool.then() 条件成立才渲染组件。text_input文本输入控件支持placeholder、回车事件、shiftenter换行。原生控件 checkbox 复选框、 divider 分割线支持虚线样式。std::mem::take 转移String所有权高效清空输入框减少内存拷贝。闭包封装UI组件 filter_tasks 快速批量生成同类型组件。五、课后习题练习1 填空这个示例采用____架构数据、视图、事件控制器分离。动态筛选列表使用迭代器的____方法满足条件返回视图不满足返回None不渲染。bool.then() 的作用是________。std::mem::take 的作用是转移字符串____避免clone。参考答案MVCfilter_map布尔为true时才渲染视图false不渲染所有权练习2 判断filter_map返回None时不会渲染该条目的UI。✅正确没有任务的时候底部筛选footer仍然会渲染。❌错误text_input默认回车直接提交任务ShiftEnter换行。✅正确任务删除依靠任务名称查找不需要索引。❌错误练习3 简答题目Xilem渲染动态列表为什么推荐filter_map而不是简单map参考答案map会为每一条数据强制生成视图filter_map可以一边遍历一边做条件判断不满足条件直接返回None跳过该条视图创建非常适合列表筛选场景代码简洁。