基于HarmonyOS 7.0 跨端开发的中国象棋残局复盘页面实战

前言

棋类教学应用的灵魂,是那块能精准呈现棋局的棋盘。中国象棋的棋盘有其独特的形制——9 路 10 行的交叉点、九宫斜线、楚河汉界,这些都无法用现成的网格组件简单拼出来,必须借助自绘。本文以一个真实的中国象棋残局复盘页面(入口类 SearchPage)为样本,深入剖析它如何在 Flutter × HarmonyOS 7.0 架构下,用 Canvas 自绘标准象棋棋盘、用难度分级的残局库列表与可点选的棋谱步骤序列,把"经典残局破解与复盘"的体验完整落地。这是一个把"复杂自绘"与"步骤状态联动"结合得很充分的页面,通过拆解它,我们能透彻理解 Flutter 的 CustomPaint 网格绘制、棋子渲染、以及"当前步"状态如何驱动整个步骤列表的视觉变化。
在这里插入图片描述

背景

象棋残局工具的核心是"看棋盘、学走法、悟思路":用户先按单兵、双兵、马炮、车兵、名局、江湖等分类挑选残局,在标准棋盘上看清当前棋子布局,再跟着破解步骤一步步推演,理解每一手的意图与评分。本页面在视觉上采用中国象棋的传统配色,红木棋盘底色(0xFFDEB887)、棕色网格线与楚河汉界、红黑双色棋子。结构上从上到下依次是:标题栏(带"破解 45 局"统计)、横向分类标签、Canvas 绘制的象棋棋盘、残局库列表(每个残局标注难度与步数),以及破解步骤序列(棋谱格式,可点击切换"当前步",已走过的步骤高亮)。其中棋盘完全由 CustomPaint 自绘,步骤列表则用一个 _currentStep 状态驱动高亮范围,是自绘与状态联动的双重示范。

Flutter × Harmony7.0 跨端开发介绍

在 HarmonyOS 7.0 上运行本页面,前提是使用 HarmonyOS 维护的定制版 Flutter SDK,因为鸿蒙对 Flutter 的支持是由 HarmonyOS 跨平台 SIG 通过 fork 扩展 Flutter SDK 实现的。

这个页面最能体现 Flutter 在鸿蒙上的自绘机制。象棋棋盘是用 CustomPaint 配合自定义 CustomPainter 绘制的:我们在 paint(Canvas, Size) 里调用的 drawLine(画网格与九宫斜线)、drawCircle(画棋子底盘)、以及 TextPainter(绘制"楚河汉界"和棋子汉字),这些绘图指令会被 Flutter Engine 收集并下沉到 Skia 图形库执行。需要强调的是,Flutter 界面由它自身的自绘渲染引擎绘制,而非交给 ArkUI 控件逐一渲染,但渲染所需的 GPU 上下文与 Surface 来自鸿蒙系统——Flutter Engine 在底层接入鸿蒙的 ArkUI RenderingContext 获取渲染上下文,再由 ArkTS 容器 FlutterAbility 承载最终输出。

对迁移而言,本页面是纯 Dart、无原生依赖的"可直接复用"场景:棋盘、棋子、楚河汉界全部是 Dart 代码加 Flutter 内置 Canvas API 实现,不涉及任何鸿蒙原生绘图接口。对于这类大量使用 Canvas 绘制的页面,AOT 编译带来的原生执行效率尤为关键——网格线、九宫斜线、棋子圆形与汉字测绘都是计算密集操作,在 Release 模式下编译为 ARM64 机器码后才能保证棋盘绘制的高帧率,这也是 Flutter 适合做棋类、绘图类应用的根本原因。

开发核心代码

第一部分:CustomPaint 绘制标准象棋棋盘网格。 把画布按 9 列 10 行划分单元格,用 drawLine 循环画出竖线与横线,再补上九宫斜线:


