开发工具: 华为云码道

原始仓库: kakzaki/flutter_color_brightness

本文配套仓库: oh-flutter/fluttertpc_color_brightness

color_brightness 把按百分比调整颜色明暗的常用算法封装为 Flutter 的 Color 扩展,应用可以直接在任意颜色上调用 darken() 和 lighten()。本文以分支 8013c036f22060a8289319e039e6a591b2b3079d 为例,介绍源码准备、OHOS 工程配置、ArkTS 实现和 example 的构建与验证。

在这里插入图片描述

插件目前支持 ohos 平台,源码位于 AtomGit 配套仓库。文中的代码以提交 6c38aa45efabf9d3c6faab61d2fcf43801d3113c 为参考。


一、插件简介与适配目标

颜色明暗调节是主题与视觉效果处理中的基础能力。应用给定一个基色和一个百分比,库按算法算出更深或更浅的同类颜色,业务层不需要自己换算 ARGB 通道,也不必为不同平台维护两套取色逻辑。

例如,卡片可以按主题色算出一个略深的悬浮层背景,按钮可以按主色算出按下态颜色,图表也可以按同一基色生成一组明暗递进的色阶。

明暗计算在 Dart 层同步完成:darken() 与 lighten() 都是纯函数,返回新的 Color 对象,不修改原对象,也不经过平台通道。

因此本库的鸿蒙化目标与"移植原生能力"类插件不同:功能面不需要重写,适配要做的是补齐 ohos 平台声明与与其他平台保持一致的 ArkTS 插件类,让该包在 OHOS 侧被正确登记为支持 ohos 的插件。


二、环境准备

环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。

完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:

flutter --version
flutter doctor -v
hdc list targets

工程使用的工具链和 SDK 配置如下:

项目版本或配置用途
Flutter OHOS SDK3.47.7-ohos-1.0.0Flutter 编译与 OHOS 平台工具链
Flutter 分支oh-3.47-devFlutter OHOS 对应开发分支
Dart SDK3.13.3Dart 语言与包管理环境
HarmonyOS 开发套件26.0.0 release(26.0.0.851)本机 DevEco Studio 与安装的 SDK 版本(API 26)
compileSdkVersion未在 product 中显式声明编译时使用本机 DevEco 安装的 SDK
targetSdkVersion6.1.1(24)应用面向的行为版本
compatibleSdkVersion6.1.1(24)当前工程声明的最低兼容版本
插件版本1.0.0pubspec.yaml 中的包版本
原生语言ArkTSHarmonyOS 插件实现
插件产物HAR被应用 entry 模块依赖

2.1 开发套件版本与工程中的 SDK 版本配置

本机 DevEco Studio 为 26.0.0,对应 API 26;宿主工程却把 compatibleSdkVersion 和 targetSdkVersion 声明为 6.1.1(24)。两者不是同一套编号,含义如下:

  • 26.0.0(API 26) 表示本机安装的 HarmonyOS 开发套件版本为 26.0.0,对应 API 26,编译时实际使用的就是它。
  • 6.1.1(24) 是本文 HarmonyOS 应用工程中 compatibleSdkVersion 和 targetSdkVersion 的属性值,表示 HarmonyOS 6.1.1,对应 API 24。

对应的 product 配置为:

{
  "name": "default",
  "signingConfig": "default",
  "compatibleSdkVersion": "6.1.1(24)",
  "targetSdkVersion": "6.1.1(24)",
  "runtimeOS": "HarmonyOS"
}

这组配置以 API 24 为目标版本,并以 API 24 作为最低兼容版本,与本文目标设备的 OpenHarmony-6.1.1.120(API 24)一致。DevEco 只安装了 API 26 的 SDK 并不影响该写法:compatibleSdkVersion 声明的是安装与运行的门槛,编译仍然使用已安装的 SDK。

版本号写法有一条固定规则:API ≥ 26 时直接写纯版本号(如 26.0.0),API < 26 必须写成 版本(API) 形式(如 6.1.1(24))。API 与版本号的对应关系可查 DevEco 的 hos-config.json(osVersionMapper)。安装报版本不匹配时,按这条规则对齐,而不是盲目沿用模板生成的 SDK 版本号。


三、从源码仓库开始准备适配工程

3.1 将上游源码同步到 AtomGit

适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。

在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。

fluttertpc_color_brightness 已从上游 kakzaki/flutter_color_brightness 同步到 AtomGit。上游包名为 color_brightness,版本为 1.0.0,许可证为 MIT。下面使用 AtomGit 地址拉取代码;需要提交修改时,使用自己有写权限的仓库或 Fork。

3.2 将代码拉取到宿主机

在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:

git clone https://atomgit.com/oh-flutter/fluttertpc_color_brightness.git
cd fluttertpc_color_brightness
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD

git clone 会创建 fluttertpc_color_brightness/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yaml、lib/ 和 example/。Git 仓库名是 fluttertpc_color_brightness,Dart 包名是 color_brightness。

需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:

git switch --detach 6c38aa45efabf9d3c6faab61d2fcf43801d3113c

适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

3.3 在仓库根目录确定适配分支与版本标记

本库没有为 OHOS 适配单独开特性分支,适配提交直接落在 master 上,版本通过 TAG 标记。TAG 命名规则为 原库版本-ohos-版本号-beta.x,本库使用 1.0.0-ohos-1.0.0-beta.1。在 fluttertpc_color_brightness/ 根目录执行:

git switch master
git branch --show-current
git tag --list '1.0.0-ohos-*'
git log --oneline -3

git branch --show-current 用于确认当前所在分支,git tag --list 用于确认版本标记是否已推送。向接收仓库提交时按其对分支与 TAG 的要求执行;需要在自己的 Fork 上独立维护时,再新建 feat/ohos_color_brightness_1.0.0 之类的分支也不影响上述规则。

3.4 自动补全 OHOS 适配结构

确认分支后,仍在同一个插件根目录执行结构补全。以下命令适用于尚无 ohos/ 目录的既有 Flutter 平台插件:

flutter create --template=plugin --platforms=ohos --project-name color_brightness .
git status --short
git diff -- pubspec.yaml lib example
  • --template=plugin 指定插件模板。
  • --platforms=ohos 指定需要补全的平台。
  • --project-name color_brightness 使用 Dart 包名,避免把仓库目录名作为包名。
  • 最后的 . 表示在当前插件目录补全工程,不是另建一层 color_brightness/。

