☰
Rust+Slint实现动态启动界面源码分享
2026/10/21 2:13:35 网站建设 项目流程

Rust+Slint实现动态启动界面源码分享

  • 一、效果展示
  • 二、源码分享
    • 1、工程结构
    • 2、main.rs
    • 3、build.rs
    • 4、main.slint
    • 5、Cargo.toml
  • 三、实现原理
    • 1、整体数据流
    • 2、动画机制
      • 2.1 发射动画
      • 2.2 旋转动画
      • 2.3 淡出效果
    • 3、资源管理
      • 3.1 图片资源
      • 3.2 模型增删
    • 4、关键设计要点

一、效果展示



二、源码分享

1、工程结构

2、main.rs

slint::include_modules!();useslint::{ModelRc,Timer,TimerMode,VecModel};usestd::rc::Rc;usestd::time::{Duration,Instant};/// 简易伪随机数生成器(LCG),返回 0.0 ~ 1.0fnpseudo_random(seed:i32)->f32{letx=((seedasi64).wrapping_mul(1_103_515_245).wrapping_add(12_345))&0x7fff_ffff;xasf32/0x7fff_ffffasf32}fnmain(){letui=MainWindow::new().expect("无法创建主窗口");// ── 浮动图片模型 ──letmodel:Rc<VecModel<FloatingImage>>=Rc::new(VecModel::from(vec![]));ui.set_floating_images(ModelRc::new(model.clone()));// ── 内部状态:每张图片的创建时间(用于清理) ──letcreated_times:Rc<std::cell::RefCell<Vec<Instant>>>=Rc::new(std::cell::RefCell::new(Vec::new()));letcounter=Rc::new(std::cell::Cell::new(0_i32));// ── 注册 spawn-image 回调:Slint Timer 每 2 秒调用一次 ──{letmodel=model.clone();letcreated_times=created_times.clone();letcounter=counter.clone();ui.on_spawn_image(move||{letid=counter.get();counter.set(id+1);letstop_x_ratio=pseudo_random(id);model.push(FloatingImage{id,stop_x_ratio});created_times.borrow_mut().push(Instant::now());});}// ── 清理定时器:每秒清理一次超过 15.5 秒的图片 ──{letmodel=model.clone();letcreated_times=created_times.clone();letcleanup_timer=Timer::default();cleanup_timer.start(TimerMode::Repeated,Duration::from_secs(1),move||{letnow=Instant::now();letmuttimes=created_times.borrow_mut();// 找出过期的索引(从高到低,避免 remove 时索引偏移)// 动画总时长 8 秒,留 500ms 缓冲letmutexpired:Vec<usize>=times.iter().enumerate().filter(|(_,t)|now.duration_since(**t)>=Duration::from_millis(8_500)).map(|(i,_)|i).collect();expired.sort_unstable_by(|a,b|b.cmp(a));// 逐个移除,不重建整个模型(保留其他元素的动画状态)foridxinexpired{times.remove(idx);model.remove(idx);}},);std::mem::forget(cleanup_timer);}ui.run().expect("运行 UI 失败");}

3、build.rs

fnmain(){slint_build::compile("ui/main.slint").unwrap()}

4、main.slint

// ==================== 动态背景 ====================// 浮动图片数据结构exportstructFloatingImage{id:int,// 目标位置占屏幕宽度的比例 (0.0 ~ 1.0),由 Rust 随机生成stop-x-ratio:float,}// 图片资源数组(在 Slint 中加载)globalImageResources{inproperty<[image]>images:[@image-url("../images/apple.svg"),@image-url("../images/banana.svg"),@image-url("../images/watermelon.svg"),@image-url("../images/peach.svg"),@image-url("../images/mango.svg"),@image-url("../images/strawberry.svg"),];}export componentMainWindowinheritsWindow{title:"动态背景";preferred-width:800px;preferred-height:600px;// 浮动图片模型,由 Rust 端管理(增删)inproperty<[FloatingImage]>floating-images;// 回调:请求 Rust 添加新图片到模型callback spawn-image();// 渐变背景(与 QML 一致:从上到下)background:@linear-gradient(180deg,#4bb6ac0%,#3edabd50%,#24efc0100%);// ==================== 生成定时器 ====================// 每 2 秒触发一次,调用 Rust 回调添加新图片Timer{interval:2000ms;running:true;triggered()=>{spawn-image();}}// ==================== 浮动图片层 ====================forimginfloating-images:Rectangle{width:100px;height:100px;// 动画状态:是否已发射property<bool>launched:false;// 旋转角度(0 ~ 360 循环)property<float>rotation-angle:0;// 起始位置(float 计算,最终转 length)property<float>start-x-px:root.width/1px/2-50;property<float>start-y-px:root.height/1px;// 终止位置property<float>stop-x-px:(root.width/1px-100)*img.stop-x-ratio;property<float>stop-y-px:-150;// 当前位置:根据 launched 状态决定x:(launched?stop-x-px:start-x-px)*1px;y:(launched?stop-y-px:start-y-px)*1px;// 旋转:使用 transform-rotationtransform-rotation:rotation-angle*1deg;// 动画:x 和 y 变化时使用 animate(加速到 8 秒)animate x,y{duration:8000ms;easing:linear;}// 透明度动画:整个 8 秒内逐渐淡出opacity:launched?0.0:1.0;animate opacity{duration:8000ms;easing:linear;}// 发射定时器:创建后 10ms 触发,将 launched 设为 true 以启动动画Timer{interval:10ms;running:!launched;triggered()=>{launched=true;}}// 旋转定时器:每 50ms 增加 6 度(3 秒一圈)Timer{interval:50ms;running:true;triggered()=>{rotation-angle=Math.mod(rotation-angle+6,360);}}// 图片渲染(从全局资源数组中按 id 取模获取)Image{width:100px;height:100px;source:ImageResources.images[Math.mod(img.id,6)];}}// 清理逻辑由 Rust 端处理(每秒检查并移除超过 15.5 秒的图片)// ==================== 中央文字 ====================Text{x:(root.width-self.width)/2;y:(root.height-self.height)/2;text:"正在启动中...请稍后!";color:white;font-size:30px;font-weight:700;horizontal-alignment:center;vertical-alignment:center;}}

