1. 为什么要在 OpenHarmony 上做步进器组件1.1 步进器在业务中的位置步进器在移动端应用里出现的频率比你想象中高得多。用户注册、下单结算、多步表单、权限引导、教程导览凡是需要把流程拆成“一步一步来”的场景步进器都是默认选择。它带来的收益很直接把长流程拆成短步骤用户每一步只需要面对少量信息认知负担降下来了转化率自然不一样。我之前在某跨平台系统里做过一个账号激活的流程原来的设计是把所有字段堆在一个页面里用户填到一半就烦了。后来改成三步步进器每步只处理三四个字段完成率大概提升了三成。这个提升不是玄学是交互结构本身带来的。到了 OpenHarmony 平台步进器的必要性没有变但实现方式有点绕。OpenHarmony 自己的 UI 框架提供了类似能力的组件但如果你团队的技术栈是 Flutter那就需要把 Flutter 生态的步进器能力搬过来。这也是 Flutter for OpenHarmony 这类适配项目的核心价值所在一次开发多端复用而不是为了一个新平台再把 UI 层重写一遍。1.2 Flutter 与 OpenHarmony 的适配现状Flutter for OpenHarmony 不是 Google 官方的分支而是社区和生态伙伴一起推动的适配项目。它做了一个非常关键的事情把 Flutter 引擎的渲染层、平台通道、插件机制映射到 OpenHarmony 的能力上。也就是说你在 Flutter 里写的 Widget最终不是跑在 Android 的 Skia/Impeller 上而是跑在 OpenHarmony 的图形栈上。这件事听起来简单实际要处理的细节多到让人头疼。比如字体渲染、触摸事件的坐标系映射、文本输入的 IME 适配、不同屏幕形态的布局约束等等。但好在 Flutter 的 Widget 层和渲染层之间有清晰的抽象边界所以大部分 UI 组件在 OpenHarmony 上是可以直接工作的只是部分系统级能力需要走 Platform Channel 去调用。步进器组件恰好属于“可以直接工作”的那一类。它不依赖太多平台底层能力主要就是布局容器、图标、文本、按钮再加一点动画。对 Flutter 开发者来说这意味着你在 OpenHarmony 上写步进器的代码和写普通 Flutter 代码几乎一样。我下面的实操过程也是基于这个前提来展开的。2. 环境准备与工程搭建2.1 工具链安装要在 OpenHarmony 上跑 Flutter常规的 Flutter SDK 是不行的你需要专门支持 OpenHarmony 的分支或适配版本。我自己用的是一套社区维护的 Flutter SDK配合 DevEco Studio 来做 OpenHarmony 工程的构建和打包。具体来说几个核心工具是这样的Flutter SDK使用支持 ohos 平台的版本装好之后在终端里执行flutter doctor能看到一个OHOS的条目。DevEco Studio这是 OpenHarmony 应用开发的官方 IDE用来创建鸿蒙工程、配置签名、打 HAP 包。OpenHarmony SDK在 DevEco Studio 里配置包含 API 接口、编译工具链、模拟器和真机调试所需的镜像文件。这里有个容易踩的坑版本匹配。Flutter SDK 的 ohos 适配版本、OpenHarmony SDK 的 API Level、DevEco Studio 的版本这三者必须对齐。比如 API Level 10 的工程配一个只支持 API Level 9 的 Flutter 适配版跑起来会出现各种诡异问题而且报错信息不一定能看出来是版本不匹配。装完工具后我建议先跑一遍官方提供的示例工程确认开发闭环是通的。我第一次搭环境的时候直接开一个自己的工程就开写结果编译报错根本分不清是环境问题还是代码问题。先跑通官方示例后面排错会轻松很多。2.2 创建第一个 Flutter for OpenHarmony 工程创建工程的方式和普通 Flutter 一样用flutter create命令但需要指定模板类型。工程创建好之后目录结构会多出ohos目录这个目录下就是 OpenHarmony 的壳工程包括entry模块和一些配置文件。你要关心的几个文件ohos/entry/src/main/module.json5入口模块配置包括应用包名、能力声明。ohos/entry/src/main/etsOpenHarmony 的 UI 层代码Flutter 引擎在这里启动。ohos/entry/src/main/resources/base资源文件比如字符串、颜色、图标。关键的一步是在首页 Ability 里把 Flutter 引擎挂载上去。默认模板会有一个FlutterAbility或者类似的入口类它会加载 Flutter 引擎然后渲染你的第一帧 Widget。这个流程是自动的但如果你的应用有多个页面需要自己管理路由和引擎实例。我通常在flutter create之后先不写任何业务代码直接修改默认的main.dart里内容为最简单的文本展示然后编译到模拟器上看效果。这一步能快速确认你的 Flutter 工程在 OpenHarmony 上能跑通避免后面写了大量代码才发现基础链路是断的。注意OpenHarmony 的模拟器比 Android 模拟器重很多启动也慢不少。如果你预算允许建议直接上真机调试体验会好很多。真机调试需要在 DevEco Studio 里配置开发者模式和证书签名这步别漏了不然装不上应用。3. 步进器组件设计拆解3.1 数据模型与步骤定义写代码之前先把步进器的数据模型想清楚。一个步进器由若干个步骤组成每个步骤至少要有标题、内容区域、状态。状态可以分成了四种未开始、进行中、已完成、不可用。这听起来简单但实际业务中往往还需要为每个步骤附加数据比如表单字段的值、校验结果、是否被跳过等等。我在实现时定义了一个结构体enum StepStatus { pending, editing, complete, disabled } class StepItem { final String title; final String? subtitle; final Widget? content; final StepStatus status; final bool enabled; const StepItem({ required this.title, this.subtitle, this.content, this.status StepStatus.pending, this.enabled true, }); }这个数据模型的好处是足够简洁又能表达大多数需求场景。content字段存放这个步骤要展示的表单或说明区域status和enabled分开控制状态避免把交互状态和业务状态混在一起。实际开发中我见过不少人把步骤状态直接存在各自的页面里步进器只是展示当前索引。这种做法在简单场景没问题一旦遇到动态跳转、回退修改之后重新计算后续步骤状态的场景就会变得非常痛苦。所以我还是建议在步进器层面统一管理状态至少维护一个“当前步骤索引”和一个“每个步骤的完成情况”。3.2 布局结构与交互反馈布局结构上步进器通常有两种形态水平步进器和垂直步进器。水平步进器的步骤图标排成一行适合步骤少、每个步骤内容少的情况。垂直步进器的步骤从上到下排列内容区直接嵌在步骤里适合步骤内容多、需要展开显示的场景。Flutter 里的Stepper组件默认支持这两种形态通过type参数切换。水平形态下步骤内容只展示当前步骤垂直形态下步骤和内容会同时呈现用户可以随时看到所有步骤的状态。交互反馈是步进器的灵魂。用户点击“下一步”时你至少要处理三件事校验当前步骤的数据是否合法。更新当前步骤状态为已完成。把步骤索引推进到下一步并把下一步状态更新为进行中。反过来点击“上一步”时你要允许用户回到之前的步骤修改而且修改之后某些后续步骤的缓存数据可能就失效了。这里需要有一个策略是保留后续步骤的填写内容还是全部重置。我在实际项目中采用的是“保留已填内容但标记为待重新校验”这样用户不会因为改了前面一个字段后面填的都白填了。4. 核心代码实现与关键细节4.1 Stepper 基础骨架先把最基本的Stepper跑起来。在 Flutter 里初始化一个步进器非常简单Stepper( currentStep: _currentStep, onStepContinue: _handleContinue, onStepCancel: _handleCancel, onStepTapped: (step) _handleTapped(step), steps: _buildSteps(), controlsBuilder: (context, details) { return Row( children: [ FilledButton( onPressed: details.onStepContinue, child: const Text(下一步), ), if (_currentStep 0) TextButton( onPressed: details.onStepCancel, child: const Text(上一步), ), ], ); }, )几个关键点我展开说一下。currentStep控制当前步骤的索引它是整个组件的核心状态。onStepContinue和onStepCancel是点击底部按钮的回调你自己决定逻辑。controlsBuilder允许你自定义底部按钮区域默认的英文文案“Continue”“Cancel”直接用于中文项目会很突兀所以我一般都会重写这个构建器。这里有个容易忽略的点onStepTapped。在水平模式下默认是不允许用户直接点击非当前步骤图标的在垂直模式下onStepTapped允许用户点击任意步骤的标题栏跳转到那一步。但这里的安全逻辑要你自己控制——我建议已经完成的步骤才能点击跳转未完成的步骤不能直接跳过去否则用户可能绕过表单校验。steps的构建方式如下ListStep _buildSteps() { return [ for (final item in _items) Step( title: Text(item.title), subtitle: item.subtitle ! null ? Text(item.subtitle!) : null, content: item.content ?? const SizedBox.shrink(), isActive: item.status StepStatus.editing, state: _mapToStepState(item.status), ), ]; } StepState _mapToStepState(StepStatus status) { switch (status) { case StepStatus.pending: return StepState.indexed; case StepStatus.editing: return StepState.editing; case StepStatus.complete: return StepState.complete; case StepStatus.disabled: return StepState.disabled; } }StepState决定图标的展现形式indexed是灰色数字completed是绿色对勾editing是高亮的数字disabled是灰化的数字。这几个状态映射关系理顺了UI 就成功了一大半。4.2 状态管理与步骤校验步进器里的状态管理我推荐先从最简单的setState入手不要一上来就引入复杂的状态管理库。因为步进器的状态更新频率并不高都是用户点击按钮时触发setState完全够用。核心逻辑是维护一个_currentStep和维护每个步骤的完成状态列表class _StepperDemoState extends StateStepperDemo { int _currentStep 0; final Mapint, StepStatus _statusMap {}; void _handleContinue() { // 校验当前步骤 if (!_validateCurrentStep()) { return; } if (_currentStep _items.length - 1) { _handleFinish(); return; } setState(() { _statusMap[_currentStep] StepStatus.complete; _currentStep; _statusMap[_currentStep] StepStatus.editing; }); } bool _validateCurrentStep() { final content _items[_currentStep].content; if (content is FormStepContent) { return content.validate(); } return true; } }这里每个步骤的内容我封装成了一个FormStepContent的 Widget它内部维护了自己的表单状态并提供validate()方法。这样做的好处是步进器只管步骤切换具体业务校验由步骤自己负责。回到上一步的逻辑也很直接void _handleCancel() { setState(() { if (_currentStep 0) { _currentStep--; _statusMap[_currentStep] StepStatus.editing; } }); }但你要注意一个细节上一步点击之后当前步骤的填写内容不能简单丢掉。我在前面提到过要保留已填内容。做到这一点需要步骤内容的 Widget 在setState重建时不丢失内部状态。最简单的方法是让每个步骤的内容持有自己的数据对象这个数据对象放在StepItem里而不是放在步骤 Widget 内部。比如一个手机号输入框它的控制器和当前输入值应该存在一个相册级别的数据模型里这样即使步进器重建输入值也不会丢。4.3 样式定制与主题适配Flutter 的Stepper组件自带一套 Material 样式的视觉表现但到了 OpenHarmony 上这套默认样式不一定符合你的产品设计。我在实际项目中几乎总会做定制。常见的定制点有三个图标颜色、连线颜色、文字样式。图标样式的调整代码如下Theme( data: Theme.of(context).copyWith( colorScheme: ColorScheme.fromSeed( seedColor: _brandColor, primary: _brandColor, onPrimary: Colors.white, ), // 通过 visualDensity 和 shape 调整图标形状 stepperTheme: StepperThemeData( color: _brandColor, connectorColor: _lineColor, connectorThickness: 2, ), ), child: Stepper(...), )不同 Flutter 版本里Stepper的主题配置方式不太一样老版本里直接改Stepper的color和connectorColor源码字段新版本里挪到了StepperThemeData。如果你发现属性名对不上先检查 Flutter 版本。还有一个容易出问题的点Stepper默认使用等宽图标布局在水平模式下如果步骤文字过长会把图标间距挤得很小。解决办法是用labelWidget自定义标题栏或者把步骤数控制在 4 步以内。在 OpenHarmony 上字体渲染的差异也可能导致标题截断。我在测试时发现同样的Text组件在 Android 上宽度刚好在 OpenHarmony 上却换行了。原因是默认字体族的字形宽度不完全相同。解决方法是给标题设置较小的字号或者用FittedBox做自适应缩放。5. 常见问题与调试锦囊5.1 组件不渲染或触摸失效这是 Flutter for OpenHarmony 上问得最多的一类问题。你在标准 Flutter 里跑得好好的组件到了 OpenHarmony 上要么白屏要么点击没反应。先说白屏问题。步进器组件如果渲染不出来先用最简单的TextWidget 验证整个 Flutter 引擎是否正常工作。如果 Text 能显示而 Stepper 不能大概率是主题或者字体的问题。我在某个模拟器上试过默认字体没正确加载导致Stepper内部的Text全部宽度为零整个组件就没有高度了。解决办法是在 MaterialApp 的theme里显式指定fontFamily。再说触摸失效。这通常是事件通道的问题。检查两个地方你的GestureDetector或者按钮是否被其他组件遮挡。OpenHarmony 上的触摸事件坐标系是否映射正确。如果只有部分区域可以点击而且点击位置和视觉位置有偏移那基本可以确定是坐标映射问题。这会出现在较早期的适配版本上更新 Flutter SDK 的 ohos 分支到最新版本一般能解决。注意在模拟器上调试触摸事件时不要完全相信鼠标点击的模拟效果。触摸事件和鼠标事件在某些版本上走的是不同通道真机上可能是正常的。5.2 状态丢失与性能痕迹状态丢失这个问题在步进器这种多状态组件上表现很明显。具体来说就是用户填好了第一步切到第二步再切回来第一步填的数据没了。造成这个问题的原因很可能是你的步骤内容 Widget 在setState时被重建且内部控制器没有保留。解决思路前面已经说过把表单数据提到步骤模型或者 State 层面而不是放在 Widget 本地。代码上可以用一个AutomaticKeepAliveClientMixin来保持步骤内容的状态但我不推荐过度依赖它因为步进器本身就有多个子页面每个子页面都保活会导致内存压力变大。更稳妥的做法是数据驱动每个步骤一个数据对象页面只是响应数据对象的渲染。关于性能如果步进器包含复杂的表单组件切换时会有卡顿感。OpenHarmony 上 Flutter 的渲染链路目前还没有 vSync 同步得完美偶发掉帧是正常的。优化的思路是减少步骤切换时的 rebuild 范围用RepaintBoundary隔离每个步骤的绘制边界避免切换时触发整个页面的重绘。我在项目里用的是RepaintBoundary( key: PageStorageKey(step_$_currentStep), child: _buildStepContent(), )这样可以避免无谓的绘制开销而且PageStorageKey还能帮你把滑动位置等 UI 状态保留下来。5.3 快速排查速查表我把一些常见的情况整理成一个速查表方便你对照排查。现象可能原因排查方向图标不显示主题字体缺失检查字体配置换用系统内置字体测试步骤内容空白Controller 未初始化确认initState里是否过早访问依赖项点击无反馈按钮被遮挡或事件映射异常用 Flutter 的 Inspector 查看组件层级文字溢出字体宽度差异缩短文案或启用FittedBox打包后闪退签名问题检查 DevEco Studio 的签名配置动画卡顿缺RepaintBoundary给步骤内容加隔离层回退后数据丢失状态存错位置数据上移到 State 或模型层这些坑我基本都踩过一遍尤其数据丢失的问题排查了一整个下午才发现是状态层级放错了。Flutter 的组件树重建模型和传统原生 UI 不太一样你需要在“组件的局部状态”和“业务的数据状态”之间划一条明确的线。凡是用户填的数据一律放在业务状态里凡是为了方便渲染而产生的状态才放在 Widget 本地。6. 从步进器到工程化复用的进阶思路如果你只是在单个页面里用一个步进器上面的内容已经够用了。但如果你和我一样需要在多个业务流程中复用步进器那有几个进阶思路值得考虑。第一把步进器封装成通用的FlowScaffold组件。它负责整体的步骤切换逻辑、底部按钮区域、进度指示、动画过渡业务方只需要传入步骤列表和校验函数。这样一来不同业务有不同步骤但步进器的交互一致性得到保证。第二步进器的状态要能被持久化。当应用被切到后台再接回来时OpenHarmony 可能会回收资源如果没有保存当前步骤索引和表单数据用户会丢失操作进度。处理思路是监听应用生命周期在onPause时缓存状态在onResume时恢复。这比在步进器内部做缓存要可靠。第三深链接路由。如果一个支付流程有四步用户暂停在第二步后被系统杀掉下次打开应用需要考虑是否需要恢复到第二步。这个属于产品逻辑上的决策但步进器的数据模型设计好了这类恢复功能做起来就会很顺。我个人的体会是组件开发这件事写代码只占三成时间剩下七成都在跟状态、生命周期和边界条件较劲。步进器在 Flutter 生态里算是一个中等复杂度的组件它没有列表页面那么琐碎也没有播放器那样深的技术栈但它恰好是一个把所有 UI 基础能力连接起来的好抓手。在 OpenHarmony 上把它跑通你基本能掌握 Flutter 适配层的绝大多数基本操作。最后分享一个小技巧在 OpenHarmony 上测试步进器的时候记得把字体大小调成超大或者超小各测一遍。因为国产设备的字体渲染参数差异非常大很多设备默认开启了字体加粗步进器的图标和文字对不齐的问题基本都是这么暴露出来的。调整字体大小之后布局没有错乱组件才算真正完成了适配。
阅读完成 · 觉得有帮助?