void paint(Canvas canvas, Size size) {
  final paint = Paint()..color = const Color(0xFF8B4513)..strokeWidth = 1;
  final cellW = size.width / 9, cellH = size.height / 10;
  final marginX = cellW, marginY = cellH;
  // 9 条竖线
  for (int i = 0; i < 9; i++) {
    canvas.drawLine(Offset(marginX + i * cellW, marginY),
        Offset(marginX + i * cellW, marginY + 9 * cellH), paint);
  }
  // 10 条横线
  for (int j = 0; j < 10; j++) {
    canvas.drawLine(Offset(marginX, marginY + j * cellH),
        Offset(marginX + 8 * cellW, marginY + j * cellH), paint);
  }
  // 九宫斜线
  canvas.drawLine(Offset(marginX + 3 * cellW, marginY),
      Offset(marginX + 5 * cellW, marginY + 2 * cellH), paint);
}

这段代码的核心是把画布尺寸按 9×10 等分得到单元格宽高 cellW/cellH,再以此为基准用循环画出所有网格线。这种"按比例划分画布"的写法让棋盘能自适应任意尺寸的容器,在鸿蒙不同屏幕上都能正确呈现标准比例的棋盘。

在这里插入图片描述

第二部分:棋子的多层圆形 + 汉字绘制。 用三层同心圆构造棋子的立体边框,再用 TextPainter 在圆心绘制居中的红/黑棋子汉字:

void _drawPiece(Canvas canvas, Offset center, String text, bool isBlack) {
  canvas.drawCircle(center, 14, Paint()..color = const Color(0xFFDEB887)); // 外圈底
  canvas.drawCircle(center, 13, Paint()
    ..color = isBlack ? const Color(0xFF1F2937) : const Color(0xFFDC2626)); // 红/黑边
  final tp = TextPainter(
    text: TextSpan(text: text, style: const TextStyle(color: Color(0xFFF5DEB3), fontSize: 14)),
    textDirection: TextDirection.ltr,
  )..layout();
  tp.paint(canvas, Offset(center.dx - tp.width / 2, center.dy - tp.height / 2));
}

棋子用同心圆叠出边框层次,红方用红色、黑方用深灰,汉字用米色以保证在深色棋子上清晰可读。TextPainter 绘制前必须 layout(),再用 center - 尺寸/2 把文字精确居中于棋子圆心——这是 Canvas 绘制居中文本的通用公式。

第三部分:"当前步"状态驱动的棋谱步骤列表。 用一个 _currentStep 状态标记进度,点击某步即跳转,已走过的步骤整体高亮:

..._moves.asMap().entries.map((entry) {
  final i = entry.key; final m = entry.value;
  final active = i <= _currentStep;  // 当前步及之前都算"已走"
  return GestureDetector(
    onTap: () => setState(() => _currentStep = i),
    child: Container(
      decoration: BoxDecoration(
        color: active ? const Color(0x1FDAA520) : Colors.transparent),
      child: Row(children: [
        Container(  // 步骤序号圆点
          decoration: BoxDecoration(shape: BoxShape.circle,
            color: active ? const Color(0xFFDAA520) : const Color(0xFF6B5B3A)),
          child: Text('${i + 1}'),
        ),
        Expanded(child: Text('${m['move']} — ${m['comment']}',
            style: TextStyle(color: active ? Colors.white : const Color(0xFFA89880)))),
      ]),
    ),
  );
})

这里用 i <= _currentStep 判断每一步是否"已走过",从而让当前步及之前的所有步骤都高亮、之后的步骤变暗,直观呈现复盘进度。点击任意步骤即把 _currentStep 跳到该步,整个列表的高亮范围随之刷新,这是用单一状态驱动批量视觉变化的经典模式。
在这里插入图片描述

心得

