构建 OpenHarmony 跨设备任务协同中心:Flutter 实现多端任务流转与状态同步

一、引言:从单设备到分布式协同

OpenHarmony 的核心愿景之一是 "超级终端" ------ 多个物理设备无缝协同,形成一个逻辑上的统一工作空间。例如:

  • 在手机上开始编辑文档,走到平板前自动续写;
  • 车机导航途中,到家后由智慧屏继续播报;
  • 智能手表接收通知,点击后在电视上播放视频。

这种 "任务接力" (Task Continuity) 能力,依赖于设备间的任务注册、状态同步与用户授权 。虽然底层由 OpenHarmony 分布式软总线实现,但前端 UI 必须清晰表达任务的流转状态与操作入口

本文将构建一个模拟页面:「跨设备任务协同中心」。它具备以下特性:

  1. 展示当前设备正在处理的任务(如"编辑报告"、"播放音乐");
  2. 列出附近可信设备(手机、平板、车机等);
  3. 提供"推送任务"按钮,将当前任务发送至其他设备;
  4. 实时反馈任务状态(已推送 / 接收中 / 已完成);
  5. 响应式布局:手机竖屏为列表,横屏/平板为双栏,桌面为三栏信息面板。

这不仅是一个 UI 演示,更是对 分布式场景下用户心智模型 的可视化探索。


二、完整可运行代码

dart 复制代码
// lib/main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'OpenHarmony 任务协同中心',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        appBarTheme: const AppBarTheme(centerTitle: true),
      ),
      home: const TaskContinuityPage(),
    );
  }
}

/// 任务状态枚举
enum TaskStatus { active, pushed, received, completed }

/// 任务类型
class Task {
  final String id;
  final String title;
  final String description;
  final IconData icon;
  late TaskStatus status;

  Task({
    required this.id,
    required this.title,
    required this.description,
    required this.icon,
    TaskStatus? status,
  }) : status = status ?? TaskStatus.active;
}

/// 设备类型(用于模拟附近设备)
class NearbyDevice {
  final String id;
  final String name;
  final String type; // "phone", "tablet", "car", "tv"
  final bool isTrusted;

  const NearbyDevice({
    required this.id,
    required this.name,
    required this.type,
    this.isTrusted = true,
  });
}

/// 模拟数据源
final List<Task> _mockActiveTasks = [
  Task(
    id: 'task_001',
    title: '编辑Q3财报',
    description: '使用WPS编辑季度财务报告',
    icon: Icons.description,
  ),
  Task(
    id: 'task_002',
    title: '播放播客',
    description: '《科技早知道》第128期',
    icon: Icons.podcasts,
  ),
];

final List<NearbyDevice> _mockNearbyDevices = [
  const NearbyDevice(id: 'dev_1', name: '我的手机', type: 'phone', isTrusted: true),
  const NearbyDevice(id: 'dev_2', name: '办公平板', type: 'tablet', isTrusted: true),
  const NearbyDevice(id: 'dev_3', name: '家庭智慧屏', type: 'tv', isTrusted: true),
  const NearbyDevice(id: 'dev_4', name: '车载系统', type: 'car', isTrusted: false),
];

class TaskContinuityPage extends StatefulWidget {
  const TaskContinuityPage({super.key});

  @override
  State<TaskContinuityPage> createState() => _TaskContinuityPageState();
}

class _TaskContinuityPageState extends State<TaskContinuityPage> {
  late List<Task> _tasks;
  late List<NearbyDevice> _devices;

