突破跨端兼容壁垒!Flutter slider_gradient 插件鸿蒙适配落地,对齐三端通道契约,完成语法适配与真机调试,保留全部原生交互能力,适配鸿蒙最新 Flutter 引擎版本
开发工具: 华为云码道
本文配套仓库: oh-flutter/slider_gradient
鸿蒙适配后仓库:https://atomgit.com/oh-flutter/slider_gradient
slider_gradient 是一个带渐变背景的 Flutter 滑块组件:轨道支持渐变 / 纯色两种背景,thumb 颜色沿 colors 渐变数组按位置插值,并提供单值与范围选择(双 thumb)两种模式。本文以 slider_gradient 0.2.0 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 真机运行。
插件的根目录 OHOS 模块(ohos/)只承载一个约 77 行的通道桩(SliderGradientPlugin.ets),复刻 Android / iOS 端的模板契约 slider_gradient / getPlatformVersion;组件本体 lib/slider_gradient.dart 没有任何 MethodChannel 调用,所有交互(GestureDetector、AnimationController、自定义布局 _ModalSliderLayout)都在 Flutter 框架内完成,因此只要 Flutter OH 引擎能跑起来,组件就自然可用。本文以 04a3751ec0b8037b558d98c39c5a2beb26158abc 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 真机运行。

适配以提交 b9d29488a61dad56ce3a636a517c782270cc605a 为参考,落地在 main 分支,并打 TAG 0.2.1。

我先读取这 6 张截图,确认每张内容后再统一整理成表格。
6 张截图均为同一个 SliderGradient 示例页(OpenHarmony TLR-AL00,6.1.0.135(SP9C00E120R3P5))在不同拖动状态下的快照。整理成两张表:
表 1 · 页面模块与配置说明
| 模块 | 关键配置 | 预期表现 |
|---|---|---|
| 平台信息 | MethodChannel("slider_gradient").invokeMethod("getPlatformVersion") | 展示运行平台版本、通道/方法名,提供「重新查询」按钮 |
| ① 纯色背景 | isGradientBg=false | 轨道为主色/辅色双色填充,下方展示当前值、百分比、Thumb 颜色 |
| ② 渐变背景(悬浮 label) | isGradientBg=true(默认)、isShowLabel=true | 蓝→青→绿→黄渐变轨道,thumb 上方悬浮 label 实时显示数值 |
| ③ 范围选择(双 thumb) | isRange=true | 红→绿渐变轨道,双 thumb;拖动时自动选中距离更近的 thumb,展示左/右端值、跨度、左右 Thumb 颜色 |
| ④ 自定义样式 | thumb 16×32、圆角 8;轨道高度 18;三色渐变 | 橙→红→紫三色渐变轨道,自定义圆角 thumb,悬浮 label 显示数值 |
| 操作事件(最近 8 条) | — | 滚动记录滑块结果、拖动开始、通道调用等事件,最新在最前 |
表 2 · 各截图实测状态快照
| 截图时刻 | ① 纯色背景(当前值 / 百分比 / Thumb 颜色) | ② 渐变背景(当前值 / 百分比 / label / Thumb 颜色) | ③ 范围选择(左 / 右 / 跨度 / 左色 / 右色) | ④ 自定义样式(当前值 / 百分比 / label) | 操作事件记录 |
|---|---|---|---|---|---|
| 22:02 | 30 / 30.0% / - | 50.00 / 50.0% / 50.0 / - | 未滚到 | 未滚到 | — |
| 22:03(a) | 75 / 75.0% / #FF2196F3 | 50.00 / 50.0% / 50.0 / - | 未滚到 | 未滚到 | — |
| 22:03(b) | 未显示 | 50.00 / 50.0% / 50.0 / - | 20 / 60 / 40 / - / - | 70.00 / - / 70.0 | — |
| 22:04 | 未显示 | 未显示 | 20 / 60 / 40 / - / - | 70.00 / 70.0% / 70.0 | 1. 纯色滑块:结果 75(75.0%);2. 纯色滑块:拖动开始;3. 通道调用成功:OpenHarmony TLR-AL00 6.1.0.135(SP9C00E120R3P5) |
| 22:05 | 未显示 | 53.00 / 53.0% / 53.0 / #FF5BE2BB | 18 / 83 / 65 / #FFDF6352 / #FF6A9F50 | 56.00 / - / 56.0 | — |
| 22:06 | 40 / 40.0% / #FF2196F3 | 53.00 / 53.0% / 53.0 / #FF5BE2BB | 35 / 83 / 48 / #FFC07351 / - | 未滚到 | — |
走查结论:四个示例卡片渲染正常;拖动过程中当前值、百分比、悬浮 label、Thumb 颜色(随轨道位置自动取色)均实时联动更新;范围滑块双 thumb 可独立拖动、跨度随动;MethodChannel 通道调用成功返回平台版本;操作事件按顺序落盘,符合预期。
以下是操作的视屏,可以参考一下:
一、插件简介与适配目标
slider_gradient 把"渐变滑块"这种常见交互封装成 SliderGradient 组件。业务只需要声明一个 colors 数组和 onChange / onChangeBegin / onChangeEnd 三个回调,就能拿到带渐变背景的滑块,并且 thumb 颜色随 thumb 位置在 colors 之间用 Color.lerp 插值;进入范围模式后,两个 thumb 各自独立,且拖动时自动选中距离触点更近的 thumb。
SliderGradient(
value: _value,
min: 0,
max: 100,
colors: const [Color(0xFF4A90D9), Color(0xFF50E3C2)],
onChange: (SliderData data) {
debugPrint('value: ${data.value}');
},
onChangeEnd: (SliderData data) {
setState(() => _value = data.value!);
},
)
上游仅提供 Android / iOS 平台实现,OpenHarmony 平台无法直接使用。OHOS 适配目标有三个:
- 平台通道对齐:补全
ohos/根目录模块,新增SliderGradientPlugin.ets(约 77 行)注册与 Android / iOS 完全一致的通道slider_gradient和方法getPlatformVersion,使组件与插件注册表契约保持一致; - Dart 层空安全迁移:上游为 pre-null-safety 写法(
Key key、@required、非空字段未初始化),而 OHOS 版 Flutter 3.44 自带 Dart 3.12,旧语法编译期直接报错;本次适配对lib/slider_gradient.dart做了最小化空安全迁移,公开 API 的名称、语义与默认值保持不变; - example 在真机跑通 4 个场景:纯色背景、渐变背景(悬浮 label)、范围选择(双 thumb)、自定义样式,并演示如何调用
getPlatformVersion获取OpenHarmony ${deviceInfo.displayVersion}。
二、环境准备
环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。
完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:
flutter --version
flutter doctor -v
hdc list targets


工程使用的工具链和 SDK 配置如下:
| 项目 | 版本或配置 | 用途 |
|---|---|---|
| Flutter OHOS SDK | 3.44.9+ohos-0.0.1-canary1 | Flutter 编译与 OHOS 平台工具链 |
| Flutter 分支 | oh-3.44.9-dev | CPF-Flutter 对应开发分支 |
| Dart SDK | 3.12.2 | Dart 语言与包管理环境 |
| HarmonyOS 开发套件 | 7.0.0(API 26) | 开发套件版本及对应的 API 级别 |
compileSdkVersion | 26.0.0 | 编译时使用的 SDK API |
targetSdkVersion | 26.0.0 | 应用面向的行为版本 |
compatibleSdkVersion | 5.1.0(18) | 当前工程声明的最低兼容版本 |
| 插件版本 | 0.2.0(未随 OHOS 适配 bump) | pubspec.yaml 中的包版本 |
| 实际 TAG | 0.2.1(git tag -l 显示) | 适配提交的 git TAG |
| 原生语言 | ArkTS | 根目录 ohos/ 插件模块(约 77 行通道桩) |
| 插件产物 | HAR | 被应用 entry 模块依赖 |
| 设备 | HUAWEI nova 14(TLR-AL00) | 真机验证 |
| 系统 ROM | OpenHarmony-6.1.1.120 | 屏幕显示的 deviceInfo.displayVersion 为 6.1.0.135(SP9C00E120R3P5) |
2.1 开发套件版本与工程中的 SDK 版本配置
7.0.0(API 26) 和 26.0.0 涉及 HarmonyOS 开发套件版本(API 版本)及其底座 OpenHarmony 的版本号体系。在本文工程中,相关版本的含义与配置方式如下:
7.0.0(API 26)表示 HarmonyOS 开发套件版本为7.0.0,对应 API 26;26.0.0是本文 HarmonyOS 应用工程中compileSdkVersion和targetSdkVersion的属性值;5.1.0(18)是本文工程中compatibleSdkVersion的属性值,声明最低兼容 API 18。
对应的 product 配置为:
{
"name": "default",
"compatibleSdkVersion": "5.1.0(18)",
"compileSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0",
"runtimeOS": "HarmonyOS"
}

