在Flutter跨平台开发中,桌面小工具是一个有趣的应用方向。本文基于Flutter实现类似苹果iPhone 14“灵动岛”的药丸式小组件,详细拆解了Windows和Android两个平台的实现差异,并提供了完整的动画逻辑与系统配置方案。
一、灵动岛动画效果拆解
灵动岛的核心动画包括两个部分:小药丸的横向放大与惯性缩放回弹。实现时,分别用两个AnimationController控制。第一个控制器负责Size渐变,让药丸宽度从初始值(如104逻辑像素)平滑增长到168逻辑像素,高度保持与系统状态栏一致。第二个控制器负责缩放回弹,在第一个动画完成后触发,从1.04或1.06回弹到1.0,模拟惯性效果。
在initState中初始化两个控制器,并为第一个添加状态监听。当状态变为AnimationStatus.completed时,启动缩放动画。
- _animationController = AnimationController(
- duration: const Duration(milliseconds: 500),
- vsync: this,
- );
- _animation = Tween<Size>(
- begin: Size(104.w, EnvConfig.relHeight),
- end: Size(168.w, EnvConfig.relHeight),
- ).animate(_animationController);
- _scaleAnimationController = AnimationController(
- duration: const Duration(milliseconds: 300),
- vsync: this,
- );
- _scaleAnimation = Tween<double>(
- begin: 1,
- end: 1,
- ).animate(_scaleAnimationController);
- _animationController.addStatusListener((status) {
- this.status = status;
- if (status == AnimationStatus.completed) {
- _scaleAnimation = Tween<double>(
- begin: count == 3 ? 1.04 : 1.06,
- end: 1,
- ).animate(_scaleAnimationController);
- _scaleAnimationController.forward(from: 0);
- }
- });
复制代码
布局上使用嵌套的AnimatedBuilder,外层的监听缩放值,内层的监听尺寸值,最终将Container的宽度和高度分别乘以缩放系数,实现双重动画叠加。
小药丸与分离的“i”形小圆点动画同样通过独立的AnimationController实现。小圆点向右移出和向左缩回是通过改变Positioned的right属性负值完成,动画持续时间600毫秒,并使用了easeInOut曲线。
二、Windows端配置为桌面小工具
在Windows上,可直接利用window_manager插件将应用设置为置顶、无标题栏、无任务栏图标的小窗口。关键步骤:
1. 获取主显示器尺寸,根据屏幕高度计算目标窗口宽度和高度(例如高度为屏幕高度的4%,宽度为高度的8倍)。
2. 确保单实例运行。
3. 设置WindowOptions:指定窗口尺寸(size)、最小尺寸、alwaysOnTop为true、TitleBarStyle.hidden、skipTaskbar为true。
4. 在waitUntilReadyToShow回调中,设置窗口背景透明,居中或指定位置(例如距屏幕顶部10像素),显示窗口,聚焦,并设为无边框模式(setAsFrameless)。
5. 窗口背景需要透明才能看到背后的桌面内容。
- static final double relHeight = primaryDisplay.size.height * 0.04;
- static final double relWidth = relHeight * 8;
- final displaySize = Size(relWidth, relHeight * 1.06);
- WindowManager w = WindowManager.instance;
- await w.ensureInitialized();
- WindowOptions windowOptions = WindowOptions(
- size: displaySize,
- minimumSize: displaySize,
- alwaysOnTop: true,
- titleBarStyle: TitleBarStyle.hidden,
- skipTaskbar: true,
- );
- w.waitUntilReadyToShow(windowOptions, () async {
- double w1 = (primaryDisplay.size.width - relWidth) / 2;
- await w.setBackgroundColor(Colors.transparent);
- await w.setPosition(Offset(w1, 10));
- await w.show();
- await w.focus();
- await w.setAsFrameless();
- });
复制代码
这样启动的应用就是一个置顶、无边框、无任务栏图标的浮动小工具,效果与iPhone的灵动岛类似。
三、Android端配置为悬浮窗小工具
Android系统对悬浮窗有较强限制,无法像Windows那样直接通过Flutter框架实现。需要走原生Android方案:创建一个后台Service,通过WindowManager添加一个系统悬浮窗(需要SYSTEM_ALERT_WINDOW权限),并在该悬浮窗内嵌入FlutterView。
具体步骤:
1. 在AndroidManifest.xml中声明权限(SYSTEM_ALERT_WINDOW和FOREGROUND_SERVICE)以及Service组件。
2. 创建后台服务类WindowsService,继承Service。在onCreate中初始化WindowManager的LayoutParams:宽168dp,高30dp,类型为TYPE_SYSTEM_ALERT,FLAG_NOT_FOCUSABLE(使悬浮窗不抢占焦点),背景透明。
3. 使用FlutterEngineGroup创建并运行FlutterEngine。FlutterEngineGroup能够有效管理多个引擎实例,避免静置回收问题。引擎的入口指向main函数。
4. 将FlutterView(通过FlutterSurfaceView承载)添加到悬浮窗布局中,并关联引擎。
5. 通过adb命令启动前台服务:
- adb shell am start-foreground-service -n com.karl.open.desktop_app/com.karl.open.desktop_app.WindowsService
复制代码
注意:Android悬浮窗服务必须为系统应用或有系统签名,否则无法正常显示。另外,为防止引擎被系统回收,一定要使用FlutterEngineGroup进行托管。
四、技术要点与思考
Flutter在桌面端的优势在于统一UI编写,但需要与平台特性结合。Windows端通过window_manager插件可以轻松实现窗口置顶、无边框和透明背景;Android端则需要原生Service和WindowManager的支持。两个平台的核心动画逻辑一致,体现了Flutter跨平台代码复用的价值。
关于灵动岛的设计理念,原文认为它颠覆了用户对状态栏的认知,但目前应用适配较少,用户教育成本高。从开发者角度看,这类小工具的设计应回归实用:提供简单且常用的功能(如天气、快捷入口),真正服务于用户,而不只是酷炫效果。对于桌面端,灵动岛式的小工具可以扩展任务栏或桌面,作为信息提示的优雅载体。
五、总结
本文基于Flutter实现了可置顶、可拖拽(通过触摸事件可扩展)的灵动岛小工具,分别给出了Windows和Android的完整实现方案。关键代码均已在示例中给出,读者可参考文末的GitHub仓库获取完整项目源码。实际开发中,可根据需求调整动画参数、窗口尺寸和功能逻辑,打造属于自己的桌面小工具。 |