  @override
  void initState() {
    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(Task task, NearbyDevice device) async {
    if (task.status != TaskStatus.active) return;

    // 更新本地任务状态为"已推送"
    setState(() {
      task.status = TaskStatus.pushed;
    });

    // 模拟网络延迟
    await Future.delayed(const Duration(seconds: 1));

    // 模拟设备接收成功(实际应通过分布式软总线回调)
    if (device.isTrusted) {
      setState(() {
        task.status = TaskStatus.received;
      });
      await Future.delayed(const Duration(seconds: 1));
      setState(() {
        task.status = TaskStatus.completed;
      });
    } else {
      // 不可信设备,回滚状态
      await Future.delayed(const Duration(seconds: 1));
      setState(() {
        task.status = TaskStatus.active;
      });
    }
  }

  /// 获取设备方向与尺寸,决定布局模式
  bool get _isLandscapeOrLarge {
    final size = MediaQuery.sizeOf(context);
    return size.width > size.height || size.shortestSide >= 600;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('任务协同中心')),
      body: _isLandscapeOrLarge
          ? _buildDualPaneLayout()
          : _buildSingleColumnLayout(),
    );
  }

  /// 单列布局(手机竖屏)
  Widget _buildSingleColumnLayout() {
    return ListView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: _tasks.length,
      itemBuilder: (context, index) {
        final task = _tasks[index];
        return Column(
          children: [
            _buildTaskCard(task),
            const SizedBox(height: 16),
            _buildDeviceSelector(task),
            const SizedBox(height: 24),
          ],
        );
      },
    );
  }

  /// 双栏/三栏布局(横屏、平板、桌面)
  Widget _buildDualPaneLayout() {
    return Row(
      children: [
        Expanded(
          flex: 2,
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('当前任务', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
                const SizedBox(height: 16),
                Expanded(
                  child: ListView.builder(
                    itemCount: _tasks.length,
                    itemBuilder: (context, index) => _buildTaskCard(_tasks[index]),
                  ),
                ),
              ],
            ),
          ),
        ),
        const VerticalDivider(width: 1),
        Expanded(
          flex: 3,
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('推送至设备', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
                const SizedBox(height: 16),
                ..._tasks.map((task) => Column(
                      children: [
                        Text('${task.title} →', style: const TextStyle(fontWeight: FontWeight.w500)),
                        const SizedBox(height: 8),
                        _buildDeviceSelector(task),
                        const SizedBox(height: 20),
                      ],
                    )),
              ],
            ),
          ),
        ),
      ],
    );
  }

  /// 构建任务卡片
  Widget _buildTaskCard(Task task) {
    final statusInfo = _getStatusInfo(task.status);
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            Icon(task.icon, size: 24, color: Theme.of(context).colorScheme.primary),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(task.title, style: Theme.of(context).textTheme.titleMedium),
                  const SizedBox(height: 4),
                  Text(task.description, style: Theme.of(context).textTheme.bodySmall),
                  const SizedBox(height: 8),
                  Container(
                    padding: const EdgeInsets.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(Task task) {
    return Wrap(
      spacing: 8,
      runSpacing: 8,
      children: _devices.map((device) {
        final isDisabled = task.status != TaskStatus.active;
        return ActionChip(
          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(String type) {
    switch (type) {
      case 'phone':
        return const Icon(Icons.phone_android, size: 18);
      case 'tablet':
        return const Icon(Icons.tablet_android, size: 18);
      case 'tv':
        return const Icon(Icons.tv, size: 18);
      case 'car':
        return const Icon(Icons.directions_car, size: 18);
      default:
        return const Icon(Icons.devices, size: 18);
    }
  }

  /// 获取任务状态显示信息
  ({Color color, String text}) _getStatusInfo(TaskStatus status) {
    switch (status) {
      case TaskStatus.active:
        return (color: Colors.blue, text: '进行中');
      case TaskStatus.pushed:
        return (color: Colors.orange, text: '已推送');
      case TaskStatus.received:
        return (color: Colors.purple, text: '接收中');
      case TaskStatus.completed:
        return (color: Colors.green, text: '已完成');
    }
  }
}

✅ 此代码无需额外依赖,可直接在 Flutter 3.24+ 环境中运行,完美适配 OpenHarmony 模拟器。


三、页面创新点与设计思想解析

1. 全新交互范式:任务为中心,而非设备或能力

前三篇文章分别聚焦于:

  • 设备类型识别;
  • 主题切换;
  • 硬件能力检测。

而本文以"任务"为核心实体,设备只是任务的载体。这更贴近 OpenHarmony "服务化"与"原子化"的设计理念------应用功能被拆解为可流转的服务单元。

2. 动态布局策略:基于方向与尺寸的智能切换

dart 复制代码
bool get _isLandscapeOrLarge {
  final size = MediaQuery.sizeOf(context);
  return size.width > size.height || size.shortestSide >= 600;
}
  • 手机竖屏 :采用 ListView 单列布局,任务与设备选择器垂直堆叠,适合单手操作;
  • 横屏/平板/桌面 :切换为 Row 双栏布局,左侧任务列表,右侧设备推送区,信息密度更高;
  • 不依赖固定断点 ,而是结合屏幕方向最小边长,更符合真实使用场景。

💡 这种"情境感知布局"比单纯按设备类型划分更智能。


四、核心组件深度剖析

1. TaskNearbyDevice:领域模型定义

dart 复制代码
class Task {
  final String id;
  final String title;
  final String description;
  final IconData icon;
  late TaskStatus status;
}
  • Task 是有状态的业务对象 ,其 status 会随用户操作变化;
  • NearbyDevice 包含 isTrusted 字段,模拟 OpenHarmony 的设备认证机制------只有可信设备才能接收任务。

📌 这些类构成了页面的数据骨架,UI 完全由它们驱动。


2. 任务状态机:TaskStatus 与状态流转

dart 复制代码
enum TaskStatus { active, pushed, received, completed }
  • active:任务在当前设备运行;
  • pushed:已发出推送请求;
  • received:目标设备确认接收;
  • completed:任务在目标设备完成。

状态流转通过 _pushTaskTo 方法模拟:

  • 可信设备:active → pushed → received → completed
  • 不可信设备:active → pushed → active(回滚)。

✅ 这种状态机设计,为未来接入真实分布式 API 提供了清晰接口。


3. 响应式布局实现:_buildSingleColumnLayout vs _buildDualPaneLayout

  • 单列布局 使用 ListView.builder,每个任务项包含卡片 + 设备选择器;
  • 双栏布局 使用 Row + Expanded,左侧固定任务列表,右侧动态生成"任务→设备"映射;
  • VerticalDivider 提供视觉分隔,增强信息分区。

📐 布局切换无动画过渡 (为简化),但可通过 AnimatedSwitcher 优化。


4. 交互控件:ActionChip 的巧妙运用

dart 复制代码
ActionChip(
  label: Text(device.name),
  avatar: _getDeviceIcon(device.type),
  onPressed: isDisabled ? null : () => _pushTaskTo(task, device),
  backgroundColor: device.isTrusted ? Colors.blue.shade50 : Colors.red.shade50,
)
  • ActionChipButton 更轻量,适合多选项场景;
  • 图标 + 文字 快速识别设备类型;
  • 背景色区分可信度:蓝色=可信,红色=不可信;
  • 禁用状态:当任务非 active 时,所有芯片置灰不可点。

✅ 这种设计让用户一眼识别可操作项与风险项


5. 状态反馈:颜色编码与文本提示

dart 复制代码
({Color color, String text}) _getStatusInfo(TaskStatus status) {
  switch (status) {
    case TaskStatus.active: return (color: Colors.blue, text: '进行中');
    // ...
  }
}


  • 蓝色:进行中(主操作色);
  • 橙色:已推送(等待中);
  • 紫色:接收中(跨设备同步);
  • 绿色:已完成(成功状态)。

颜色心理学在此得到应用:用户无需阅读文字,仅凭颜色即可理解状态。


五、工程价值与扩展性

1. 易于对接 OpenHarmony 分布式能力

  • _pushTaskTo 中的 Future.delayed 替换为:

    dart 复制代码
    final result = await MethodChannel('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),告知操作已受理;
  • 模拟进度(receivedcompleted),减少等待焦虑。

3. 防错设计

  • 不可信设备用红色背景警示;
  • 非 active 任务自动禁用操作,防止误触。

七、结语:协同是未来的 UI 范式

本文构建的「跨设备任务协同中心」,超越了传统单设备 UI 的局限,进入了 "多设备协同" 的新维度。它不仅是 OpenHarmony 分布式能力的前端体现,更是对未来人机交互方式的一次探索。

在万物互联的时代,应用不再是孤立的程序,而是流动的服务。作为开发者,我们有责任设计出能清晰表达这种流动性的界面。

🌐 欢迎加入开源鸿蒙跨平台社区

https://openharmonycrossplatform.csdn.net/

在这里,您将获得:

  • OpenHarmony 分布式任务开发指南;
  • Flutter 跨设备状态同步最佳实践;
  • 实战项目模板与社区支持。

让我们携手,打造真正无缝的超级终端体验!

相关推荐
●VON2 小时前
Flutter for OpenHarmony:基于可空截止日期与时间语义可视化的 TodoList 时间管理子系统实现
安全·flutter·交互·openharmony·跨平台开发
晚霞的不甘3 小时前
Flutter for OpenHarmony 引力弹球游戏开发全解析:从零构建一个交互式物理小游戏
前端·flutter·云原生·前端框架·游戏引擎·harmonyos·骨骼绑定
雨季6663 小时前
构建 OpenHarmony 智能场景自动化配置面板:Flutter 实现可视化规则编排
运维·flutter·自动化
[H*]3 小时前
Positioned高级定位技巧
flutter·华为·harmonyos
b2077213 小时前
Flutter for OpenHarmony 身体健康状况记录App实战 - 数据导出实现
python·flutter·harmonyos
雨季6664 小时前
构建 OpenHarmony 简易 BMI 健康指数计算器:用基础数学实现健康自评
javascript·flutter·ui·自动化·dart
●VON4 小时前
Flutter for OpenHarmony:基于选择模式状态机与原子批量更新的 TodoList 批量操作子系统实现
学习·flutter·ui·openharmony·von
一起养小猫4 小时前
Flutter for OpenHarmony 实战:贪吃蛇蛇的移动逻辑详解
android·flutter
晚霞的不甘4 小时前
Flutter for OpenHarmony:从零到一:构建购物APP的骨架与精美UI
前端·javascript·flutter·ui·前端框架·鸿蒙