该命令生成 OHOS 平台脚手架。生成后务必用 diff 检查:模板会连带注入与 OHOS 无关的内容,例如 federated 骨架(lib/*_method_channel.dart、lib/*_platform_interface.dart、lib/*_web.dart)、.kts 构建脚本、*.iml、local.properties,还可能改写 analysis_options.yaml。本次适配保留了已有 API、其他平台注册项及依赖配置,只保留 ohos/ 与 example/ohos/ 相关产物。不同 Flutter OH 版本生成的模板可能略有差异。

如果生成后 example/ohos/ 仍不存在,进入已有示例应用补全平台:

cd example
flutter create --platforms=ohos .
cd ..

配套仓库已经包含 ohos/ 和 example/ohos/,直接运行示例时可以跳过结构补全。新建插件则使用 flutter create --org com.example --template=plugin --platforms=ohos color_brightness;已有插件使用上面的 . 在当前目录补全。

3.5 适配后的项目目录

适配后的关键目录如下:

fluttertpc_color_brightness/
├── lib/
│   ├── color_brightness.dart
│   └── src/color_brightness_base.dart
├── ohos/
│   ├── index.ets
│   ├── oh-package.json5
│   ├── build-profile.json5
│   └── src/main/
│       ├── ets/components/plugin/ColorBrightnessPlugin.ets
│       └── module.json5
├── example/
│   ├── lib/main.dart
│   ├── lib/pages/color_brightness_demo_page.dart
│   └── ohos/entry/
├── docs/
└── pubspec.yaml

项目根目录如下,其中包含 ohos/、example/,以及 OpenHarmony 中英文说明和变更记录文件:

文件主要职责
lib/color_brightness.dart对外导出入口,暴露 Color 扩展
lib/src/color_brightness_base.dartdarken() / lighten() 的 ARGB 计算实现
ColorBrightnessPlugin.ets注册 Flutter 通道并返回平台版本信息
插件 module.json5声明 HAR 模块与设备类型,本库不申请权限
示例 entry module.json5声明宿主应用 Ability、设备类型与权限场景
example/lib/pages/color_brightness_demo_page.dart展示基色选择、比例调节、实时结果与计算结果日志

四、Dart 接口与通道分析

OHOS 实现需要遵循 Dart 层已有的方法、参数和回调约定。先阅读 lib/color_brightness.dart、lib/src/color_brightness_base.dart,再在 ohos/src/main/ets/components/plugin/ColorBrightnessPlugin.ets 中实现对应的原生调用。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。

本例的对应关系如下:

Dart 入口或模型通道协议OHOS 实现应保持的行为
Color.darken(percent)无,纯 Dart 计算不涉及原生代码各通道乘以 1 - percent / 100,Alpha 不变
Color.lighten(percent)无,纯 Dart 计算不涉及原生代码各通道向 255 线性插值,Alpha 不变
无 Dart 调用方color_brightness / getPlatformVersiondeviceInfo 读取系统版本返回"平台 + 版本"字符串,未识别方法返回未实现

原生端需要保持通道名、方法名和返回值结构与其余五个平台一致。方法执行异常时,通过错误回调通知 Dart 层。

4.1 跨端架构与调用时序

Flutter 侧与 HarmonyOS 侧之间有两条互不依赖的路径:

  1. 明暗计算:完全在 Dart 层完成,不经过任何通道,也不判断当前平台;
  2. MethodChannel('color_brightness'):只提供一个 getPlatformVersion 自检方法,与 Android、iOS、macOS、Windows、Linux 的实现逐字对齐。

通道自检

返回版本字符串

Flutter 页面

ColorBrightness 扩展

ARGB 纯计算

MethodChannel color_brightness

ArkTS ColorBrightnessPlugin

deviceInfo 系统信息

第一条路径决定功能是否可用,第二条路径决定插件形态是否完整。本库的功能不依赖通道,因此即使不注册插件,darken() 和 lighten() 在 OHOS 上依然可以正常计算。

4.1.1 一次颜色计算与一次通道自检的时序
HarmonyOS deviceInfo ColorBrightnessPlugin.ets ARGB 计算 ColorBrightness Flutter 页面 HarmonyOS deviceInfo ColorBrightnessPlugin.ets ARGB 计算 ColorBrightness Flutter 页面 base.darken(50) 各通道 × (1 - 50/100) 新的 Color(Alpha 不变) base.lighten(50) 各通道向 255 插值 50% 新的 Color 可选调用 invokeMethod(getPlatformVersion) 读取发行版名称与版本 HarmonyOS 6.1.0.145 result.success(字符串)

计算是同步的,调用返回即拿到结果;通道自检是异步的,只在业务主动调用时发生。

4.2 颜色计算模型:lib/src/color_brightness_base.dart

两个扩展方法都按整数通道处理,Alpha 原样保留:

extension ColorBrightness on Color {
  Color darken([double percent = 10]) {
    assert(1 <= percent && percent <= 100);
    double x = 1 - percent / 100;
    return Color.fromARGB(
        alpha, (red * x).round(), (green * x).round(), (blue * x).round());
  }

  Color lighten([double percent = 10]) {
    assert(1 <= percent && percent <= 100);
    double x = percent / 100;
    return Color.fromARGB(alpha, red + ((255 - red) * x).round(),
        green + ((255 - green) * x).round(), blue + ((255 - blue) * x).round());
  }
}
入参计算方式业务含义
darken(percent)每个通道乘以 1 - percent / 100数值越小颜色越深,100 得到黑色
lighten(percent)每个通道向 255 插值 percent / 100数值越大颜色越浅,100 得到白色
percent 超出 1~100触发 assert 断言失败属于调用方入参错误,库不做钳制

计算结果是新的 Color,原对象保持不变,因此同一个基色可以反复计算不同比例而互不影响。Alpha 不参与运算,半透明色调用后仍然保持原有的透明度。

4.3 公开 API 与平台接口

本库没有 PlatformInterface,也没有平台实例注册流程。对外只有一个扩展,业务在任意 Color 上直接调用:

const Color baseColor = Colors.red;

final Color darker = baseColor.darken(30);
final Color lighter = baseColor.lighten(30);

percent 是可选参数,默认值为 10。两个方法的参数和返回语义如下:

方法参数默认值返回
darkendouble percent,取值 1~10010新的 Color
lightendouble percent,取值 1~10010新的 Color

扩展对任意 Color 实例生效,包括 MaterialColor、Color.fromARGB 与 Color.fromRGBO 构造的颜色。由于没有异步回调和平台分支,业务侧不需要初始化、订阅或释放任何资源。

4.4 Dart 通道协议分析

4.4.1 通道名称必须两端完全一致
final methodChannel = const MethodChannel('color_brightness');

通道名称属于跨语言协议。Dart 和 ArkTS 任何一端拼写不一致,都会出现"方法未实现"等问题。ArkTS 侧使用同一个常量:

const CHANNEL_NAME: string = 'color_brightness';
4.4.2 Dart 层不调用通道,通道保留为自检手段

lib/ 下没有 invokeMethod、EventChannel 或 Platform.is* 判断,明暗计算完全在本地完成。如果业务需要确认插件是否注册成功,可以主动调用一次:

import 'package:flutter/services.dart';

const MethodChannel _channel = MethodChannel('color_brightness');

Future<String?> readPlatformVersion() async {
  try {
    return await _channel.invokeMethod<String>('getPlatformVersion');
  } on MissingPluginException {
    return null; // 插件未注册
  } on PlatformException catch (error) {
    debugPrint('getPlatformVersion 失败:${error.code} ${error.message}');
    return null;
  }
}

这段代码只是可选自检,不影响 darken() 和 lighten() 的使用。返回值应形如 HarmonyOS 6.1.0.145 或 OpenHarmony-6.1.1.120。

4.4.3 未识别的方法返回未实现

原生侧只处理 getPlatformVersion,其余方法统一走 notImplemented(),与其余五个平台一致:

if (call.method == 'getPlatformVersion') {
  result.success(this.getPlatformVersion());
} else {
  result.notImplemented();
}

保留这个分支的用意是让协议边界清晰:任何拼错的方法名都会立刻得到"未实现"的明确反馈,而不是静默返回空值。


五、补全 OHOS 原生实现与工程配置

5.1 在 ColorBrightnessPlugin.ets 中实现原生能力

以 getPlatformVersion 为例:业务调用 invokeMethod('getPlatformVersion') 时,Dart 通道发出同名命令。需要补全的是 ColorBrightnessPlugin.ets 中的对应分支:读取系统版本,通过 result.success 回传结果,异常时通过 result.error 返回错误码。明暗计算不在这里实现,它仍然由 Dart 层的扩展完成,业务侧调用方式没有任何变化。

ColorBrightnessPlugin 实现 FlutterPlugin 和 MethodCallHandler。下面列出类中的主要成员和方法,完整文件还包含 getUniqueClassName() 等插件接口。

原生插件位于:

ohos/src/main/ets/components/plugin/ColorBrightnessPlugin.ets
5.1.1 引入 Flutter 和 HarmonyOS 能力
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodCall,
  MethodCallHandler,
  MethodChannel,
  MethodResult,
} from '@ohos/flutter_ohos';
import deviceInfo from '@ohos.deviceInfo';
import { BusinessError } from '@ohos.base';
import hilog from '@ohos.hilog';

其中:

  • FlutterPlugin 负责接入 Flutter Engine 生命周期;
  • MethodChannel 接收 Dart 发来的命令;
  • deviceInfo 提供系统版本信息,export default 的导出形式决定了要用默认导入;
  • BusinessError 用于读取 HarmonyOS 异常码和异常消息;
  • hilog 用于原生侧诊断日志。

这里没有 EventChannel 和 EventSink:本库不产生持续事件,因此不需要事件通道。

5.1.2 连接 Flutter Engine
private channel: MethodChannel | null = null;

onAttachedToEngine(binding: FlutterPluginBinding): void {
  try {
    this.channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME);
    this.channel.setMethodCallHandler(this);
    hilog.info(DOMAIN, TAG, 'attached to engine, channel [%{public}s] registered', CHANNEL_NAME);
  } catch (err) {
    const error = err as BusinessError;
    hilog.error(DOMAIN, TAG, 'onAttachedToEngine failed, code: %{public}d, message: %{public}s',
      error.code, error.message);
  }
}

onAttachedToEngine 只做三件事:创建通道、注册处理器、写一条日志。通道注册失败时吞掉异常会让问题隐藏到运行期,所以这里用 hilog.error 保留现场的异常码和消息。

5.1.3 读取平台版本信息
private getPlatformVersion(): string {
  const osName: string = deviceInfo.distributionOSName ? deviceInfo.distributionOSName : '';
  const osVersion: string = deviceInfo.distributionOSVersion ? deviceInfo.distributionOSVersion : '';
  if (osName.length > 0 && osVersion.length > 0) {
    return osName + ' ' + osVersion;
  }
  if (deviceInfo.osFullName && deviceInfo.osFullName.length > 0) {
    return deviceInfo.osFullName;
  }
  return 'OpenHarmony API ' + deviceInfo.sdkApiVersion;
}

取值顺序与 Android 的 Build.VERSION.RELEASE、iOS 的 UIDevice.current.systemVersion 语义对齐:

  1. 优先取 distributionOSName + distributionOSVersion,即用户可见的发行版信息,例如 HarmonyOS 6.1.0.145;
  2. 其次取 osFullName,OpenHarmony 设备没有发行版信息时使用,格式本身已含平台名,因此不再重复加前缀;
  3. 最后用 sdkApiVersion 兜底,保证任何设备都能返回可读字符串。

这些字段均无需申请权限,只有 udid、ODID、diskSN 等字段需要。

5.1.4 没有常驻监听需要停止

本库不订阅系统事件,因此没有"取消订阅"这一步。getPlatformVersion 是一次性调用,返回即结束,不持有回调、不占用系统资源。

真正需要释放的是通道本身和处理器引用,它们在 Engine 解绑时统一清理,见 5.1.6。如果后续要在此基础上扩展"读取系统亮度"这类能力,才需要引入相应的权限与监听,并在页面退出时停止——那属于新的能力设计,不在本次适配范围内。

5.1.5 处理 MethodChannel 命令
onMethodCall(call: MethodCall, result: MethodResult): void {
  try {
    hilog.debug(DOMAIN, TAG, 'onMethodCall: %{public}s', call.method);
    if (call.method == 'getPlatformVersion') {
      result.success(this.getPlatformVersion());
    } else {
      result.notImplemented();
    }
  } catch (err) {
    const error = err as BusinessError;
    hilog.error(DOMAIN, TAG, 'onMethodCall [%{public}s] failed, code: %{public}d, message: %{public}s',
      call.method, error.code, error.message);
    result.error(ERR_CODE, 'Failed to handle ' + call.method + ': ' + error.message, error.message);
  }
}

result.error 将错误码和消息传回 Dart,触发 PlatformException;hilog 则保留原生侧的诊断信息。错误码使用常量 COLOR_BRIGHTNESS_ERROR,便于在日志中检索。

5.1.6 Engine 解绑时释放资源
onDetachedFromEngine(binding: FlutterPluginBinding): void {
  try {
    if (this.channel != null) {
      // 置空 handler 避免引擎销毁后仍持有回调,导致插件实例无法释放
      this.channel.setMethodCallHandler(null);
      this.channel = null;
    }
    hilog.info(DOMAIN, TAG, 'detached from engine');
  } catch (err) {
    const error = err as BusinessError;
    hilog.error(DOMAIN, TAG, 'onDetachedFromEngine failed, code: %{public}d, message: %{public}s',
      error.code, error.message);
  }
}

Flutter Engine 销毁时执行清理,注销通道处理器并释放引用,覆盖应用退出、多引擎切换等场景。

5.2 声明插件和宿主权限

本库的核心能力由 Dart 层实现,原生侧只注册方法通道并读取系统版本信息,因此不需要任何权限。

5.2.1 插件 HAR 的权限

插件 ohos/src/main/module.json5 中没有 requestPermissions 字段,模块信息保持如下:

{
  "module": {
    "name": "color_brightness",
    "type": "har",
    "deviceTypes": ["default", "tablet"]
  }
}

type 为 har,表示它作为静态库被宿主 entry 依赖。这里不添加权限声明,也不会因为缺少权限而出现功能受限。

5.2.2 应用 entry 的权限

宿主应用 example/ohos/entry/src/main/module.json5 中保留了模板自带的网络权限:

{
  "module": {
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ]
  }
}

这条声明是工程模板的默认项,与本库的颜色计算和版本读取都无关,删掉不影响示例运行。getPlatformVersion 读取的 distributionOSName、distributionOSVersion、osFullName、sdkApiVersion 均为系统公开信息,不涉及运行时授权。

权限声明和运行时授权是两个步骤。若后续为插件新增能力,需要按目标 SDK 的权限定义处理授权;module.json5 中的声明不会自动完成运行时授权。

5.3 注册并导出插件

pubspec.yaml 通过以下配置声明 OHOS 插件类:

flutter:
  plugin:
    platforms:
      ohos:
        pluginClass: ColorBrightnessPlugin

原有 android、ios、linux、macos、windows、web 的注册项保持不变。

插件的 ohos/index.ets 需要导出实现:

import ColorBrightnessPlugin from './src/main/ets/components/plugin/ColorBrightnessPlugin';
export default ColorBrightnessPlugin;

执行 flutter pub get 和构建后,Flutter 工具会为应用生成插件注册代码。通常不应手工编辑 GeneratedPluginRegistrant.ets,因为下次构建可能覆盖它。

注册异常的排查步骤见第九节 MissingPluginException。

5.4 检查 example 的 OHOS 应用结构

本例的 example/ohos/build-profile.json5 应在 products 中设置版本。下面是需核对的配置片段,请合并到现有工程;其中 signingConfig: "default" 需要与本机配置的签名名称一致,签名材料保留在本地。

{
  "app": {
    "products": [
      {
        "name": "default",
        "signingConfig": "default",
        "compatibleSdkVersion": "6.1.1(24)",
        "targetSdkVersion": "6.1.1(24)",
        "runtimeOS": "HarmonyOS"
      }
    ]
  },
  "modules": [
    {
      "name": "entry",
      "srcPath": "./entry",
      "targets": [
        {
          "name": "default",
          "applyToProducts": ["default"]
        }
      ]
    }
  ]
}

配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。

版本号要与目标设备对齐:本文设备为 API 24,因此两项都写 6.1.1(24)。沿用模板默认的 26.0.0 会导致安装时报版本不匹配,详见 9.7。


六、补全交付文件并提交适配分支

6.1 除代码外还要补全哪些文件

代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:

文件应写清楚的内容
README.md原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息
README.OpenHarmony_CN.md简介、AtomGit 安装方式、版本对应关系、环境约束、权限、接口表、示例、已验证范围和遗留问题
README.OpenHarmony.md与中文说明对应的英文文档
CHANGELOG.OpenHarmony.mdOHOS 新增能力、适配版本、兼容限制与测试范围
LICENSE保留上游许可证,本库为 MIT
example/README.md依赖方式、运行目录、签名、操作步骤与效果图
pubspec.yaml、ohos/oh-package.json5核对包名、版本、插件注册、仓库地址、许可证和依赖
.gitignore忽略构建缓存及本机签名材料,不漏提交必要源码和配置

本库是单包插件,直接沿用上游 LICENSE,未额外增加 NOTICE。上游版权与许可证信息已在 README.OpenHarmony_CN.md 中说明:包名 color_brightness,版本 1.0.0,MIT 许可证,来源为 kakzaki/flutter_color_brightness。

整理时还有两处要核对:ohos/oh-package.json5 的 license 字段目前仍是模板生成的 Apache-2.0,与上游 MIT 不一致,应改为与 LICENSE 相同;example/README.md 仍是模板默认内容,尚未写运行步骤。

6.2 提交前检查

提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:

git branch --show-current
git diff --check
git status --short
git diff --stat
git diff

检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。

本仓库当前工作区中,example/ohos/build-profile.json5 已被写入本机证书路径与加密口令。这类内容属于个人环境信息,提交前必须还原为空的 signingConfigs,不能进入仓库。

6.3 提交并推送到 AtomGit

文档和代码整理完成后,在根目录暂存并提交。文件名按项目实际情况调整:

git add ohos pubspec.yaml example
git add README.md README.OpenHarmony_CN.md README.OpenHarmony.md
git add CHANGELOG.OpenHarmony.md docs .gitignore
git diff --cached --check
git diff --cached --stat
git diff --cached
git commit -m "feat: add OpenHarmony platform support for color_brightness"
git remote -v
git branch --show-current
git push -u origin master
git tag 1.0.0-ohos-1.0.0-beta.1
git push origin 1.0.0-ohos-1.0.0-beta.1

DevEco 可能向 example/ohos/build-profile.json5 写入本机签名配置,需要在提交前从暂存内容中移除。推送时,origin 应指向自己的 AtomGit 仓库,当前分支为 master。

推送后在 AtomGit 发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。


七、使用根目录 example 演示接入

仓库自带 example/,可以直接用来调试插件和体验颜色明暗变化。

7.1 本地适配时使用路径依赖

当前 example/pubspec.yaml 的依赖是:

dependencies:
  flutter:
    sdk: flutter
  color_brightness:
    path: ../

../ 相对于 example/pubspec.yaml 指向插件根目录,修改根目录插件后可直接联调。

7.2 通过 AtomGit 引入插件

业务应用通过 AtomGit 引入时,将 color_brightness 的 path 配置替换为下面的 Git 依赖。这里固定到本文使用的版本 TAG:

dependencies:
  flutter:
    sdk: flutter
  color_brightness:
    git:
      url: https://atomgit.com/oh-flutter/fluttertpc_color_brightness.git
      ref: 1.0.0-ohos-1.0.0-beta.1

使用自己的适配版本时,先推送分支与 TAG,再将 url 改为对应仓库,ref 改为自己的 TAG 或提交号。需要跟随开发过程时也可以先用分支名。

从插件根目录执行:

cd example
flutter pub get
flutter pub deps

检查 example/pubspec.lock 中 color_brightness 的来源为 git,并核对 url、ref 和 resolved-ref。同时检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖,确认应用使用的是 Git 依赖。

7.3 调用接口实现明暗对照页面

下面的页面展示基色选择、比例调节和三种颜色的对照,可用于 example/lib/main.dart。仓库中的完整 Demo 还提供色阶矩阵、HEX 明细、操作历史与日志。

import 'package:color_brightness/color_brightness.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: BrightnessPage()));
}

class BrightnessPage extends StatefulWidget {
  const BrightnessPage({super.key});

  
  State<BrightnessPage> createState() => _BrightnessPageState();
}

class _BrightnessPageState extends State<BrightnessPage> {
  static const List<Color> _colors = <Color>[
    Colors.red,
    Colors.orange,
    Colors.green,
    Colors.teal,
    Colors.blue,
    Colors.purple,
  ];

  int _index = 0;
  double _percent = 50;

  Color get _base => _colors[_index];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('颜色明暗调节')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: List<Widget>.generate(_colors.length, (int i) {
                return ChoiceChip(
                  label: Text('基色 ${i + 1}'),
                  selected: i == _index,
                  avatar: CircleAvatar(backgroundColor: _colors[i], radius: 8),
                  onSelected: (_) => setState(() => _index = i),
                );
              }),
            ),
            const SizedBox(height: 12),
            Slider(
              min: 1,
              max: 100,
              divisions: 99,
              value: _percent,
              label: '${_percent.round()}%',
              onChanged: (double value) => setState(() => _percent = value),
            ),
            Text('当前比例:${_percent.round()}%'),
            const SizedBox(height: 12),
            Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                _Swatch(color: _base, label: '原色'),
                const SizedBox(width: 12),
                _Swatch(color: _base.darken(_percent), label: 'darken'),
                const SizedBox(width: 12),
                _Swatch(color: _base.lighten(_percent), label: 'lighten'),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class _Swatch extends StatelessWidget {
  const _Swatch({required this.color, required this.label});

  final Color color;
  final String label;

  
  Widget build(BuildContext context) {
    return Expanded(
      child: Column(
        children: <Widget>[
          Container(height: 80, color: color),
          const SizedBox(height: 4),
          Text(label),
        ],
      ),
    );
  }
}

滑块的 min 和 max 固定为 1 和 100,与库内 assert 的取值范围一致,避免拖动过程中触发断言失败。

7.4 页面退出时的处理

本库没有订阅、没有异步回调,也没有需要释放的原生资源,因此不需要写 dispose 来取消任何东西,也不需要为回调检查 mounted。

需要注意的是相反的方向:darken() 和 lighten() 在 build 中同步执行,比例变化时由 setState 触发重建即可拿到新颜色。如果业务把计算结果缓存到 State 字段,则要在比例或基色变化时一起更新,否则界面会显示上一次的结果。

多个页面共用同一套配色时,可以把"基色 + 比例 → 颜色"的换算抽成主题层的方法,各页面直接取用,避免各自复制一份比例常量导致色调不一致。


八、验证、构建与鸿蒙设备运行效果

8.1 分别验证插件与 example

从插件仓库根目录执行:

flutter pub get
flutter analyze
cd example
flutter pub get
flutter analyze

本仓库没有保留测试目录,因此没有 flutter test 这一步:功能是纯函数计算,验证以静态分析和页面手工对照为主。如果需要自动化测试,可以按 darken(0)、darken(101) 这类边界补充断言用例,或在 Widget 测试中比对色块的实际颜色值。

静态分析方面,仓库现有记录为插件工程存在 11 条既有提示,全部是 Color.red/green/blue 的弃用告警,与本次适配无关;示例工程为 No issues found!。重新执行时以自己的输出为准,重点是确认没有新增问题。

git diff --stat -- lib/ 的输出应为空,这是"Dart 层零改动"最直接的验收方式。

8.2 确认设备连接

hdc list targets
flutter devices

设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。

8.3 配置签名

真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 entry 模块配置自动签名:

  1. 用 DevEco Studio 打开 example/ohos,不是仓库根目录;
  2. 等待工程 Sync 成功,确认 Project 视图中存在 entry 模块;
  3. 打开 File > Project Structure > Signing Configs;
  4. 为 default product 选择或生成签名;
  5. 确认设备、应用包名、证书和 Profile 匹配;
  6. 再回到终端执行 Flutter 构建或运行。

签名材料保存在本机,公开仓库中只保留构建所需的通用配置。

8.4 运行示例

以下命令在 example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:

flutter run -d <device-id>

也可以先构建 HAP:

flutter build hap --debug

在 Windows 宿主上,flutter build hap 可能在 hvigor 的 ohpm install 阶段失败(见 9.11)。此时可以直接调用 hvigor 打包,效果等价:

cd example/ohos
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

该命令需要 java 在 PATH 中,若命令行找不到 java,把 DevEco 自带的 <DevEco>/jbr/bin 加入 PATH。

典型产物位于:

example/ohos/entry/build/default/outputs/default/

目录中通常包含 entry-default-unsigned.hap;完成签名配置后会出现已签名产物。真机安装应选择与当前设备匹配的已签名产物。

8.5 在设备上测试明暗调节

  1. 打开应用,确认初始状态为红色基色、比例 50%,对照区显示原色、darken(50) 与 lighten(50) 三个色块;
  2. 依次切换红、橙、绿、青、蓝、紫六个基色,确认三个色块同步换色;
  3. 把比例拖到 1% 与 100% 两个边界,确认最浅与最深的结果符合预期;
  4. 在自定义基色区域调整 R、G、B 通道,确认结果只随滑块变化,不出现异常;
  5. 点击"应用",检查实时结果的 HEX 值与操作历史是否更新;
  6. 观察 flutter run 控制台的日志,日志标签为 color_brightness_example,应出现 darken(...) = #...;lighten(...) = #...;
  7. 点击右上角重置,确认回到初始状态且历史清空;
  8. 反复切换与调节,确认无卡顿、无断言失败弹窗。

页面初始显示的三个色块来自示例默认值,属于本地计算,不代表任何平台差异。真机验证的重点是安装成功、页面可交互、颜色与预期一致。

本文尚未在鸿蒙设备上安装运行,以上为待执行的验收步骤,不作为"已通过"的证据。

8.6 鸿蒙设备运行效果

适配完成后,Flutter 应用在鸿蒙设备上可以直接使用 darken() 与 lighten():任意基色都能得到一组明暗递进的颜色,半透明色的 Alpha 保持不变,页面切换基色与比例时三个色块同步刷新。

下面三个位置用于放置真机运行截图:

原色darken(50)lighten(50)
待补真机截图待补真机截图待补真机截图

本库不依赖设备硬件能力,只要应用能正常安装启动,颜色计算即与平台无关,不存在机型差异。


九、FAQ:适配过程与使用问题

9.1 Missing SDK components

典型错误如下:

Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.

这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。

处理顺序:

  1. 在 DevEco Studio SDK Manager 中确认所需 API 组件已经下载完整;
  2. 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
  3. 避免误用 /Applications/DevEco-Studio.app/Contents/sdk 之类的不完整目录,Windows 上同理不要指向 DevEco 安装目录下的中间目录;
  4. 确认 SDK 根目录下存在 toolchains、ets、js、native、previewer;
  5. 执行 flutter config --ohos-sdk <正确路径>;
  6. 重新执行 flutter doctor -v 和 DevEco Studio Sync。
为什么连接 API 24 手机仍然会报这个错误?

因为 Sync 和 Compile 首先读取宿主机本地 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。

当前工程的 compatibleSdkVersion 是 API 24,因此 API 24 在安装版本门槛上是满足的;本机只装 API 26 的 SDK 也能编译,因为 compatibleSdkVersion 声明的是最低兼容版本。

9.2 DevEco Studio 中看不到 entry 模块

插件的 ohos/ 目录是 HAR 模块,可安装应用的 entry 模块位于 example/ohos/entry。

请直接使用 DevEco Studio 打开:

fluttertpc_color_brightness/example/ohos

如果仍看不到 entry,先解决 SDK Sync 错误,再检查 example/ohos/build-profile.json5 的 modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。

9.3 无法手动签名

签名配置依附于可构建的应用模块和 product。只有 HAR 插件模块、工程 Sync 失败,或者打开了错误目录时,DevEco Studio 都可能无法提供 entry 签名入口。

建议先确认:

  • 打开的是 example/ohos;
  • SDK 组件完整并且 Sync 成功;
  • entry 的模块类型为 entry;
  • default product 和 target 已正确关联;
  • 当前账号、证书和调试设备状态有效。

9.4 能安装但颜色没有变化

按以下顺序检查:

  1. 是否使用了方法的返回值——darken() 返回新对象,不会修改原 Color;
  2. 比例是否真的传到了计算处,例如滑块的值是否被 clamp 后仍为 1~100;
  3. 是否在 setState 中更新了比例或基色,缺失时界面不会重建;
  4. 色块是否使用了 const 构造导致没有跟随状态变化;
  5. 是否把计算结果缓存进 State 字段却没有同步更新;
  6. 是否在自定义基色模式下仍以为使用预设色,此时基色由 R/G/B 滑块决定。

9.5 MissingPluginException

这通常表示 Dart 通道找不到已注册的原生插件。新增原生插件后需要重新构建应用。从仓库根目录执行:

cd example
flutter clean
flutter pub get
flutter run -d <device-id>

如果仍然出现,检查自动生成的插件注册文件中是否包含 ColorBrightnessPlugin,同时核对 pubspec.yaml、ohos/index.ets 和 oh-package.json5。

需要说明的是:本库的明暗计算不经过通道,因此即使出现该异常,darken() 和 lighten() 仍然可用,受影响的只有主动调用 getPlatformVersion 的自检逻辑。

9.6 连续调用会不会累积变暗

不会。darken() 和 lighten() 都是纯函数,每次都以传入的颜色为基准重新计算,不修改原对象,也没有内部状态或缓存。

如果出现"越点越黑",通常是把上一次的结果当作下一次的输入,例如在事件里写成 _color = _color.darken(10)。需要固定基准色时,始终从原始基色出发计算。

9.7 编译成功但安装失败

常见原因包括:

  • HAP 未签名或使用了错误的 Profile;
  • 设备未加入调试设备列表;
  • 包名与签名 Profile 不匹配;
  • 安装包的 compatibleSdkVersion 高于设备 API;
  • 手机上已经安装了使用不同证书签名的同包名应用;
  • 工程声明的 API 与设备 apiVersion 不一致,尤其是沿用模板默认的 26.0.0 而设备为 API 24。

根据安装错误码区分签名、版本和包名冲突,再处理对应配置。版本一项按 2.1 的规则改成设备的 版本(API) 形式即可。

9.8 flutter create 不认识 ohos,或包名不合法

先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name color_brightness;仓库目录名 fluttertpc_color_brightness 与 Dart 包名不同,不能直接当作包名使用。生成后检查 diff,清理模板注入的 federated 骨架与 .kts 构建脚本,再补充 ArkTS 业务实现。

9.9 AtomGit 依赖提示找不到分支、TAG 或无权限

先检查 URL 是否指向已同步的目标仓库,再确认 ref 中的 TAG 1.0.0-ohos-1.0.0-beta.1 已经推送。TAG 尚未推送时,可以先使用第七节的提交号。私有仓库还需在本机配置 Git 认证。

9.10 改了本地 ArkTS,Demo 为什么没变化

先检查 example/pubspec.yaml:Git 依赖读取远程提交,不会自动读取本地插件改动。本地联调切回 path: ../;测试远程版本则先提交推送,再更新依赖并核对 pubspec.lock 的 resolved-ref。原生代码变动后停止应用并重新构建运行,不能只做 Dart 热重载。

9.11 Windows 下 flutter build hap 卡在 ohpm install

典型现象是构建很快失败,并输出:

> hvigor start to execute ohpm install
******  B A T C H   R E C U R S I O N  exceeds STACK limits ******
Recursion Count=242, Stack Usage=90 percent
> hvigor ERROR: 00306053 Specification Limit Violation
Error Message: ohpm install failed.

这是 DevEco 自带 ohpm.bat 在 Windows 下按字符递归处理首个参数导致的批处理栈溢出,与插件代码无关:手动在 example/ohos 执行 ohpm install 可以通过,直接调用 hvigorw 也不会复现,只有经 flutter build hap 拉起时触发。

可用处理方式:

cd example/ohos
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

如果此时报 spawn java ENOENT,把 DevEco 自带的 jbr/bin 加入 PATH 后重试。也可以直接在 DevEco Studio 中构建。构建成功后可核对产物同级 pack.info 的 apiVersion,确认 compatible、target 与设备一致。


相关链接

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