Flutter for OpenHarmony:dart_console 打造炫酷命令行界面,绘制表格、控制光标与进度条(CLI 交互库) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前言

虽然 Flutter 主要用于 GUI 开发,但 Dart 也是一门优秀的脚本语言。如果你想写一些命令行工具(如 flutter clean 的替代品,或者 CI/CD 助手),干巴巴的黑白输出显然不够友好。

dart_console 是一个强大的纯 Dart 终端控制库,它支持修改前景色/背景色、控制光标移动(重绘)、读取单字符输入(不需要回车)、以及绘制 ASCII 表格和进度条。

在 GUI (图形用户界面) 盛行的今天,CLI (命令行界面) 依然占据着不可动摇的地位。无论是服务器运维、Docker 容器管理、还是 Git 操作,黑窗口始终是效率的代名词。

但传统的 Dart print() 只能逐行输出,无法做到:

  • 清屏 :像 clear 命令一样。
  • 定位光标:在屏幕任意位置打印(比如覆盖上一行进度条)。
  • 读取按键 :无需回车,直接响应方向键或 y/n
  • 绘制表格:整齐对齐的列数据。

dart_console 是一个功能极其强大的库,它封装了底层的 ANSI 转义序列 (Escape Codes) ,让你能像写 Canvas 一样在终端绘制 UI。

对于 OpenHarmony 开发者,这意味着你可以为鸿蒙设备编写高效的板级调试工具、性能监控看板,甚至是简单的终端游戏!

一、核心原理与 ANSI 协议解析

1.1 VT100 与 ANSI Escape Codes

终端(Terminal)本质上是一个字符流设备。

当你发送 hello,它显示 hello。