这组配置使用 API 26 SDK 编译,并以 API 26 为目标版本,最低兼容 API 18。SliderGradient 组件本身不依赖具体设备 API,但通道桩在 onMethodCall 中读取 deviceInfo.displayVersion,要求 SDK 提供 @ohos.deviceInfo 模块。
三、从源码仓库开始准备适配工程
3.1 将上游源码同步到 AtomGit
适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。
在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。
slider_gradient 现可从 AtomGit 获取,远程为 git@atomgit.com:oh-flutter/slider_gradient.git。下面使用 AtomGit 地址拉取代码;需要提交修改时,使用自己有写权限的仓库或 Fork。
3.2 将代码拉取到宿主机
在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:
git clone https://github.com/dilireba521/slider_gradient
cd slider_gradient
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

git clone 会创建 slider_gradient/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yaml、lib/、example/ 和 ohos/。Git 仓库名是 slider_gradient,Dart 包名也是 slider_gradient。
需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:
git switch --detach 04a3751ec0b8037b558d98c39c5a2beb26158abc
适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。
路径提示:本文实际仓库位于
/Users/david/workspace/flutter/lib/slider_gradient(即~/workspace/flutter/lib/之下),不是~/workspace/flutter/slider_gradient。这是该 workspace 下多个 Flutter 插件共用一个父目录的常见路径陷阱,克隆或拉取时需要按实际位置进入。基线提交(不含 OHOS 改动)是04a3751“add range selection function”。

图 1:克隆 AtomGit 仓库 oh-flutter/slider_gradient,git log 显示 OHOS 适配提交 b9d2948 已打 TAG 0.2.1,pubspec.yaml 中 name: slider_gradient、version: 0.2.0。
3.3 在仓库根目录创建适配分支
接着在 slider_gradient/ 根目录创建分支。分支名统一使用 feat/ohos_库名称_版本号,库名称取 pubspec.yaml 的 name,版本号取此次适配的基线版本。本例为:
git switch -c feat/ohos_slider_gradient_0.2.0
git branch --show-current
如果该分支已存在,使用 git switch feat/ohos_slider_gradient_0.2.0 切换即可。基线提交(不含任何 OHOS 改动)是 04a3751,先 detach 验证可编译再回到 main 即可。

图 2:在基线 04a3751(“add range selection function”)上 detach 验证,回到 main 创建分支 feat/ohos_slider_gradient_0.2.0;TAG 命名规则为 git tag 0.2.0-ohos-1.0.0-beta.1,但实际 TAG 是 0.2.1(详见 9.3),git tag -l 列出 0.1.0 / 0.1.1 / 0.2.0 / 0.2.1。
3.4 自动补全 OHOS 适配结构
分支创建后,仍在同一个插件根目录执行结构补全。以下命令适用于尚无 ohos/ 目录的既有 Flutter 平台插件:
flutter create --template=plugin --platforms=ohos --project-name slider_gradient .
git status --short
git diff -- pubspec.yaml lib example
--template=plugin指定插件模板;--platforms=ohos指定需要补全的平台;--project-name slider_gradient使用 Dart 包名,避免把含连字符的仓库目录名作为包名;- 最后的
.表示在当前插件目录补全工程,不是另建一层slider_gradient/。
该命令生成 OHOS 平台脚手架,业务逻辑需要在 ArkTS 中实现。生成后通过 diff 检查 pubspec.yaml、lib/ 和 example/ 的变化,保留已有 API、其他平台注册项及依赖配置。不同 Flutter OH 版本生成的模板可能略有差异。
如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:
cd example
flutter create --platforms=ohos .
cd ..
配套仓库已经包含 ohos/ 和 example/ohos/,直接运行示例时可以跳过结构补全。新建插件则使用 flutter create --org com.nutpi --template=plugin --platforms=ohos slider_gradient;已有插件使用上面的 . 在当前目录补全。

图 3:在 slider_gradient/ 根目录执行 flutter create --template=plugin --platforms=ohos . 生成 ohos/,再 cd example && flutter create --platforms=ohos . 补全宿主工程 example/ohos/。
3.5 适配后的项目目录
适配后的关键目录如下:
slider_gradient/
├── lib/
│ └── slider_gradient.dart # 组件实现(纯 Dart,跨平台,已做空安全迁移)
├── android/ # Android 模板通道桩
├── ios/ # iOS 模板通道桩
├── ohos/
│ ├── index.ets
│ ├── oh-package.json5
│ └── src/main/
│ ├── ets/components/plugin/SliderGradientPlugin.ets # 77 行通道桩
│ └── module.json5
├── example/
│ ├── lib/main.dart # 4 场景演示 + 平台信息卡 + 操作事件日志
│ └── ohos/
│ ├── AppScope/app.json5 # bundleName: com.example.slider_gradient_example
│ ├── entry/
│ └── build-profile.json5 # ⚠ 含本机签名材料,见 9.4
├── docs/
│ ├── SliderGradient-ohos-adaptation-blog.md
│ └── evidence/ # 真机截图
├── README.OpenHarmony_CN.md
├── README.OpenHarmony.md
├── CHANGELOG.OpenHarmony.md
└── pubspec.yaml
项目根目录如下,其中包含 ohos/、example/ohos/、docs/evidence/ 以及 OpenHarmony 中英文说明和变更记录文件:

图 4:适配后的 slider_gradient 项目根目录,包含 lib/、ohos/(含 77 行 SliderGradientPlugin.ets)、example/ohos/、docs/evidence/ 和三份 OpenHarmony 文档。
| 文件 | 主要职责 |
|---|---|
lib/slider_gradient.dart | 组件实现(纯 Dart,已空安全迁移) |
ohos/index.ets | 导出 SliderGradientPlugin |
ohos/src/main/ets/components/plugin/SliderGradientPlugin.ets | 注册通道 slider_gradient,处理 getPlatformVersion |
ohos/oh-package.json5 | 声明 HAR 模块信息(name: slider_gradient,license: Apache-2.0,与上游 MIT 不一致,详见 9.5) |
插件 module.json5 | 声明 HAR 模块信息 |
示例 entry module.json5 | 声明宿主应用 Ability 与 ohos.permission.INTERNET(Flutter 模板默认) |
example/lib/main.dart | 4 场景演示 + 平台信息卡 + 操作事件日志 |
example/ohos/AppScope/app.json5 | bundleName com.example.slider_gradient_example(与示例工程一致,无残留) |
example/ohos/build-profile.json5 | products.default + modules.entry,含本机签名材料,需在提交前剥离(见 9.4) |
README.OpenHarmony_CN.md / README.OpenHarmony.md | 中英文安装说明、TAG 表、接口表、示例 |
CHANGELOG.OpenHarmony.md | OHOS 适配变更记录 |
docs/SliderGradient-ohos-adaptation-blog.md | 本仓库自带的适配技术笔记(223 行) |
docs/evidence/ | 3 张真机截图(首次启动 / 中段 / 底部) |
四、Dart 接口与通道分析
OHOS 实现需要遵循 Dart 层已有的方法、参数和返回值约定。先阅读 lib/slider_gradient.dart,确认组件本体与平台通道的关系;本组件是纯 Dart 实现——lib/slider_gradient.dart 中没有任何 MethodChannel / Platform / invokeMethod 调用,因此适配重点落在通道契约对齐与空安全迁移上。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。
本例的对应关系如下:
| Dart 入口或模型 | 通道协议 | OHOS 实现 | 应保持的行为 |
|---|---|---|---|
SliderGradient(组件本体) | — | 纯 Dart,GestureDetector + AnimationController + 自定义布局 | OHOS 上自然可用,不经通道 |
_MyHomePageState._queryPlatformVersion()(demo 中调用) | slider_gradient / getPlatformVersion | SliderGradientPlugin.ets 返回 OpenHarmony ${deviceInfo.displayVersion} | 模板契约,三端一致;返回值形如 OpenHarmony 6.1.0.135 |
SliderChangeCallback / SliderData | — | Dart 内部回调,不经过通道 | 单值模式回调 payload 含 value / color;范围模式回调 payload 含 values / colors |
原生端需要保持通道名与方法名一致。SliderGradientPlugin 实现 FlutterPlugin 和 MethodCallHandler,对未实现方法统一返回 notImplemented(),保证通道桩可被 Flutter 工具链识别并通过 GeneratedPluginRegistrant 注册。
4.1 跨端架构与调用时序
组件本体是纯 Dart 组件,OHOS 上不需要为滑块本身打开通道;通道仅承担 demo 中的 getPlatformVersion 调用。整体架构如下:
组件交互(拖动 thumb、点击轨道、Color.lerp 计算 thumb 颜色、AnimatedBuilder 过渡)全部在 Flutter 框架内完成;通道仅承载一次性的"平台版本字符串"查询。
4.1.1 一次 getPlatformVersion 调用的时序
注意组件本体走的是左侧虚线:拖动 thumb 不经过通道,OHOS 上只要 Flutter 引擎能跑起来就自然工作。
4.2 公开 API:组件属性与回调 payload
lib/slider_gradient.dart 中的 SliderGradient 是业务层唯一入口;SliderData 是 onChange / onChangeBegin / onChangeEnd 回调的 payload。
class SliderGradient extends StatefulWidget {
SliderGradient({
Key? key,
this.label,
this.thumbStyle = const ThumbStyle(),
this.labelStyle = const LabelStyle(),
this.sliderStyle = const SliderStyle(),
this.isGradientBg = true,
this.colors,
this.min = 0,
this.max = 100,
this.isShowLabel = false,
this.isRange = false,
this.values,
required this.value,
required this.onChange,
this.onChangeBegin,
this.onChangeEnd,
this.divisions,
}) : assert(min < max),
assert(isRange ? true : value >= min && value <= max),
assert(isRange
? values != null &&
values.length == 2 &&
values.first <= values.last
: true),
super(key: key);
}
class SliderData {
SliderData({this.color, this.value, this.values, this.colors});
double? value;
List<double>? values;
Color? color;
List<Color>? colors;
}
4.2.1 属性与样式默认值
| 名称 | 类型 | 默认 | 必填 | 备注 |
|---|---|---|---|---|
value | double | — | 是 | 单值模式当前数值;可被外部修改(initData 中会再次同步) |
min / max | double | 0 / 100 | 否 | assert(min < max) |
colors | List<Color>? | null | 否 | null 时主色取 Theme.of(context).primaryColor,辅色取白色 |
isGradientBg | bool | true | 否 | false 时轨道为主色 / 辅色双色填充 |
isRange | bool | false | 否 | true 时必须传入长度为 2 且递增的 values |
values | List<double>? | null | isRange=true 时必填 | 范围模式下的两个数值 |
isShowLabel | bool | false | 否 | true 时在 thumb 上方显示悬浮 label |
label | String? | null | 否 | label 显示内容;null 时显示当前数值 |
onChange | SliderChangeCallback | — | 是 | 拖动或点击轨道时触发 |
onChangeBegin / onChangeEnd | SliderChangeCallback? | null | 否 | 拖动开始 / 结束时触发 |
divisions | int? | null(自动按 max - min 算) | 否 | 滑块等分数 |
sliderStyle | SliderStyle | SliderStyle(height: 16, radius: 4) | 否 | 轨道高度与圆角 |
thumbStyle | ThumbStyle | ThumbStyle(width: 16, height: 32, radius: 4, borderColor: 0xffE6E6E6) | 否 | thumb 尺寸与边框颜色 |
labelStyle | LabelStyle | LabelStyle(color: 0xffffffff, size: 10) | 否 | label 字体颜色与字号 |
回调触发时机如下:
onChange:每次 thumb 位置变化(拖动或点击轨道)都会触发,可执行多次;onChangeBegin:用户开始拖动 thumb 时触发一次;onChangeEnd:用户结束拖动时触发一次,业务通常在此回调里setState更新_value;- 单值模式 payload 含
value(double?)与color(Color?,已按Color.lerp在colors数组中插值); - 范围模式 payload 含
values(List<double>?)与colors(List<Color>?),两个 thumb 各自独立。
4.2.2 组件内部:_ModalSliderLayout 与 thumb 颜色插值
_SliderGradientState 使用 SingleTickerProviderStateMixin 持有 AnimationController,自定义布局通过 _ModalSliderLayout extends SingleChildLayoutDelegate 完成,getPositionForChild 根据 progress(0.0 – 1.0)把 thumb 放到轨道对应位置:
Offset getPositionForChild(Size size, Size childSize) {
return Offset(size.width * progress - thumbWidth / 2, 0);
}
thumb 颜色在 _lerp 中按当前百分比在 colors 数组的相邻两色之间做线性插值:
Color _lerp(int len, double percent) {
// ...
return Color.lerp(widget.colors![_num - 1], widget.colors![_num], _percent);
}
代码中保留了一条针对华为机型的注释:labelTextHeight 必须给 TextPainter 显式传入 locale: Localizations.localeOf(context),否则华为设备上算出的 label 高度偏小(详见 9.6)。
4.3 空安全迁移的改动范围
上游 lib/slider_gradient.dart 是 pre-null-safety 写法,而 OHOS 版 Flutter 3.44 自带 Dart 3.12 强制 null-safety。本次迁移保留 API 名称、语义和默认值,仅做以下最小化改动:
Key key→Key? key;@required→required;- 字段类型改可空:
SliderChangeCallback?、double?、Color?、String?、List<Color>?、List<double>?、LabelStyle.fillColor; - 内部状态字段加
late声明(late AnimationController controller、late Color _beginColor、late Color _endColor),并在initState/initData中显式赋值; initData中通过局部变量final colors = widget.colors绕过实例字段不参与类型提升的限制;_doubleToInt删除 dead null check,直接int.parse(val.toString().split('.')[0]);pubspec.yaml中sdk: ">=2.7.0 <3.0.0"→sdk: ">=2.12.0 <4.0.0";flutter: ">=1.20.0"保持不变。
4.4 Dart 通道协议分析
4.4.1 通道名称三端完全一致
static const MethodChannel _channel = MethodChannel('slider_gradient');
Dart demo 与 ArkTS 插件约定的通道名是 slider_gradient,方法名是 getPlatformVersion。任何一端拼写不一致都会出现“方法未实现”或“收不到返回值”等问题。
4.4.2 通道调用与错误处理
Future<void> _queryPlatformVersion() async {
try {
final Object? result =
await _channel.invokeMethod('getPlatformVersion');
if (!mounted) return;
setState(() => _platformVersion = result?.toString() ?? '(空)');
_log('通道调用成功:$_platformVersion');
} catch (e) {
if (!mounted) return;
setState(() => _platformVersion = '调用失败: $e');
_log('通道调用失败: $e');
}
}
invokeMethod 返回 Future<Object?>。demo 把它 toString() 后塞进 _platformVersion,并写入操作事件日志;catch 兜底 MissingPluginException / PlatformException。OHOS 真机上正常路径返回形如 OpenHarmony 6.1.0.135(SP9C00E120R3P5)(deviceInfo.displayVersion 包含 ROM build 号)。
五、补全 OHOS 原生实现与工程配置
5.1 在 SliderGradientPlugin.ets 中实现通道桩
SliderGradient 是纯 Dart 组件,所有交互都在 Flutter 框架内完成,因此 OHOS 原生侧只需要一个通道桩:在 pubspec.yaml 声明 ohos: pluginClass: SliderGradientPlugin,并在根目录 ohos/src/main/ets/components/plugin/SliderGradientPlugin.ets 中注册与 Android / iOS 完全一致的通道契约。核心实现如下:
import {
FlutterPlugin,
FlutterPluginBinding,
MethodCall,
MethodCallHandler,
MethodChannel,
MethodResult,
} from '@ohos/flutter_ohos';
import deviceInfo from '@ohos.deviceInfo';
import hilog from '@ohos.hilog';
/**
* SliderGradientPlugin
*
* OpenHarmony implementation of the `slider_gradient` plugin channel.
*
* Channel contract (kept identical to the Android/iOS implementations):
* channel name: "slider_gradient"
* methods:
* - "getPlatformVersion" -> String, e.g. "OpenHarmony 6.1.0.135"
*/
export default class SliderGradientPlugin implements FlutterPlugin, MethodCallHandler {
private static readonly TAG: string = 'SliderGradientPlugin';
private static readonly DOMAIN: number = 0xFF00;
private channel: MethodChannel | null = null;
getUniqueClassName(): string {
return 'SliderGradientPlugin';
}
onAttachedToEngine(binding: FlutterPluginBinding): void {
try {
this.channel = new MethodChannel(binding.getBinaryMessenger(), 'slider_gradient');
this.channel.setMethodCallHandler(this);
hilog.info(SliderGradientPlugin.DOMAIN, SliderGradientPlugin.TAG, 'onAttachedToEngine');
} catch (err) {
hilog.error(SliderGradientPlugin.DOMAIN, SliderGradientPlugin.TAG,
`onAttachedToEngine failed: ${JSON.stringify(err)}`);
}
}
onDetachedFromEngine(binding: FlutterPluginBinding): void {
try {
if (this.channel != null) {
this.channel.setMethodCallHandler(null);
}
this.channel = null;
hilog.info(SliderGradientPlugin.DOMAIN, SliderGradientPlugin.TAG, 'onDetachedFromEngine');
} catch (err) {
hilog.error(SliderGradientPlugin.DOMAIN, SliderGradientPlugin.TAG,
`onDetachedFromEngine failed: ${JSON.stringify(err)}`);
}
}
onMethodCall(call: MethodCall, result: MethodResult): void {
try {
if (call.method == 'getPlatformVersion') {
const version = `OpenHarmony ${deviceInfo.displayVersion}`;
hilog.info(SliderGradientPlugin.DOMAIN, SliderGradientPlugin.TAG,
`getPlatformVersion -> ${version}`);
result.success(version);
} else {
result.notImplemented();
}
} catch (err) {
hilog.error(SliderGradientPlugin.DOMAIN, SliderGradientPlugin.TAG,
`onMethodCall failed, method: ${call.method}, error: ${JSON.stringify(err)}`);
result.error('error', `method ${call.method} failed: ${JSON.stringify(err)}`, null);
}
}
}
5.1.1 引入 Flutter 和 OpenHarmony 能力
FlutterPlugin/FlutterPluginBinding/MethodChannel/MethodResult来自@ohos/flutter_ohos,负责接入 Flutter Engine 生命周期并处理 Dart 调用;deviceInfo来自@ohos.deviceInfo,提供displayVersion字符串(系统版本完整标识,语义最贴近"平台版本字符串");hilog来自@kit.PerformanceAnalysisKit,负责原生侧诊断日志,本例DOMAIN = 0xFF00、TAG = 'SliderGradientPlugin'。
5.1.2 连接 Flutter Engine 与错误处理
onAttachedToEngine 在 Engine 绑定插件时建立通道并设置 MethodCallHandler;onDetachedFromEngine 在 Engine 解绑时清空 Handler 并把 channel 置 null,避免下一次 Engine 绑定时残留旧 Handler。两个入口都用 try / catch 包住,并通过 hilog 记录成功 / 失败日志。
5.1.3 处理 MethodChannel 命令
onMethodCall 仅识别 getPlatformVersion,返回 `OpenHarmony ${deviceInfo.displayVersion}`(如 OpenHarmony 6.1.0.135);其他方法统一 result.notImplemented()。try / catch 中通过 result.error('error', ...) 把异常码 / 消息传回 Dart,触发 demo 的 catch 回调。
5.1.4 getUniqueClassName() 与 HAR 注册
getUniqueClassName() 必须返回 'SliderGradientPlugin',与 pubspec.yaml 的 plugin.platforms.ohos.pluginClass: SliderGradientPlugin 完全一致;Flutter 工具链在生成 GeneratedPluginRegistrant.ets 时会校验这个类名。
5.2 声明插件和宿主权限
SliderGradient 不读取传感器、不访问网络、不申请任何系统权限——组件本身是纯 Dart 实现,通道桩只读 deviceInfo.displayVersion。
5.2.1 插件 HAR 的权限
在插件的 ohos/src/main/module.json5 中不申请任何权限:
{
"module": {
"name": "slider_gradient",
"type": "har",
"deviceTypes": ["default", "tablet"]
}
}
5.2.2 应用 entry 的权限
最终安装的是宿主应用 example/ohos/entry。本例需要核对 example/ohos/entry/src/main/module.json5,保留 Flutter 模板自带的 ohos.permission.INTERNET(与插件功能无关):
{
"module": {
"requestPermissions": [
{"name": "ohos.permission.INTERNET"}
]
}
}
权限声明和运行时授权是两个步骤。接入应用时,还需根据目标 SDK 的权限定义处理授权要求;module.json5 中的声明不会自动完成运行时授权。INTERNET 属于普通权限,无需额外填写权限原因资源。
5.3 注册并导出插件
pubspec.yaml 通过以下配置声明 OHOS 插件类:
flutter:
plugin:
platforms:
android:
package: com.example.slider_gradient
pluginClass: SliderGradientPlugin
ios:
pluginClass: SliderGradientPlugin
ohos:
pluginClass: SliderGradientPlugin
适配前 pubspec.yaml 声明的是 web: 平台,本次替换为 ohos: 平台;插件类名 SliderGradientPlugin 三端一致。
插件的 ohos/index.ets 需要导出实现:
import SliderGradientPlugin from './src/main/ets/components/plugin/SliderGradientPlugin';
export default SliderGradientPlugin;
执行 flutter pub get 和构建后,Flutter 工具会为应用生成插件注册代码:
import { FlutterEngine, Log } from '@ohos/flutter_ohos';
import SliderGradientPlugin from 'slider_gradient';
const TAG = "GeneratedPluginRegistrant";
export class GeneratedPluginRegistrant {
static registerWith(flutterEngine: FlutterEngine) {
try {
flutterEngine.getPlugins()?.add(new SliderGradientPlugin());
} catch (e) {
Log.e(TAG, "Tried to register plugins with FlutterEngine (" + flutterEngine + ") failed.");
Log.e(TAG, "Received exception while registering", e);
}
}
}
import SliderGradientPlugin from 'slider_gradient' 说明 entry 模块通过 oh-package 依赖了根目录 ohos/ 生成的本地 HAR。注册进引擎的是通道桩:它注册与 Dart demo 完全一致的通道名 slider_gradient,但仅暴露 getPlatformVersion 一个方法。组件本体是纯 Dart 实现,不经过通道。
注册异常的排查步骤见第九节 MissingPluginException。
5.4 检查 example 的 OHOS 应用结构
本例的 example/ohos/AppScope/app.json5 中 bundleName 为 com.example.slider_gradient_example,与示例工程名一致,无残留坑。下面是需核对的配置片段,请合并到现有工程;其中 signingConfig: "default" 需要与本机配置的签名名称一致,签名材料不应提交到公开仓库(详见 9.4):
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "5.1.0(18)",
"runtimeOS": "HarmonyOS",
"compileSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": ["default"]
}
]
}
]
}
配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。
六、补全交付文件并提交适配分支
6.1 除代码外还要补全哪些文件
代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:
| 文件 | 应写清楚的内容 |
|---|---|
README.OpenSource | 上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖 |
README.md | 原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息 |
README.OpenHarmony_CN.md | 简介、AtomGit 安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题 |
README.OpenHarmony.md | 与中文说明对应的英文文档 |
CHANGELOG.OpenHarmony.md | OHOS 新增能力、适配版本、兼容限制与测试范围 |
LICENSE / NOTICE | 保留上游许可证;NOTICE 按许可证和原项目要求保留或补充 |
example/README.md | 依赖方式、运行目录、签名、操作步骤与效果图;覆盖 4 个场景 |
pubspec.yaml、ohos/oh-package.json5 | 核对包名、版本、插件注册、仓库地址、许可证和依赖 |
.gitignore | 忽略构建缓存及本机签名材料,不漏提交必要源码和配置 |
README.OpenHarmony_CN.md 记录库本身的来源与版本。本例的包名为 slider_gradient,pubspec 版本为 0.2.0(未随 OHOS 适配 bump,详见 9.7),采用 MIT 许可证;Flutter 和 HarmonyOS SDK 版本写入环境说明。
6.2 提交前检查
提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:
git branch --show-current
git diff --check
git status --short
git diff --stat
git diff
重点检查:
example/ohos/build-profile.json5仍带着本机调试签名的绝对路径与口令,发布前必须剥离(见 9.4);example/ohos/entry/src/main/module.json5只保留ohos.permission.INTERNET;docs/evidence/下保留关键证据(见第八节);pubspec.yaml的flutter.plugin.platforms把web:替换为ohos: pluginClass: SliderGradientPlugin,三端pluginClass一致。
6.3 提交并推送到 AtomGit
文档和代码整理完成后,在根目录暂存并提交。文件名按项目实际情况调整:
git add ohos pubspec.yaml lib .gitignore
git add example/pubspec.yaml example/lib example/ohos example/test docs
git add README.md README.OpenHarmony_CN.md
git add README.OpenHarmony.md CHANGELOG.OpenHarmony.md
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: 完成 OpenHarmony 平台适配"
git remote -v
git branch --show-current
git push -u origin feat/ohos_slider_gradient_0.2.0
DevEco 可能向 example/ohos/build-profile.json5 写入本机签名配置,需要在提交前从暂存内容中移除(详见 9.4)。推送时,origin 应指向自己的 AtomGit 仓库,当前分支为 feat/ohos_slider_gradient_0.2.0。
本文对应的实际提交是 b9d29488a61dad56ce3a636a517c782270cc605a(“feat: 完成 OpenHarmony 平台适配”,Co-Authored-By: AtomCode glm5.3-flash-pro),直接落在 main 分支并打 TAG 0.2.1(详见 9.3);该提交共 44 个文件、1543 行新增 / 146 行删除,包含根目录 ohos/ 通道桩、example/ohos/ 宿主工程、4 场景示例、docs/evidence/ 证据与三份 OpenHarmony 文档。
推送后在 AtomGit 发起合并请求,说明上游来源和版本、OHOS 实现范围(纯 Dart 组件 + 77 行通道桩)、依赖及权限(仅 INTERNET)、测试环境、操作结果、已知限制(详见第九节遗留问题),并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。
七、使用根目录 example 演示接入
仓库自带 example/,可以直接用来调试插件和体验 SliderGradient 在 OpenHarmony 真机上的四种用法。
7.1 本地适配时使用路径依赖
当前 example/pubspec.yaml 的依赖是:
dependencies:
flutter:
sdk: flutter
slider_gradient:
path: ../
../ 相对于 example/pubspec.yaml 指向插件根目录,修改根目录插件后可直接联调。
7.2 通过 AtomGit 引入插件
业务应用通过 AtomGit 引入时,将 slider_gradient 的 path 配置替换为下面的 Git 依赖。这里固定到 README 中声明的 TAG(注意:实际 TAG 名是 0.2.1,详见 9.3):
dependencies:
flutter:
sdk: flutter
slider_gradient:
git:
url: https://atomgit.com/oh-flutter/slider_gradient.git
ref: 0.2.1
使用自己的适配版本时,先推送分支,再将 url 改为对应仓库,ref 改为 feat/ohos_slider_gradient_0.2.0。正式发布后可固定到 tag 或 commit。
从插件根目录执行:
cd example
flutter pub get
flutter pub deps
检查 example/pubspec.lock 中 slider_gradient 的来源为 git,并核对 url、ref 和 resolved-ref。同时检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。
7.3 调用接口实现 4 场景演示
SliderGradient 的核心 API 只有一个组件,demo 把它扩展为 4 个场景,分别覆盖纯色背景、渐变背景(悬浮 label)、范围选择(双 thumb)、自定义样式,外加平台信息卡(演示 getPlatformVersion)与操作事件日志(最近 8 条)。下面这段可直接用于 example/lib/main.dart,对应仓库 docs/evidence/01_first_screen.jpeg、02_scrolled.jpeg、03_bottom.jpeg:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:slider_gradient/slider_gradient.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'SliderGradient Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.light,
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const MethodChannel _channel = MethodChannel('slider_gradient');
/// 平台信息(通过插件通道 getPlatformVersion 获取)
String? _platformVersion;
/// ① 纯色滑块
double _solidValue = 30;
Color? _solidColor;
/// ② 渐变滑块
double _gradValue = 50;
Color? _gradColor;
/// ③ 范围选择
List<double> _rangeValues = [20, 60];
Color? _rangeColorL;
Color? _rangeColorR;
/// ④ 自定义样式
double _customValue = 70;
Color? _customColor;
/// 操作事件(最近 8 条)
final List<String> _events = [];
void initState() {
super.initState();
_queryPlatformVersion();
}
Future<void> _queryPlatformVersion() async {
try {
final Object? result =
await _channel.invokeMethod('getPlatformVersion');
if (!mounted) return;
setState(() => _platformVersion = result?.toString() ?? '(空)');
_log('通道调用成功:$_platformVersion');
} catch (e) {
if (!mounted) return;
setState(() => _platformVersion = '调用失败: $e');
_log('通道调用失败: $e');
}
}
void _log(String msg) {
_events.insert(0, msg);
if (_events.length > 8) _events.removeRange(8, _events.length);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('slider_gradient OHOS demo'),
actions: [
IconButton(
icon: const Icon(Icons.replay),
onPressed: _queryPlatformVersion,
tooltip: '重新查询',
),
],
),
body: ListView(
padding: const EdgeInsets.all(8),
children: [
// 平台信息卡
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('平台信息', style: TextStyle(fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text('MethodChannel("slider_gradient").invokeMethod("getPlatformVersion") 返回:'),
SelectableText(_platformVersion ?? '查询中…'),
],
),
),
),
// ① 纯色背景
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('① 纯色背景', style: TextStyle(fontWeight: FontWeight.w600)),
const Text('isGradientBg=false,轨道为主色/辅色双色填充'),
SliderGradient(
value: _solidValue,
colors: const [Color(0xFF2196F3), Color(0xFFE3F2FD)],
isGradientBg: false,
onChange: (d) => setState(() {
_solidValue = d.value ?? _solidValue;
_solidColor = d.color;
}),
onChangeBegin: (_) => _log('纯色滑块:拖动开始'),
onChangeEnd: (d) => _log('纯色滑块:结果 ${d.value?.toStringAsFixed(0)}'),
),
if (_solidColor != null)
Text('thumb 颜色:${_solidColor.toString()}'),
],
),
),
),
// ② 渐变背景(悬浮 label)
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('② 渐变背景(悬浮 label)',
style: TextStyle(fontWeight: FontWeight.w600)),
const Text('isGradientBg=true(默认),isShowLabel=true,thumb 上方实时显示数值'),
SliderGradient(
value: _gradValue,
colors: const [Color(0xFFFF5252), Color(0xFF4CAF50)],
isShowLabel: true,
onChange: (d) => setState(() {
_gradValue = d.value ?? _gradValue;
_gradColor = d.color;
}),
onChangeBegin: (_) => _log('渐变滑块:拖动开始'),
onChangeEnd: (d) => _log('渐变滑块:结果 ${d.value?.toStringAsFixed(2)}'),
),
if (_gradColor != null)
Text('thumb 颜色:${_gradColor.toString()}'),
],
),
),
),
// ③ 范围选择(双 thumb)
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('③ 范围选择(双 thumb)',
style: TextStyle(fontWeight: FontWeight.w600)),
const Text('isRange=true,拖动时自动选中距离更近的 thumb'),
SliderGradient(
value: _rangeValues.first,
values: _rangeValues,
isRange: true,
colors: const [Color(0xFF4A90D9), Color(0xFF50E3C2)],
onChange: (d) => setState(() {
_rangeValues = d.values ?? _rangeValues;
_rangeColorL = d.colors?.first;
_rangeColorR = d.colors?.last;
}),
onChangeBegin: (_) => _log('Range 滑块:拖动开始'),
onChangeEnd: (d) => _log('Range 滑块:结果 ${d.values?.toList()}'),
),
Text('当前范围:${_rangeValues.toList()}, '
'左 thumb 颜色:${_rangeColorL.toString()}, '
'右 thumb 颜色:${_rangeColorR.toString()}'),
],
),
),
),
// ④ 自定义样式
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('④ 自定义样式',
style: TextStyle(fontWeight: FontWeight.w600)),
SliderGradient(
value: _customValue,
colors: const [
Color(0xFF9C27B0),
Color(0xFF03A9F4),
Color(0xFF8BC34A),
],
thumbStyle: const ThumbStyle(
width: 16, height: 32, radius: 8,
),
sliderStyle: const SliderStyle(height: 18, radius: 9),
isShowLabel: true,
onChange: (d) => setState(() {
_customValue = d.value ?? _customValue;
_customColor = d.color;
}),
onChangeBegin: (_) => _log('自定义滑块:拖动开始'),
onChangeEnd: (d) => _log('自定义滑块:结果 ${d.value?.toStringAsFixed(2)}'),
),
if (_customColor != null)
Text('thumb 颜色:${_customColor.toString()}'),
],
),
),
),
// 操作事件卡
Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('操作事件(最近 8 条)',
style: TextStyle(fontWeight: FontWeight.w600)),
for (final e in _events)
Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(e),
),
],
),
),
),
],
),
);
}
}
7.3.1 4 场景行为差异
| 场景 | 关键开关 | thumb 颜色来源 | 回调 payload |
|---|---|---|---|
| ① 纯色背景 | isGradientBg: false | colors 首尾两色分别做为主色 / 辅色,thumb 取主色 | data.value、data.color |
| ② 渐变背景(悬浮 label) | isGradientBg: true、isShowLabel: true | Color.lerp(colors[i], colors[i+1], percent) | data.value、data.color |
| ③ 范围选择(双 thumb) | isRange: true、values: [20, 60] | 两个 thumb 各自按位置插值 | data.values、data.colors |
| ④ 自定义样式 | thumbStyle、sliderStyle、colors 三色数组 | 三段渐变插值 | data.value、data.color |
7.4 页面退出时的异步处理
异步回调先检查 mounted,避免页面销毁后继续调用 setState。SliderGradient 不需要主动取消订阅,dispose 中无需额外清理。
多个页面都需要平台版本时,可以在应用启动阶段缓存一次 _platformVersion,各页面只读取缓存。
八、验证、构建与鸿蒙设备运行效果
8.1 分别验证插件与 example
从插件仓库根目录执行:
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze
flutter test
接口测试应覆盖 SliderGradient 的属性(value / colors / isRange / isShowLabel / thumbStyle / sliderStyle)、回调 payload(SliderData.value / values / color / colors)与边界条件(min < max 断言、isRange=true 时 values 长度 / 顺序断言)。
当前仓库的运行结果为:flutter analyze 在根目录与 example 目录下合计报告 0 error、6 issue——must_be_immutable(SliderGradient 的 value / colors / values / divisions 是 mutable 字段,设计上由外部修改以联动 thumb 位置)、unused_element(_isShowLabelClick)、invalid_null_aware_operator(sliderStyle?.height 在第 423 行,sliderStyle 非空)、test/slider_gradient_test.dart 中的 3 处未使用导入。flutter test 在本机因 flutter_tester 的 WebSocketException: Invalid WebSocket upgrade request 无法启动,这是本地测试环境问题,不是被测代码缺陷。
8.2 确认设备连接
hdc list targets
flutter devices
设备首次连接电脑时,需要在手机端确认调试授权。本文适配使用的设备为 4UQ9K25508013016(HUAWEI nova 14,TLR-AL00),系统 ROM OpenHarmony-6.1.1.120(API 24),架构 ohos-arm64。列表为空时,检查 USB 连接、调试模式和电脑授权。
8.3 配置签名
真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 entry 模块配置自动签名:
- 用 DevEco Studio 打开
example/ohos,不是仓库根目录; - 等待工程 Sync 成功,确认 Project 视图中存在
entry模块; - 打开 File > Project Structure > Signing Configs;
- 为
defaultproduct 选择或生成签名; - 确认设备、应用包名
com.example.slider_gradient_example、证书和 Profile 匹配; - 再回到终端执行 Flutter 构建或运行。
签名材料保存在本机,公开仓库中只保留构建所需的通用配置(详见 9.4)。
8.4 运行示例
以下命令在 example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:
flutter run -d <device-id>
也可以先构建 HAP:
flutter build hap --debug
典型产物位于:
example/ohos/entry/build/default/outputs/default/
目录中通常包含已签名和未签名 HAP。真机安装应选择与当前设备匹配的已签名产物。
8.5 在设备上测试 4 个场景
- 打开应用,确认首屏渲染"平台信息"卡(显示
OpenHarmony 6.1.0.135(SP9C00E120R3P5))和"① 纯色背景"滑块; - 拖动"① 纯色背景"滑块的 thumb,确认 thumb 沿轨道移动,操作事件卡出现"纯色滑块:拖动开始" / “纯色滑块:结果 N”;
- 滚动到"② 渐变背景",确认 thumb 上方出现悬浮 label(如
50.0),thumb 颜色随位置在红 / 绿之间插值; - 滚动到"③ 范围选择",确认双 thumb 独立拖动且不互相穿越,触点自动选择距离更近的 thumb,回调 payload 包含
values: [v1, v2]与colors: [c1, c2]; - 滚动到"④ 自定义样式",确认 thumb 尺寸(16×32,圆角 8)与轨道(高 18,圆角 9)符合
ThumbStyle/SliderStyle; - 点击右上角"重新查询"按钮,确认平台信息卡再次刷新,操作事件卡写入"通道调用成功:…";
- 切到后台再切回前台,确认页面状态不丢失;
- 卸载后重新安装,确认无缓存副作用。
8.6 鸿蒙设备运行效果
完成适配后,Flutter 应用能够在 OpenHarmony 6.1.1.120(API 24)真机上正常渲染 SliderGradient 的 4 个场景,通道桩 getPlatformVersion 返回 OpenHarmony 6.1.0.135(SP9C00E120R3P5)。
下面是 6 张真机截图,分别对应首屏、中段(渐变 + 范围选择)、底部(自定义样式 + 操作事件):
| 首屏 | 中段(滚动后) |
|---|---|
平台信息卡显示 OpenHarmony 6.1.0.135(SP9C00E120R3P5) + ① 纯色背景滑块(value=30,colors [0xFF2196F3, 0xFFE3F2FD])+ ② 渐变背景(顶部) | ② 渐变背景悬浮 label 50.0,thumb 颜色沿渐变插值;③ 范围选择双 thumb(values=[20, 60],跨度 40) |
截图来源:docs/evidence/01_first_screen.jpeg | 截图来源:docs/evidence/02_scrolled.jpeg |
| 底部(继续滚动) |
|---|
④ 自定义样式(value=100,thumb #FF9C27B0,SliderStyle(height: 18, radius: 9))+ 操作事件卡(最近 8 条) |
截图来源:docs/evidence/03_bottom.jpeg |
3 张截图均保留在仓库的 docs/evidence/ 目录下,便于评审时核对。SliderGradient 是纯 Dart 组件,没有额外的性能热点;OHOS 上运行效果与 Android / iOS 上等价,差异主要来自 Flutter 引擎本身。
我先读取这 6 张截图,确认每张内容后再统一整理成表格。
6 张截图均为同一个 SliderGradient 示例页(OpenHarmony TLR-AL00,6.1.0.135(SP9C00E120R3P5))在不同拖动状态下的快照。整理成两张表:
表 1 · 页面模块与配置说明
| 模块 | 关键配置 | 预期表现 |
|---|---|---|
| 平台信息 | MethodChannel("slider_gradient").invokeMethod("getPlatformVersion") | 展示运行平台版本、通道/方法名,提供「重新查询」按钮 |
| ① 纯色背景 | isGradientBg=false | 轨道为主色/辅色双色填充,下方展示当前值、百分比、Thumb 颜色 |
| ② 渐变背景(悬浮 label) | isGradientBg=true(默认)、isShowLabel=true | 蓝→青→绿→黄渐变轨道,thumb 上方悬浮 label 实时显示数值 |
| ③ 范围选择(双 thumb) | isRange=true | 红→绿渐变轨道,双 thumb;拖动时自动选中距离更近的 thumb,展示左/右端值、跨度、左右 Thumb 颜色 |
| ④ 自定义样式 | thumb 16×32、圆角 8;轨道高度 18;三色渐变 | 橙→红→紫三色渐变轨道,自定义圆角 thumb,悬浮 label 显示数值 |
| 操作事件(最近 8 条) | — | 滚动记录滑块结果、拖动开始、通道调用等事件,最新在最前 |
表 2 · 各截图实测状态快照
| 截图时刻 | ① 纯色背景(当前值 / 百分比 / Thumb 颜色) | ② 渐变背景(当前值 / 百分比 / label / Thumb 颜色) | ③ 范围选择(左 / 右 / 跨度 / 左色 / 右色) | ④ 自定义样式(当前值 / 百分比 / label) | 操作事件记录 |
|---|---|---|---|---|---|
| 22:02 | 30 / 30.0% / - | 50.00 / 50.0% / 50.0 / - | 未滚到 | 未滚到 | — |
| 22:03(a) | 75 / 75.0% / #FF2196F3 | 50.00 / 50.0% / 50.0 / - | 未滚到 | 未滚到 | — |
| 22:03(b) | 未显示 | 50.00 / 50.0% / 50.0 / - | 20 / 60 / 40 / - / - | 70.00 / - / 70.0 | — |
| 22:04 | 未显示 | 未显示 | 20 / 60 / 40 / - / - | 70.00 / 70.0% / 70.0 | 1. 纯色滑块:结果 75(75.0%);2. 纯色滑块:拖动开始;3. 通道调用成功:OpenHarmony TLR-AL00 6.1.0.135(SP9C00E120R3P5) |
| 22:05 | 未显示 | 53.00 / 53.0% / 53.0 / #FF5BE2BB | 18 / 83 / 65 / #FFDF6352 / #FF6A9F50 | 56.00 / - / 56.0 | — |
| 22:06 | 40 / 40.0% / #FF2196F3 | 53.00 / 53.0% / 53.0 / #FF5BE2BB | 35 / 83 / 48 / #FFC07351 / - | 未滚到 | — |
走查结论:四个示例卡片渲染正常;拖动过程中当前值、百分比、悬浮 label、Thumb 颜色(随轨道位置自动取色)均实时联动更新;范围滑块双 thumb 可独立拖动、跨度随动;MethodChannel 通道调用成功返回平台版本;操作事件按顺序落盘,符合预期。
以下是操作的视屏,可以参考一下:
九、FAQ:适配过程与使用问题
9.1 OHOS 上需要写原生 ArkTS 代码吗?
要写一个约 77 行的通道桩,复刻 getPlatformVersion 契约;但组件本体无需任何原生逻辑。
SliderGradient 本身是纯 Dart 组件(lib/slider_gradient.dart 中没有任何 MethodChannel / Platform / invokeMethod 调用),所有交互(GestureDetector / AnimationController / 自定义布局 _ModalSliderLayout / Color.lerp)都在 Flutter 框架内完成。因此 OHOS 上只要 Flutter 引擎能跑起来,组件就自然可用。
但为了保持与 Android / iOS 端完全一致的通道契约(通道名 slider_gradient、方法 getPlatformVersion),本次适配补全了:
pubspec.yaml的flutter.plugin.platforms新增ohos: pluginClass: SliderGradientPlugin(同时移除原web:平台);- 根目录
ohos/src/main/ets/components/plugin/SliderGradientPlugin.ets(77 行),注册同名通道,读取deviceInfo.displayVersion返回`OpenHarmony ${deviceInfo.displayVersion}`,其他方法统一notImplemented(); ohos/index.ets导出实现,GeneratedPluginRegistrant.ets自动注入注册代码。
如果未来插件作者在 OHOS 上提供真正的平台差异(例如读取特定传感器),只需把 notImplemented 替换为实际实现并保持通道契约即可。
9.2 为什么必须做空安全迁移?
SliderGradient 上游代码是 pre-null-safety 写法,而 OHOS 版 Flutter 3.44 自带 Dart 3.12,强制 null-safety。旧语法在编译期直接报错(undefined_named_parameter.key、non_nullable_field_not_initialized 等)。"Dart 层零改动"在此场景不可行。
迁移的最小化原则:
Key key→Key? key;@required→required;- 回调 / 数值 / 颜色字段改可空(
SliderChangeCallback?、double?、Color?、String?、List<Color>?); initData中给_beginColor/_endColor等late字段显式赋值;widget.colors实例字段无法参与类型提升,改用局部变量final colors = widget.colors;_doubleToInt删除 dead null check,直接int.parse(val.toString().split('.')[0]);pubspec.yaml中sdk: ">=2.7.0 <3.0.0"→sdk: ">=2.12.0 <4.0.0"。
API 名称、语义、默认值全部保持不变,对 Flutter 2.12+ 用户透明;Flutter 2.12 以下用户继续使用 pub.dev 原版本。
9.3 TAG 不一致问题的说明与修复建议
这是本次适配最重要的遗留问题。 README.OpenHarmony_CN.md 和 CHANGELOG.OpenHarmony.md 声明的 TAG 名是 0.2.0-ohos-1.0.0-beta.1,但仓库中根本不存在这个名字的 TAG:
git tag -l
# => 0.1.0
# => 0.1.1
# => 0.2.0
# => 0.2.1 ← 实际指向 OHOS 适配提交 b9d2948
如果用户按 README.OpenHarmony_CN.md 的示例配置 ref: 0.2.0-ohos-1.0.0-beta.1 来依赖,flutter pub get 会因为找不到该 TAG 而报错;正确做法是使用真实存在的 ref: 0.2.1 或直接 ref: main。
修复建议:
- 短期:保留现
0.2.1TAG 不动,把README.OpenHarmony_CN.md/README.OpenHarmony.md/CHANGELOG.OpenHarmony.md中所有0.2.0-ohos-1.0.0-beta.1替换为0.2.1,并补一行注释说明 “TAG 命名规则为原库版本-ohos-版本号,本次实际以0.2.1落地”。 - 长期:按
原库版本-ohos-版本号(README 中声明的规则)重新打 TAG,并在合并请求中追加git tag -f 0.2.0-ohos-1.0.0-beta.1 b9d2948,避免历史消费者继续踩坑。
本仓库的 pubspec.yaml 中 version 也仍是 0.2.0,未随 OHOS 适配 bump(见 9.7)。
9.4 签名材料入库问题
example/ohos/build-profile.json5 中包含本地调试签名材料,已随本次提交进入公开仓库:
signingConfigs[*].material.certpath = /Users/david/.ohos/config/default_ohos_y-fnKD-R1pebw874YSsfbf9rxqrjqGGjJn2J0eUmMRM=.cer
signingConfigs[*].material.storeFile = /Users/david/.ohos/config/default_ohos_y-fnKD-R1pebw874YSsfbf9rxqrjqGGjJn2J0eUmMRM=.p12
signingConfigs[*].material.profile = /Users/david/.ohos/config/default_ohos_y-fnKD-R1pebw874YSsfbf9rxqrjqGGjJn2J0eUmMRM=.p7b
keyPassword = 0000001A23A16F8C0D152EA655E138164E57BCD4FDC31B51C2FF81C27D77ABA03B4BE433A7E4F9FE07D2
storePassword = 0000001A11F982339A5D0733FA9B3FC8CDD82F3696BDD34E8BE5E185B6D733F29BA486FE96FBA4C9776F
certpath / storeFile / profile 是本机绝对路径,离开本机就找不到文件;keyPassword / storePassword 是调试签名口令(Base64 字符串),一旦泄露需要轮换证书。
处理顺序:
-
本地立即轮换调试证书:在 DevEco Studio 删除现有自动签名,重新生成一份;
-
剥离仓库中的敏感配置:把
example/ohos/build-profile.json5的signingConfigs替换为通用占位:{ "app": { "signingConfigs": [ { "name": "default", "type": "HarmonyOS" } ], "products": [ { "name": "default", "signingConfig": "default", "compatibleSdkVersion": "5.1.0(18)", "runtimeOS": "HarmonyOS" } ] }, "modules": [ { "name": "entry", "srcPath": "./entry", "targets": [{ "name": "default", "applyToProducts": ["default"] }] } ] } -
提交一个清理 PR:在 PR 描述中说明
git diff -- example/ohos/build-profile.json5已不再包含绝对路径或口令; -
避免再次写入:使用 DevEco Studio 时关闭"保存签名到项目",或把
example/ohos/build-profile.json5加入本地.gitignore(注意:这会影响其他贡献者,需要在 README 中说明)。
9.5 通道 getPlatformVersion 在真机上返回什么
ArkTS 端读取 deviceInfo.displayVersion,再拼上 OpenHarmony 前缀返回:
const version = `OpenHarmony ${deviceInfo.displayVersion}`;
result.success(version);
在 HUAWEI nova 14(TLR-AL00,OpenHarmony-6.1.1.120 ROM)上的实测返回:
OpenHarmony 6.1.0.135(SP9C00E120R3P5)
注意 deviceInfo.displayVersion 包含完整的 ROM build 号(SP9C00E120R3P5),不是 OpenHarmony-6.1.1.120 这种"用户视角"的系统版本号。README.OpenHarmony_CN.md 中"ROM: OpenHarmony-6.1.1.120"指的是设备 ROM 版本,而 getPlatformVersion 实际返回的是底层 displayVersion 字符串,两者差异属正常现象。
如果业务需要的是"用户视角"的版本号,应该在 Dart 侧用正则从返回值里抽取主版本号,或在 ArkTS 端改用其他字段(如 deviceInfo.majorVersion + deviceInfo.minorVersion 自行拼接)。
9.6 华为设备上 label 高度计算偏小
lib/slider_gradient.dart 中 labelTextHeight 给 TextPainter 显式传入了 locale: Localizations.localeOf(context),并在源码中保留了一条注释:
//AUTO:华为手机如果不指定locale的时候,该方法算出来的文字高度是比系统计算偏小的。
TextPainter painter = TextPainter(
locale: Localizations.localeOf(context),
...
);
迁移到 null-safety 后,方法签名从 String value 改为 String? value,并在函数开头加 if (value == null || value.length == 0) return 0;。OHOS 真机(同样存在类似机型差异)上保留这一行为;不要把 locale 改成 LOCALES.first,否则华为设备上 label 会被裁剪。
9.7 pubspec.yaml 的 version 仍然是 0.2.0
本次适配没有 bump pubspec.yaml 的 version 字段(仍是 0.2.0),但实际 git TAG 已经打在 0.2.1,README 中又声称 TAG 是 0.2.0-ohos-1.0.0-beta.1,三者目前是不一致的。
上游 0.2.0 是 OHOS 适配前的最后一个 TAG,OHOS 适配没有引入新功能(Dart 迁移 + 通道桩属于"适配工作"),从语义上是否 bump 都可以。但如果按 TAG 命名规则 原库版本-ohos-版本号 打 TAG(见 9.3),通常需要把 pubspec.yaml 的 version 也调整为 0.2.0-ohos-1.0.0-beta.1 这种带后缀的形式——而 Dart pub 规范不支持带 - 后缀的版本。
建议:
- 若不准备上游发布:保持
pubspec.yaml的version: 0.2.0不动,TAG 用真实存在的0.2.1(或后续 bump 到0.2.2),README 中如实写明"基于slider_gradient 0.2.0适配"; - 若准备上游发布:先在 PR 中与上游作者协商 bump 策略(如
0.2.1兼容 OHOS + null-safety 迁移),再合并。
9.8 LICENSE 与 oh-package.json5 的 license 不一致
LICENSE 文件声明的是 MIT,但 ohos/oh-package.json5 中 license 字段写的是 "Apache-2.0":
{
"name": "slider_gradient",
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {}
}
更奇怪的是,LICENSE 文件的 copyright 行写着 Radoslav Vitanov <radoslav.vitanov@icloud.com> (https://github.com/Sh1d0w)——这看起来像是从其它项目拷贝过来的模板残留,并不是 slider_gradient 的真实作者(GitHub 上 dilireba521)。
本文仅记录该不一致,不作修改。建议:
- 保留
LICENSE不动:插件作者在适配前已通过git log/LICENSE内容确认这是上游的现状; oh-package.json5的license改为"MIT":与上游保持一致;如果上游未来调整为Apache-2.0,再同步修改;LICENSE的 copyright 行:提一个上游 issue,让作者确认是否需要替换为真实作者(dilireba521);如果替换,需要在 PR 中同步更新。
9.9 编译成功但安装失败
常见原因包括:
- HAP 未签名或使用了错误的 Profile;
- 设备未加入调试设备列表;
- 包名与签名 Profile 不匹配(
AppScope/app.json5中的bundleName必须与 Profile 中的bundle-name一致,本例已确认为com.example.slider_gradient_example); - 安装包的
compatibleSdkVersion高于设备 API; - 手机上已经安装了使用不同证书签名的同包名应用。
根据安装错误码区分签名、版本和包名冲突,再处理对应配置。hvigor 在 SignHap 阶段强校验 hap 内 bundleName 与签名 profile 中的 bundle-name 一致(错误码 00303074),调试 profile 只能由 DevEco Studio 用已实名认证的华为账号按 bundleName 申请生成,无法跨 bundle 复用。
9.10 MissingPluginException
MissingPluginException 通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用:
cd example
flutter clean
flutter pub get
flutter run -d <device-id>
如果仍然出现,检查:
example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets是否包含import SliderGradientPlugin from 'slider_gradient';与flutterEngine.getPlugins()?.add(new SliderGradientPlugin());;pubspec.yaml的flutter.plugin.platforms.ohos.pluginClass是不是SliderGradientPlugin;ohos/index.ets是否正确export default SliderGradientPlugin;- 插件的
ohos/oh-package.json5的name字段是不是slider_gradient(与GeneratedPluginRegistrant中的import ... from 'slider_gradient'对应)。
由于组件本体不经过通道,仅在 demo 调用 getPlatformVersion 时需要插件注册;其他场景不会触发 MissingPluginException。
9.11 flutter create 不认识 ohos,或包名不合法
先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name slider_gradient;仓库名 slider_gradient 已经是合法 Dart 包名,直接使用即可。生成后检查 diff,再补充 ArkTS 业务实现(本例仅替换 SliderGradientPlugin.ets 为 77 行通道桩)。
如果 flutter create . 在仓库根目录崩溃(Xcode 12.5 过旧会导致 xcodebuild 探测抛 ProcessException),可在干净临时目录用 flutter create slider_gradient --template=plugin --platforms=ohos --no-pub 生成,再把 ohos/ 与 example/ohos/ 拷入仓库。
相关链接
更多推荐



所有评论(0)