5、Cargo.toml

[package]name="modbus-debug-assistant"version="0.1.0"edition="2024"[dependencies]slint="1.17.1"[build-dependencies]slint-build="1.17.1"

三、实现原理

本项目的核心思路是Rust 负责数据与逻辑、Slint 负责界面与动画,两者通过 Slint 提供的回调(callback)与模型(model)机制进行通信。下面从数据流、动画机制和资源管理三个维度拆解实现细节。

1、整体数据流

整个动态启动界面的运行流程如下:

Slint Timer 每 2 秒触发

调用 spawn-image 回调

Rust 生成随机目标位置

向 VecModel 推送 FloatingImage

Slint for 循环渲染新图片

图片发射动画 + 旋转 + 淡出

Rust 清理定时器移除过期图片

  • Slint 侧:Timer组件每 2 秒触发一次spawn-image()回调,通知 Rust 端生成新图片。
  • Rust 侧:收到回调后,用 LCG 伪随机数生成器计算目标横坐标比例,构造FloatingImage结构体并推入VecModel。
  • Slint 侧:for img in floating-images响应模型变化,自动为每条新数据创建对应的Rectangle组件实例,并启动动画。

2、动画机制

2.1 发射动画

每张图片创建后,其launched属性初始为false,此时图片停留在屏幕底部起始位置。图片内部的Timer在 10ms 后触发,将launched置为true,从而触发x、y属性的变化:

x: (launched ? stop-x-px : start-x-px) * 1px; y: (launched ? stop-y-px : start-y-px) * 1px; animate x, y { duration: 8000ms; easing: linear; }

由于animate声明了 8 秒的线性过渡,Slint 会自动在起始位置与终止位置之间插值,形成图片从底部缓缓飘向顶部的效果。

2.2 旋转动画

旋转通过独立的Timer实现,每 50ms 将rotation-angle增加 6 度,配合transform-rotation属性让图片持续旋转:

Timer { interval: 50ms; running: true; triggered() => { rotation-angle = Math.mod(rotation-angle + 6, 360); } }

2.3 淡出效果

透明度动画与位移动画同步进行,同样持续 8 秒,从完全不透明渐变为完全透明:

opacity: launched ? 0.0 : 1.0; animate opacity { duration: 8000ms; easing: linear; }

3、资源管理

3.1 图片资源

六张水果图片通过global ImageResources统一管理,在 Slint 中直接加载 SVG 资源。渲染时按图片id对 6 取模,实现循环使用不同图片:

source: ImageResources.images[Math.mod(img.id, 6)];

3.2 模型增删

Rust 端维护VecModel<FloatingImage>作为数据源,同时用Vec<Instant>记录每张图片的创建时间。清理定时器每秒检查一次,将存在超过 8.5 秒的图片索引从高到低逐个移除:

letmutexpired:Vec<usize>=times.iter().enumerate().filter(|(_,t)|now.duration_since(**t)>=Duration::from_millis(8_500)).map(|(i,_)|i).collect();expired.sort_unstable_by(|a,b|b.cmp(a));foridxinexpired{times.remove(idx);model.remove(idx);}

这里有两个关键设计:

  • 从高到低移除:避免remove时索引偏移导致误删。
  • 不重建整个模型:只移除过期元素,保留其他图片的动画状态,避免界面闪烁。

4、关键设计要点

设计点说明
数据与界面分离Rust 只负责数据增删与随机逻辑,Slint 只负责渲染与动画
回调驱动Slint 通过callback主动请求数据,而非 Rust 轮询界面
模型响应式VecModel的增删会自动同步到界面,无需手动刷新
动画状态独立每张图片的launched、rotation-angle均为独立属性,互不干扰
定时器分工生成定时器在 Slint 侧,清理定时器在 Rust 侧,各司其职

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

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

立即咨询