基于HarmonyOS 7.0 跨端开发的麻将算番计算页面实战
基于HarmonyOS 7.0 跨端开发的麻将算番计算页面实战
前言
中国各地麻将规则差异巨大,番种繁多,算番历来是新手最头疼的环节。一个好的麻将算番工具,要能切换不同地区规则、展示当前手牌、并清晰地讲解每个番种的组成与番数。本文以一个真实的麻将算番页面(入口类 ProfilePage)为样本,深入剖析它如何在 Flutter × HarmonyOS 7.0 架构下,用筹码式规则标签、象牙白麻将牌面与记分册样式的番种列表,把"各地麻将番种计算"的体验完整落地。这是一个把"规则状态切换"与"番数驱动着色"结合得很典型的页面,通过拆解它,我们能透彻理解 Flutter 的横向列表选择、Unicode 字符渲染、以及多档阈值映射颜色等实战手法在鸿蒙平台上的应用。
背景
麻将算番工具的核心诉求是"跨规则的番种查询与计算":玩家先选择广东、国标、四川、日本、台湾等不同地区规则,再查看当前 14 张手牌组成了哪些番种、合计多少番,并能逐个了解清一色、碰碰胡、混一色、一条龙、七对、十三幺等番种的组成条件与番数。本页面在视觉上采用麻将馆风格,墨绿桌面主色(0xFF1B5E20)配象牙白牌面(0xFFFFFDE7)与红中点缀。结构上从上到下依次是:标题栏(带"总计 36 番"统计)、横向滚动的筹码式规则选择标签(五种规则各用不同颜色)、墨绿底的手牌展示区(14 张麻将牌 + 当前番种结果),以及记分册样式的番种详解列表。其中麻将牌直接用 Unicode 麻将字符(🀇🀈🀉…)渲染,番种则按番数高低映射为四档颜色,是数据驱动着色的好例子。
Flutter × Harmony7.0 跨端开发介绍
在 HarmonyOS 7.0 上运行本页面,前提是使用 HarmonyOS 维护的定制版 Flutter SDK——鸿蒙对 Flutter 的支持由 HarmonyOS 跨平台 SIG 通过 fork 扩展 Flutter SDK 实现,官方 SDK 并不包含鸿蒙平台。
本页面有一个有趣的跨端要点:麻将牌面用的是 Unicode 麻将字符(如 🀇),它们的显示依赖系统字体对这些字符的支持。在不同平台上,同一个 Unicode 字符可能因系统字体不同而显示出略有差异的字形——这正是知识库提到的字体适配问题的一种体现。如果要保证麻将牌在鸿蒙、Android、iOS 上呈现完全一致的字形,更稳妥的做法是打包一套专门的麻将字体文件并在配置中声明,从而摆脱对系统字体的依赖。本示例直接使用系统字体渲染,简洁但样式受平台影响。
除此之外,页面用到的 ListView、Wrap、Container、GestureDetector 等都是纯 Dart 的 Framework 层组件,无原生依赖,可直接跨端复用。渲染依旧走"Dart 描述 → Engine 收集 → Skia 光栅化 → 鸿蒙 ArkUI RenderingContext 输出"的链路,文本字形的测绘与排版由 Skia 完成。经 AOT 编译后,规则切换的响应、手牌的 Wrap 换行布局都能在鸿蒙设备上保持原生级流畅。
开发核心代码
第一部分:横向滚动的筹码式规则选择器。 用 ListView.separated 实现横向规则标签,每种规则配一个专属颜色,选中态高亮:
SizedBox(
height: 42,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _rules.length,
separatorBuilder: (_, __) => const SizedBox(width: 6),
itemBuilder: (_, i) {
final selected = i == _selectedRule;
final colors = [Color(0xFFDC2626), Color(0xFFDAA520),
Color(0xFF2563EB), Color(0xFF8B5CF6), Color(0xFF10B981)];
return GestureDetector(
onTap: () => setState(() => _selectedRule = i),
child: Container(
decoration: BoxDecoration(
color: selected ? colors[i] : Colors.white,
border: Border.all(color: selected ? colors[i] : const Color(0xFFE5D5C0)),
),
child: Text(_rules[i],
style: TextStyle(color: selected ? Colors.white : const Color(0xFF6B7280))),
),
);
},
),
)
ListView.separated 是横向标签栏的理想选择——它支持水平滚动(规则多时可滑动)、自动按需构建子项、并用 separatorBuilder 统一管理间距。每种规则用 colors[i] 取专属颜色,选中时整块染色,营造出"筹码"般的视觉效果,选中状态完全由 _selectedRule 状态推导。
第二部分:Unicode 麻将牌的 Wrap 布局。 用 Wrap 把 14 张麻将牌自动换行排列,每张牌是一个象牙白圆角容器套 Unicode 字符:
Wrap(
spacing: 4, runSpacing: 4, alignment: WrapAlignment.center,
children: _tiles.map((t) => Container(
width: 44, height: 56,
decoration: BoxDecoration(
color: const Color(0xFFFFFDE7),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: const Color(0xFFD4C4B0)),
),
alignment: Alignment.center,
child: Text(t, style: const TextStyle(fontSize: 22)),
)).toList(),
)
14 张牌用 Wrap 自动换行,一行放不下就折到下一行并居中对齐,无需手动计算每行放几张。每张牌的象牙白底色、圆角、边框统一还原了麻将牌的质感,而牌面内容就是一个 Unicode 麻将字符,极其简洁。
第三部分:番数驱动的四档颜色映射。 番种列表用嵌套三元把番数映射为红/金/蓝/绿四档颜色,番数越高颜色越醒目:
..._fans.map((f) {
final fan = f['fan'] as int;
final color = fan >= 88 ? const Color(0xFFDC2626) // 红-役满级
: fan >= 24 ? const Color(0xFFDAA520) // 金-大番
: fan >= 12 ? const Color(0xFF2563EB) // 蓝-中番
: const Color(0xFF10B981); // 绿-小番
return Container(
decoration: BoxDecoration(
color: color.withValues(alpha: 0.04),
border: Border(left: BorderSide(color: color.withValues(alpha: 0.3), width: 3)),
),
child: Row(children: [
Container(child: Text('$fan')), // 番数徽标
Expanded(child: Text(f['name'] as String)),
]),
);
})
这里把番数这个连续量化为四个视觉档位:十三幺这类 88 番大牌用红色,清一色、七对等 24 番用金色,一条龙等 12 番用蓝色,碰碰胡等小番用绿色。番数徽标、左侧竖线、卡片底色全部从同一个 color 派生,让用户扫一眼就能感知番种的"分量"。
心得
做这个麻将算番页面,最大的收获是对"用颜色给数据分级"这一手法的再次确认。番种繁多,光看名字和番数,新手很难快速建立"哪些是大牌、哪些是小番"的直觉。我用嵌套三元把番数分成 88、24、12 及以下四档,分别对应红、金、蓝、绿,让颜色先于数字传递"分量感"——红色一出现,用户就知道这是役满级的大牌。这种把连续数值离散为视觉档位的做法,和此前在温度页、字体匹配度页里用过的思路一脉相承,已经成为我处理"带等级含义的数值"时的固定套路。关键在于阈值的选择要符合领域常识,颜色的选择要符合用户直觉,二者结合才能真正提升信息的可读性。
第二个体会来自横向规则选择器的实现。五种地区规则需要横向排列且可能需要滚动,我选用了 ListView.separated 而非 Row + SingleChildScrollView。原因是 ListView 天生支持滚动和按需构建,separatorBuilder 又能优雅地统一分隔间距,比手动在 Row 里插 SizedBox 干净得多。给每种规则分配专属颜色,则是为了强化"不同规则是不同体系"的认知——这是个小细节,却能让界面更有信息层次。选中态用整块染色模拟筹码质感,也呼应了麻将馆的主题氛围。
第三个值得记录的是关于 Unicode 字符渲染的跨端思考。麻将牌我直接用了 Unicode 麻将字符,写起来极其简洁,一个 Text 就搞定一张牌。但在写的过程中我也清醒地意识到,这种做法把字形的呈现权交给了系统字体——在鸿蒙、Android、iOS 上,同一个麻将 Unicode 可能长得不太一样,甚至某些老旧系统可能根本不支持显示。这正是知识库强调字体适配的现实场景。对一个追求视觉一致性的正式产品来说,更稳妥的方案是打包专用的麻将字体,确保所有平台上牌面完全一致。理解这个权衡,让我在"简洁"与"一致"之间能做出有依据的取舍,而不是盲目地用了 Unicode 就完事。
总结
这个麻将算番页面完整呈现了 Flutter 在 HarmonyOS 7.0 上构建规则切换型工具页面的标准做法:用 ListView.separated 实现可滚动的横向规则选择器,用 Wrap 实现手牌的自动换行布局,用嵌套三元把番数映射为多档颜色以增强可读性。整个页面以 _selectedRule 这一可变状态驱动规则切换,以 const 列表承载牌面与番种数据,状态与数据分工清晰,框架自动接管了选中态的重绘与列表的换行排版。这种范式对各类"多规则、多档位"的查询工具都有很强的复用价值。
从跨端落地的角度看,本页面绝大部分是纯 Dart 实现、可零适配复用的:规则标签、手牌布局、番种列表全部使用 Flutter 内置组件,切换到 HarmonyOS 提供的定制版 SDK 后即可在鸿蒙设备上直接运行,与 Android、iOS 共享同一套逻辑。唯一需要在跨端时特别留意的是 Unicode 麻将字符的字形一致性——若产品对视觉精度要求高,应打包专用字体并在配置中声明,确保字体文件被纳入鸿蒙构建产物,从而摆脱对各平台系统字体的依赖。把握住"业务逻辑纯 Dart 共享、字体等平台资源妥善打包"这一分工,麻将算番这类文化属性浓厚的工具应用,就能在鸿蒙生态里既保持开发效率,又呈现出地道而一致的视觉体验。这正是 Flutter × HarmonyOS 组合在垂直工具领域值得长期投入的工程价值。

更多推荐





所有评论(0)