当你发送 \x1b[2J,它清空屏幕。

当你发送 \x1b[31m,它将后续字符变成红色。

这些以 ESC (ASCII 27, 0x1b) 开头的特殊指令就是 ANSI 转义序列。
dart_console 的工作原理就是帮你拼接这些字符串。

1.2 Raw Mode vs Cooked Mode

  • Cooked Mode (标准模式) :
    • 用户输入时,终端先缓冲,直到按下回车才一次性发给程序。
    • 支持 Ctrl+C 发送 SIGINT 信号。
    • Dart: 默认模式。
  • Raw Mode (原始模式) :
    • 用户按下一个键,程序立刻收到。
    • 不处理特殊键(如退格、箭头),全靠程序自己画。
    • dart_console : 提供了 Console.rawMode 让你接管一切。

写入
解析
显示文本
ESC [ 31 m
ESC [ 10;20 H
向上箭头
原始模式 (Raw Mode)
控制台应用
标准输出 (stdout)
终端模拟器
屏幕
红色文本
移动光标
Keyboard
标准输入 (stdin)

二、核心 API 详解

2.1 初始化 Console

注意dart_console v4+ 版本精简了部分 API。例如设置标题可能需要直接操作 stdout 或使用 ANSI 序列。

dart 复制代码
import 'dart:io';
import 'package:dart_console/dart_console.dart';

void main() {
  final console = Console();
  
  // 1. 设置标题 (最通用的 ANSI 方式)
  stdout.write('\x1b]0;OpenHarmony Monitor\x07');
  
  // 2. 清屏
  console.clearScreen();
  
  // 3. 隐藏光标 (绘制界面时防止闪烁)
  console.hideCursor();
  
  // 4. 获取窗口大小
  print('Size: ${console.windowWidth} x ${console.windowHeight}');
}

提示:为了方便体验,我们已在项目中提供了可直接运行的 Dart 脚本:

  • lib/dart_console/basic_init_2_1.dart
  • lib/dart_console/colored_text_2_2.dart
  • lib/dart_console/cursor_control_2_3.dart

您可以在项目根目录下运行 dart lib/dart_console/xxxx.dart 来尝试。

2.2 绘制彩色文本

dart 复制代码
// 链式调用
console.setForegroundColor(ConsoleColor.blue);
console.setBackgroundColor(ConsoleColor.white);
console.write(' Hello World ');
console.resetColorAttributes(); // 记得重置!
console.writeLine();

// TextPen 在 v4+ 已移除,请使用 console 直接控制
console.setForegroundColor(ConsoleColor.yellow);
console.write('Warning: ');
console.setForegroundColor(ConsoleColor.white);
console.write('CPU usage high!');
console.resetColorAttributes();
console.writeLine();

2.3 光标控制 (Cursor Positioning)

这是实现进度条或动画的核心。

dart 复制代码
// 移动到第 5 行,第 10 列 (1-based)
console.cursorPosition = Coordinate(5, 10);
console.write('Loading...');

// 覆盖上一行
console.cursorUp();
console.eraseLine();

三、OpenHarmony 平台适配实战

在鸿蒙设备上通过 hdc shell 运行 Dart 程序时,终端环境与 PC 有所不同。

3.1 实战:开发鸿蒙系统监控看板 (Dashboard)

我们将利用 dart_console 实现一个实时刷新的 TUI,显示 CPU 使用率和内存占用。

dart 复制代码
import 'dart:async';
import 'dart:io';
import 'package:dart_console/dart_console.dart';

void main() {
  final console = Console();

  // 1. 初始化屏幕
  console.clearScreen();
  console.hideCursor();
  // console.setWindowTitle('OpenHarmony Monitor Dashboard');
  // 替代方案 (ANSI Code):
  stdout.write('\x1b]0;OpenHarmony Monitor Dashboard\x07');

  // 2. 绘制静态边框
  _drawBorder(console);

  // 3. 定时刷新数据 (每秒)
  Timer? timer;
  timer = Timer.periodic(const Duration(seconds: 1), (t) {
    _updateStats(console);
  });

  // 4. 监听键盘事件 (按 'q' 退出)
  // 注意:在某些环境(如简单的 hdc shell)中,Raw Mode 可能表现不一致。
  // 如果遇到输入乱码,请尝试 Ctrl+C 退出。
  try {
    console.rawMode = true; // 进入原始模式以捕获按键

    // 使用 stdin 流监听按键,避免阻塞 Timer
    stdin.listen((data) {
      final char = String.fromCharCodes(data);
      // 检测 'q' 或 Ctrl+C (ASCII 3)
      if (char.trim() == 'q' || data.contains(3)) {
        timer?.cancel();
        // 恢复终端状态
        console.rawMode = false;
        console.clearScreen();
        console.showCursor();
        console.resetColorAttributes();
        print('监控已停止。');
        exit(0);
      }
    });

    // 保持程序运行 (StreamSubscription 会保持 isolate 活跃,但为了保险可以加个空等待或不做任何事,让 Timer 跑)
    // 注意:Timer.periodic 本身就会保持 isolate 活跃。
  } catch (e) {
    // 如果不支持 Raw Mode (例如非交互式 shell),则只运行定时器
    // 这种情况下用户只能通过外部信号 (Ctrl+C) 终止
  }
}

void _drawBorder(Console console) {
  int width = console.windowWidth;
  int height = console.windowHeight;

  // 简单的边界检查
  if (width < 20 || height < 10) return;

  console.setForegroundColor(ConsoleColor.blue);

  // 顶边
  console.cursorPosition = Coordinate(0, 0);
  console.write('┌${'─' * (width - 2)}┐');

  // 左右边
  for (int i = 1; i < height - 1; i++) {
    console.cursorPosition = Coordinate(i, 0);
    console.write('│');
    console.cursorPosition = Coordinate(i, width - 1);
    console.write('│');
  }

  // 底边
  console.cursorPosition = Coordinate(height - 1, 0);
  console.write('└${'─' * (width - 2)}┘');

  // 标题
  console.resetColorAttributes();
  console.cursorPosition = Coordinate(0, (width - 22) ~/ 2);
  console.write(' OpenHarmony Monitor ');
}

void _updateStats(Console console) {
  // 模拟获取数据
  // 在真实鸿蒙设备上,您可以读取 File('/proc/stat') 或运行 Process.run('top', ...)
  int cpu = DateTime.now().second * 100 ~/ 60; // 0-100 循环
  int mem = 40 + DateTime.now().millisecond % 30; // 模拟波动

  // 绘制时间
  console.cursorPosition = Coordinate(2, 4);
  console.writeLine('系统时间: ${DateTime.now().toIso8601String()}');

  // 绘制 CPU 条
  _drawProgressBar(console, 4, 'CPU 使用率', cpu, ConsoleColor.green);

  // 绘制 Memory 条
  _drawProgressBar(console, 6, '内存占用率', mem, ConsoleColor.yellow);

  // 底部提示
  console.cursorPosition = Coordinate(console.windowHeight - 2, 2);
  console.write('按 "q" 键退出监控...');
}

void _drawProgressBar(Console console, int row, String label, int percentage,
    ConsoleColor color) {
  console.cursorPosition = Coordinate(row, 4);
  console.write('$label: [');

  int barWidth = 40;
  int fillLen = (percentage / 100 * barWidth).round();

  // 绘制进度部分
  console.setForegroundColor(color);
  console.write('=' * fillLen);

  // 绘制剩余部分
  console.resetColorAttributes();
  console.write(' ' * (barWidth - fillLen));

  console.write('] $percentage%');
}

3.2 鸿蒙下的兼容性问题 (hdc shell)

鸿蒙的 HDC Shell 本质上是通过 Socket 转发的 PTY(伪终端)。

  • Window Size : console.windowWidth 可能获取不到准确值(默认为 80)。在运行 dart 程序前,建议手动设置环境变量 export COLUMNS=120
  • Raw Mode: 部分旧版 HDC 可能不支持完全的 Raw Mode(无法捕获 Arrow Keys)。如果遇到乱码,请回退到 Cooked Mode。

四、高级进阶:表格与日历组件

dart_console 内置了高级组件。

4.1 表格 (Table)

dart 复制代码
final table = Table()
  ..insertColumn(header: 'ID', alignment: TextAlignment.right)
  ..insertColumn(header: '姓名')
  ..insertColumn(header: '角色')
  ..insertRows([
    [101, '张三', '管理员'],
    [102, '李四', '用户'],
    [103, '王五', '访客'],
    [103, '赵六', '经理'],
  ]);

console.write(table);

4.2 日历 (Calendar)

dart 复制代码
final calendar = Calendar(DateTime.now());
console.write(calendar);

五、总结

dart_console 让 Dart 走出了简单的脚本语言范畴,成为了构建复杂 CLI 应用的利器。

在 OpenHarmony 生态中,我们可以利用它:

  1. 调试工具: 编写运行在开发板上的测试脚本,实时显示传感器数据。
  2. 安装向导: 在终端中通过箭头键选择安装选项,提升交互体验。

最佳实践

  • 优雅退出 : 始终记得在程序退出前 console.showCursor()console.resetColorAttributes(),否则用户的终端光标可能会消失或一直变色,导致不得不重启终端。
  • 响应式布局 : 监听 console.onResize 事件(如果支持),动态调整 UI。

相关推荐
不懒不懒2 小时前
【Python办公自动化进阶指南:系统交互与网页操作实战】
开发语言·python·交互
Quz2 小时前
QML与JavaScript 交互的四种方式
javascript·qt·交互
加农炮手Jinx2 小时前
Flutter for OpenHarmony 实战:疯狂头像 App(三)— 复合动画与交互反馈 — 让 UI 跃动起来
flutter·ui·交互·harmonyos·鸿蒙
王码码20352 小时前
lutter for OpenHarmony 实战之基础组件:第六十二篇 SystemChannels — 探秘 Flutter 与系统交互的捷径
flutter·microsoft·交互·harmonyos
RaidenLiu4 小时前
别再手写 MethodChannel 了:Flutter Pigeon 工程级实践与架构设计
前端·flutter·前端框架
Bowen_J7 小时前
HarmonyOS 主流跨平台开发框架对比: ArkUI、Flutter、React Native、KMP、UniApp
flutter·react native·harmonyos
Ziky学习记录7 小时前
从输入 URL 到页面可交互:浏览器加载全过程都发生了什么
交互
lili-felicity9 小时前
基础入门 React Native 鸿蒙跨平台开发:react-native-easy-toast三方库适配
react native·react.js·harmonyos
星空22239 小时前
【HarmonyOS】day38:React Native实战项目+输入格式化掩码Hook
react native·华为·harmonyos