一、引言:从单设备到分布式协同
OpenHarmony 的核心愿景之一是“超级终端”—— 多个物理设备无缝协同,形成一个逻辑上的统一工作空间。例如:
- 在手机上开始编辑文档,走到平板前自动续写;
- 车机导航途中,到家后由智慧屏继续播报;
- 智能手表接收通知,点击后在电视上播放视频。
这种“任务接力”(Task Continuity) 能力,依赖于设备间的任务注册、状态同步与用户授权。虽然底层由 OpenHarmony 分布式软总线实现,但前端 UI 必须清晰表达任务的流转状态与操作入口。
本文将构建一个模拟页面:「跨设备任务协同中心」。它具备以下特性:
- 展示当前设备正在处理的任务(如“编辑报告”、“播放音乐”);
- 列出附近可信设备(手机、平板、车机等);
- 提供“推送任务”按钮,将当前任务发送至其他设备;
- 实时反馈任务状态(已推送 / 接收中 / 已完成);
- 响应式布局:手机竖屏为列表,横屏/平板为双栏,桌面为三栏信息面板。
这不仅是一个 UI 演示,更是对分布式场景下用户心智模型的可视化探索。
二、完整可运行代码
// lib/main.dartimport'package:flutter/material.dart';voidmain(){runApp(constMyApp());}classMyAppextendsStatelessWidget{constMyApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'OpenHarmony 任务协同中心',debugShowCheckedModeBanner:false,theme:ThemeData(useMaterial3:true,colorScheme:ColorScheme.fromSeed(seedColor:Colors.indigo),appBarTheme:constAppBarTheme(centerTitle:true),),home:constTaskContinuityPage(),);}}/// 任务状态枚举enumTaskStatus{active,pushed,received,completed}/// 任务类型classTask{finalStringid;finalStringtitle;finalStringdescription;finalIconDataicon;lateTaskStatusstatus;Task({requiredthis.id,requiredthis.title,requiredthis.description,requiredthis.icon,TaskStatus?status,}):status=status??TaskStatus.active;}/// 设备类型(用于模拟附近设备)classNearbyDevice{finalStringid;finalStringname;finalStringtype;// "phone", "tablet", "car", "tv"finalbool isTrusted;constNearbyDevice({requiredthis.id,requiredthis.name,requiredthis.type,this.isTrusted=true,});}/// 模拟数据源finalList<Task>_mockActiveTasks=[Task(id:'task_001',title:'编辑Q3财报',description:'使用WPS编辑季度财务报告',icon:Icons.description,),Task(id:'task_002',title:'播放播客',description:'《科技早知道》第128期',icon:Icons.podcasts,),];finalList<NearbyDevice>_mockNearbyDevices=[constNearbyDevice(id:'dev_1',name:'我的手机',type:'phone',isTrusted:true),constNearbyDevice(id:'dev_2',name:'办公平板',type:'tablet',isTrusted:true),constNearbyDevice(id:'dev_3',name:'家庭智慧屏',type:'tv',isTrusted:true),constNearbyDevice(id:'dev_4',name:'车载系统',type:'car',isTrusted:false),];classTaskContinuityPageextendsStatefulWidget{constTaskContinuityPage({super.key});@overrideState<TaskContinuityPage>createState()=>_TaskContinuityPageState();}class_TaskContinuityPageStateextendsState<TaskContinuityPage>{lateList<Task>_tasks;lateList<NearbyDevice>_devices;@overridevoidinitState(){super.initState();// 深拷贝模拟数据,避免修改原始列表_tasks=_mockActiveTasks.map((t)=>Task(id:t.id,title:t.title,description:t.description,icon:t.icon,status:t.status,)).toList();_devices=List.of(_mockNearbyDevices);}/// 模拟推送任务到设备(异步)Future<void>_pushTaskTo(Tasktask,NearbyDevicedevice)async{if(task.status!=TaskStatus.active)return;// 更新本地任务状态为“已推送”setState((){task.status=TaskStatus.pushed;});// 模拟网络延迟awaitFuture.delayed(constDuration(seconds:1));// 模拟设备接收成功(实际应通过分布式软总线回调)if(device.isTrusted){setState((){task.status=TaskStatus.received;});awaitFuture.delayed(constDuration(seconds:1));setState((){task.status=TaskStatus.completed;});}else{// 不可信设备,回滚状态awaitFuture.delayed(constDuration(seconds:1));setState((){task.status=TaskStatus.active;});}}/// 获取设备方向与尺寸,决定布局模式boolget_isLandscapeOrLarge{finalsize=MediaQuery.sizeOf(context);returnsize.width>size.height||size.shortestSide>=600;}@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('任务协同中心')),body:_isLandscapeOrLarge?_buildDualPaneLayout():_buildSingleColumnLayout(),);}/// 单列布局(手机竖屏)Widget_buildSingleColumnLayout(){returnListView.builder(padding:constEdgeInsets.all(16),itemCount:_tasks.length,itemBuilder:(context,index){finaltask=_tasks[index];returnColumn(children:[_buildTaskCard(task),constSizedBox(height:16),_buildDeviceSelector(task),constSizedBox(height:24),],);},);}/// 双栏/三栏布局(横屏、平板、桌面)Widget_buildDualPaneLayout(){returnRow(children:[Expanded(flex:2,child:Padding(padding:constEdgeInsets.all(16),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[constText('当前任务',style:TextStyle(fontSize:20,fontWeight:FontWeight.bold)),constSizedBox(height:16),Expanded(child:ListView.builder(itemCount:_tasks.length,itemBuilder:(context,index)=>_buildTaskCard(_tasks[index]),),),],),),),constVerticalDivider(width:1),Expanded(flex:3,child:Padding(padding:constEdgeInsets.all(16),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[constText('推送至设备',style:TextStyle(fontSize:20,fontWeight:FontWeight.bold)),constSizedBox(height:16),..._tasks.map((task)=>Column(children:[Text('${task.title}→',style:constTextStyle(fontWeight:FontWeight.w500)),constSizedBox(height:8),_buildDeviceSelector(task),constSizedBox(height:20),],)),],),),),],);}/// 构建任务卡片Widget_buildTaskCard(Tasktask){finalstatusInfo=_getStatusInfo(task.status);returnCard(child:Padding(padding:constEdgeInsets.all(16),child:Row(children:[Icon(task.icon,size:24,color:Theme.of(context).colorScheme.primary),constSizedBox(width:16),Expanded(child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Text(task.title,style:Theme.of(context).textTheme.titleMedium),constSizedBox(height:4),Text(task.description,style:Theme.of(context).textTheme.bodySmall),constSizedBox(height:8),Container(padding:constEdgeInsets.symmetric(horizontal:8,vertical:4),decoration:BoxDecoration(color:statusInfo.color.withOpacity(0.15),borderRadius:BorderRadius.circular(4),),child:Text(statusInfo.text,style:TextStyle(color:statusInfo.color,fontSize:12,fontWeight:FontWeight.bold),),),],),),],),),);}/// 构建设备选择器(带推送按钮)Widget_buildDeviceSelector(Tasktask){returnWrap(spacing:8,runSpacing:8,children:_devices.map((device){finalisDisabled=task.status!=TaskStatus.active;returnActionChip(label:Text(device.name),avatar:_getDeviceIcon(device.type),onPressed:isDisabled?null:()=>_pushTaskTo(task,device),backgroundColor:isDisabled?Colors.grey.shade200:(device.isTrusted?Colors.blue.shade50:Colors.red.shade50),disabledColor:Colors.grey.shade200,);}).toList(),);}/// 根据设备类型返回图标Widget_getDeviceIcon(Stringtype){switch(type){case'phone':returnconstIcon(Icons.phone_android,size:18);case'tablet':returnconstIcon(Icons.tablet_android,size:18);case'tv':returnconstIcon(Icons.tv,size:18);case'car':returnconstIcon(Icons.directions_car,size:18);default:returnconstIcon(Icons.devices,size:18);}}/// 获取任务状态显示信息({Colorcolor,Stringtext})_getStatusInfo(TaskStatusstatus){switch(status){caseTaskStatus.active:return(color:Colors.blue,text:'进行中');caseTaskStatus.pushed:return(color:Colors.orange,text:'已推送');caseTaskStatus.received:return(color:Colors.purple,text:'接收中');caseTaskStatus.completed:return(color:Colors.green,text:'已完成');}}}✅ 此代码无需额外依赖,可直接在 Flutter 3.24+ 环境中运行,完美适配 OpenHarmony 模拟器。
三、页面创新点与设计思想解析
1. 全新交互范式:任务为中心,而非设备或能力
前三篇文章分别聚焦于:
- 设备类型识别;
- 主题切换;
- 硬件能力检测。
而本文以“任务”为核心实体,设备只是任务的载体。这更贴近 OpenHarmony “服务化”与“原子化”的设计理念——应用功能被拆解为可流转的服务单元。
2. 动态布局策略:基于方向与尺寸的智能切换
boolget_isLandscapeOrLarge{finalsize=MediaQuery.sizeOf(context);returnsize.width>size.height||size.shortestSide>=600;}- 手机竖屏:采用
ListView单列布局,任务与设备选择器垂直堆叠,适合单手操作; - 横屏/平板/桌面:切换为
Row双栏布局,左侧任务列表,右侧设备推送区,信息密度更高; - 不依赖固定断点,而是结合屏幕方向与最小边长,更符合真实使用场景。
💡 这种“情境感知布局”比单纯按设备类型划分更智能。
四、核心组件深度剖析
1.Task与NearbyDevice:领域模型定义
classTask{finalStringid;finalStringtitle;finalStringdescription;finalIconDataicon;lateTaskStatusstatus;}Task是有状态的业务对象,其status会随用户操作变化;NearbyDevice包含isTrusted字段,模拟 OpenHarmony 的设备认证机制——只有可信设备才能接收任务。
📌 这些类构成了页面的数据骨架,UI 完全由它们驱动。
2. 任务状态机:TaskStatus与状态流转
enumTaskStatus{active,pushed,received,completed}active:任务在当前设备运行;pushed:已发出推送请求;received:目标设备确认接收;completed:任务在目标设备完成。
状态流转通过_pushTaskTo方法模拟:
- 可信设备:
active → pushed → received → completed; - 不可信设备:
active → pushed → active(回滚)。
✅ 这种状态机设计,为未来接入真实分布式 API 提供了清晰接口。
3. 响应式布局实现:_buildSingleColumnLayoutvs_buildDualPaneLayout
- 单列布局使用
ListView.builder,每个任务项包含卡片 + 设备选择器; - 双栏布局使用
Row+Expanded,左侧固定任务列表,右侧动态生成“任务→设备”映射; VerticalDivider提供视觉分隔,增强信息分区。
📐 布局切换无动画过渡(为简化),但可通过
AnimatedSwitcher优化。
4. 交互控件:ActionChip的巧妙运用
ActionChip(label:Text(device.name),avatar:_getDeviceIcon(device.type),onPressed:isDisabled?null:()=>_pushTaskTo(task,device),backgroundColor:device.isTrusted?Colors.blue.shade50:Colors.red.shade50,)ActionChip比Button更轻量,适合多选项场景;- 图标 + 文字快速识别设备类型;
- 背景色区分可信度:蓝色=可信,红色=不可信;
- 禁用状态:当任务非 active 时,所有芯片置灰不可点。
✅ 这种设计让用户一眼识别可操作项与风险项。
5. 状态反馈:颜色编码与文本提示
({Colorcolor,Stringtext})_getStatusInfo(TaskStatusstatus){switch(status){caseTaskStatus.active:return(color:Colors.blue,text:'进行中');// ...}}- 蓝色:进行中(主操作色);
- 橙色:已推送(等待中);
- 紫色:接收中(跨设备同步);
- 绿色:已完成(成功状态)。
颜色心理学在此得到应用:用户无需阅读文字,仅凭颜色即可理解状态。
五、工程价值与扩展性
1. 易于对接 OpenHarmony 分布式能力
- 将
_pushTaskTo中的Future.delayed替换为:finalresult=awaitMethodChannel('ohos/distributed_task').invokeMethod('pushTask',{'taskId':task.id,'targetDeviceId':device.id},); - 监听分布式回调更新
Task.status。
2. 支持任务历史与撤销
- 新增
TaskHistoryPage展示已完成任务; - 在
pushed状态下提供“取消推送”按钮。
3. 多任务并发处理
- 当前一次只处理一个任务,可扩展为支持批量推送;
- 使用
Future.wait并行推送至多个设备。
4. 安全与权限提示
- 对不可信设备,点击时弹出授权对话框;
- 集成 OpenHarmony 权限管理 API。
六、用户体验思考
1. 降低认知负荷
- 用户无需理解“分布式软总线”等技术概念;
- 通过“推送至设备”这一动作,直观表达任务流转意图。
2. 提供即时反馈
- 点击后立即变色(
pushed),告知操作已受理; - 模拟进度(
received→completed),减少等待焦虑。
3. 防错设计
- 不可信设备用红色背景警示;
- 非 active 任务自动禁用操作,防止误触。
七、结语:协同是未来的 UI 范式
本文构建的「跨设备任务协同中心」,超越了传统单设备 UI 的局限,进入了“多设备协同”的新维度。它不仅是 OpenHarmony 分布式能力的前端体现,更是对未来人机交互方式的一次探索。
在万物互联的时代,应用不再是孤立的程序,而是流动的服务。作为开发者,我们有责任设计出能清晰表达这种流动性的界面。
🌐欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net/
在这里,您将获得:
- OpenHarmony 分布式任务开发指南;
- Flutter 跨设备状态同步最佳实践;
- 实战项目模板与社区支持。
让我们携手,打造真正无缝的超级终端体验!