做这个象棋残局页面,最大的体会是棋盘自绘对"按比例划分画布"思维的训练。象棋棋盘是 9 路 10 行的固定形制,我没有写死任何像素坐标,而是把画布宽高分别除以 9 和 10 得到单元格尺寸,所有网格线、九宫斜线、棋子位置都以单元格为基准用倍数表达。这样做的好处是棋盘彻底与容器尺寸解耦——无论这个 CustomPaint 被放进多大的区域,棋盘都能保持标准的 9×10 比例正确绘制。这在鸿蒙这种多设备形态的生态里尤为重要,手机、平板、折叠屏的棋盘都能正确呈现。绘制九宫斜线、楚河汉界这些象棋特有的元素时,也都是用单元格倍数来定位起止点,逻辑清晰且易于校对。这种"网格化思维"是做任何棋盘、表格类自绘的基本功。

第二个深刻的体会来自棋子的绘制。一个看似简单的棋子,其实包含了底盘、彩色边框、居中汉字几个层次,我用同心圆从外到内叠出立体感,再用 TextPainter 把汉字精确居中。绘制居中文本时,center.dx - tp.width / 2 这个公式我反复用到——它的本质是"把文本的中心对齐到目标点",无论是棋子汉字还是其它需要居中的标注都适用。值得一提的是,TextPainter 必须先 layout() 才能拿到 widthheight,这是新手最容易忽略的一步,漏掉就会取不到尺寸导致定位错误。

第三个让我印象深刻的是"单一状态驱动批量视觉变化"的设计。破解步骤列表里有一个 _currentStep,它本身只是一个整数,却控制了整个列表的高亮范围——每一步通过 i <= _currentStep 判断自己该亮还是该暗。用户点击任意一步,只需更新这一个状态,整个列表的视觉就会重新计算并刷新。这种"一个状态、多处响应"的模式,比为每一步单独维护一个"是否高亮"的布尔值要优雅得多,也更不容易出错。它再次印证了状态最小化的价值:能用一个数推导出来的视觉,就不要拆成一堆冗余状态。把复盘进度这种本质上是"线性推进"的逻辑,用一个游标式的整数来表达,是非常自然且高效的建模。

总结

这个中国象棋残局复盘页面完整呈现了 Flutter 在 HarmonyOS 7.0 上构建棋类教学型页面的标准做法:用 CustomPaint 按比例划分画布自绘标准棋盘与棋子,用 TextPainter 绘制居中的汉字与楚河汉界,用单一的 _currentStep 状态驱动整个步骤列表的高亮联动。整个页面把"复杂图形自绘"与"线性进度状态"两件事处理得清晰而高效——前者依赖网格化的坐标思维让棋盘自适应任意尺寸,后者依赖状态最小化让一个游标控制批量视觉。这种范式对象棋、围棋、五子棋等各类需要自绘棋盘并复盘步骤的教学应用都有很强的复用价值。

从跨端落地的角度看,本页面是"纯 Dart、零适配"的典范。棋盘绘制、棋子渲染、残局列表、步骤序列全部使用 Flutter 内置的 Canvas API 与标准组件,不依赖任何平台原生能力,因此切换到 HarmonyOS 提供的定制版 SDK 后即可在鸿蒙设备上直接复用,与 Android、iOS 共享同一套代码。所有自绘指令都通过 Framework 层下沉到 Engine 层,由 Skia 借助鸿蒙系统的渲染上下文完成 GPU 光栅化,在鸿蒙平台上以原生性能运行。对于棋类这种高度依赖精确自绘的应用品类而言,Flutter 的 CustomPaint 配合 Skia 渲染,恰好提供了一条既高性能又完全跨端的实现路径。如果进一步把"标准棋盘"抽象为一个接收棋子布局数据的可复用 CustomPainter 组件,就能在残局、对弈、打谱等多个场景反复使用,把一次性的绘制投入沉淀为长期的技术资产,这正是 Flutter × HarmonyOS 组合在棋类垂直领域最值得深耕的工程价值。

在这里插入图片描述

Logo

一站式 AI 云服务平台

更多推荐