什么是动态取色(Dynamic Color / Material You)?

Android 12(S)起,系统会从用户壁纸中提取一组颜色(Material You),派生出 CorePalette 调色板与完整的 ColorScheme 配色方案,App 拿到后即可让整个界面"随壁纸变色",这正是 Material 3 设计语言的核心体验。

但鸿蒙(OpenHarmony / HarmonyOS NEXT)没有 Material You 动态取色、也没有系统级 accent color 的等价公开 API。适配的关键就落在:原生取不到颜色时,如何优雅地 null、回退,并把库的其余纯 Dart 能力(色板转换、配色协调)完整带到鸿蒙上。

本文基于 flutter_dynamic_system_colors(原 dynamic_color)的鸿蒙适配版,介绍如何在 OpenHarmony 上接入这套能力。

开发工具:华为云码道(DevEco Studio)

配套仓库:原库 hasali19/flutter_dynamic_system_colors,鸿蒙适配版 https://atomgit.com/CPF-Flutter/fluttertpc_flutter_dynamic_system_colors(TAG:1.9.0-ohos-1.0.0-beta.1,分支 br_v1.9.0_ohos)

在这里插入图片描述


一、最终运行效果

示例 App 启动后展示适配演示页:

请添加图片描述

页面上半部分是三个原生接口的调用区:getCorePalette()、getColorSchemes()、getAccentColor() 三个按钮,点击后逐个调用并在下方"调用结果"卡片里显示返回状态。在 OpenHarmony 真机上,三个接口都返回 null(系统未提供动态取色能力),结果卡片以灰色减号标识"完成:null"。

请添加图片描述

页面下半部分依次是:

  • DynamicColorBuilder 卡片:显示"未检测到系统动态配色,使用回退配色(OpenHarmony 上即为该结果)",并提供回退 primary 色块、暗色色块,以及一个演示 配色协调(harmonization) 的色块——Colors.red.harmonizeWith(primary),可见红色已向主题蓝色偏移;
  • 色板预览卡片:用 CorePalette.of(0xFF1E88E5) 生成的回退色板,按 Primary / Secondary / Tertiary / Neutral / Neutral Variant 五行 × 0~100 色调逐格展示,直观呈现"一个种子色如何展开成完整调色板";
  • 说明卡片:四条要点解释鸿蒙上的行为与回退策略。

验证点结果一览:

验证点结果
插件注册(Adding plugin: DynamicColorPlugin)✅ 通过
通道注册 io.material.plugins/dynamic_color✅ 通过
getColorSchemes() 返回 null(不抛异常)✅ 通过
getCorePalette() 返回 null(不抛异常)✅ 通过
getAccentColor() 返回 null(不抛异常)✅ 通过
Dart 层回退分支(“Dynamic color not detected”)✅ 通过
CorePalette → 色板预览 / harmonizeWith 纯 Dart 能力✅ 通过
全程无需申请任何敏感权限✅ 通过

KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页 KeyHash 示例页

鸿蒙技术点:FlutterPage 与 XComponent 渲染管线

与所有 Flutter 鸿蒙化工程一致:Index.ets 的 @Entry 组件在 build() 中使用 FlutterPage,其内部封装 XComponent 作为底层渲染画布,通过 NAPI 桥接 C++ 引擎层并挂载 Skia 渲染管线;EntryAbility 继承 FlutterAbility,在 configureFlutterEngine 回调里调用 GeneratedPluginRegistrant.registerWith(flutterEngine) 完成插件注册——我们的 DynamicColorPlugin 就是在这一步被挂进引擎的。

以下是操作的视屏,可以参考一下:

Example 启动授权 Example 启动授权 Example 启动授权


二、dynamic_system_colors 是什么

原库是 Material 团队 dynamic_color 包的社区维护分支(pub 包名 dynamic_system_colors,版本 1.9.0),用于根据操作系统实现的动态取色能力生成 Material 配色方案:

  • Android S+:从系统取 Material You 壁纸派生的 CorePalette,Dart 侧用扩展方法转成 ColorScheme;
  • Android U+:直接取系统给出的明暗两套 ColorSchemes;
  • macOS / Windows / Linux:取系统强调色(accent color),再用 ColorScheme.fromSeed() 播种生成方案;
  • 全平台附带 配色协调(harmonization) 能力,让自定义颜色与动态主色视觉融合。

鸿蒙适配版在此基础上新增 ohos 平台:Dart 层 API 零改动,OpenHarmony 侧提供 ArkTS 插件 DynamicColorPlugin 注册同名方法通道。

使用者友好特点(分条):

  • 一套代码跨五端:Android、iOS、OHOS、桌面端、Web 共用同一套 Dart API;
  • null 即语义:Dart 层用 OptionalMethodChannel,平台不支持时拿到的是 null 而非异常,回退逻辑自然;
  • 纯 Dart 计算:toColorScheme()、harmonized()、harmonizeWith() 全部由 Dart 层实现,鸿蒙上与其它平台表现一致;
  • 零权限:插件不访问任何系统能力,HAR 的 module.json5 不申请任何权限;
  • 色调反推黑科技:对 TonalPalette 中不常见到的色调(如 87、92、94)用 HCT 色彩空间反推生成,保证 surfaceContainer 系列颜色准确。

接口说明

DynamicColorPlugin 静态方法:

方法返回说明
getCorePalette()Future<CorePalette?>Android S+ 返回壁纸动态调色板;其余平台(含 OHOS)返回 null
getColorSchemes()Future<ColorSchemes?>Android U+ 直接返回明暗两套 ColorScheme;其余平台返回 null
getAccentColor()Future<Color?>macOS 10.14+ / Windows Vista+ / GTK 桌面返回系统强调色;其余平台返回 null

DynamicColorBuilder 构件:

名称类型说明
DynamicColorBuilderStatefulWidgetbuilder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic),自动依次探测三种来源,取不到时两个参数均为 null

Dart 侧扩展方法:

扩展说明
CorePalette.toColorScheme({brightness})将调色板映射为完整 ColorScheme(明/暗两套)
ColorScheme.harmonized()将 scheme 中的 error 系列颜色向 primary 协调
Color.harmonizeWith(color)将任意颜色向指定颜色(通常是 primary)协调

各平台取色路径表

平台getColorSchemesgetCorePalettegetAccentColor
Android U+✅ 系统直出✅ 系统直出—
Android S~U—✅ 系统直出—
macOS / Windows / Linux——✅ 系统强调色
OpenHarmony(本文)❌ 返回 null❌ 返回 null❌ 返回 null
iOS / Web———

说明:OptionalMethodChannel 在通道不存在或方法未实现时同样返回 null,因此 iOS/Web 与 OHOS 走的是同一条"不支持 → null → 业务方回退"路径。


三、环境准备

本文实测环境:

环境项版本
Flutter(ohos 版)3.47.7-ohos-1.0.0(channel oh-3.47-dev,Dart 3.13.3)
DevEco Studio26.0.0(本机 SDK API 26)
编译 SDK6.1.1(24)(compatibleSdkVersion / targetSdkVersion)
真机华为 TLR-AL00,HarmonyOS 6.1.0.145(C00E120R3P5),API 24

鸿蒙技术点:compatibleSdkVersion 与 API Level 对应关系

example/ohos/build-profile.json5 中的关键字段:

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

在这里插入图片描述

6.1.1(24) 表示 HarmonyOS 6.1.1、API 24(注意保留括号旧格式)。系统会校验应用声明的兼容版本不得高于设备 API,否则安装时报"此应用暂不支持在当前设备安装"。本机 DevEco SDK 为 API 26,向下兼容 API 24 的目标设备。

两点提醒:

  1. 需要 ohos 版 Flutter SDK(本文使用 3.47.7-ohos-1.0.0),普通官方 Flutter 无法编译出鸿蒙产物;
  2. 真机安装报错大多与 compatibleSdkVersion 设置过高有关,与插件本身无关,见 FAQ Q4。

四、引入依赖

在工程 pubspec.yaml 中添加 git 依赖:

...

dependencies:
  dynamic_system_colors:
    git:
      url: https://atomgit.com/CPF-Flutter/fluttertpc_flutter_dynamic_system_colors
      # ref: 1.9.0-ohos-1.0.0-beta.1
      ref: TAG  # 请根据下方TAG版本对应表选择TAG
...

执行命令:

flutter pub get

TAG 版本对应表

Flutter 框架版本TAG分支
3.471.9.0-ohos-1.0.0-beta.1br_v1.9.0_ohos

TAG 命名规则:原库版本-ohos-版本号。原库 1.9.0 对应 1.9.0-ohos-1.0.0-beta.1(分支 br_v1.9.0_ohos)。

依赖树方面,本库只依赖 Flutter SDK 自带的 material_color_utilities(版本范围 >=0.2.0 <= 0.13.0),无任何第三方包、无原生 SDK 依赖,pub get 秒完成。


五、代码接入

5.1 导入库

import 'package:dynamic_system_colors/dynamic_system_colors.dart';

一行导入即可使用全部 API。注意包名是 dynamic_system_colors(不是目录名 flutter_dynamic_system_colors,详见 FAQ Q5)。

5.2 DynamicColorBuilder:一行构件接入

最省事的接入方式——用 DynamicColorBuilder 包住 MaterialApp:

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

const _brandBlue = Color(0xFF1E88E5); // 品牌回退色

class CompleteExample extends StatelessWidget {
  const CompleteExample({super.key});

  
  Widget build(BuildContext context) {
    return DynamicColorBuilder(
      builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
        ColorScheme lightColorScheme;
        ColorScheme darkColorScheme;

        if (lightDynamic != null && darkDynamic != null) {
          // Android S+ / 支持动态取色的平台:直接用动态配色
          // (推荐)对 scheme 内置的语义色做协调
          lightColorScheme = lightDynamic.harmonized();
          // (可选)用品牌色覆盖动态配色中的某些槽位
          lightColorScheme = lightColorScheme.copyWith(secondary: _brandBlue);

          darkColorScheme = darkDynamic.harmonized();
          darkColorScheme = darkColorScheme.copyWith(secondary: _brandBlue);
        } else {
          // 不支持动态取色(含 OpenHarmony):回退到种子色方案
          lightColorScheme = ColorScheme.fromSeed(seedColor: _brandBlue);
          darkColorScheme = ColorScheme.fromSeed(
            seedColor: _brandBlue,
            brightness: Brightness.dark,
          );
        }

        return MaterialApp(
          theme: ThemeData(colorScheme: lightColorScheme),
          darkTheme: ThemeData(colorScheme: darkColorScheme),
          home: const Home(),
        );
      },
    );
  }
}

代码逐段分析:DynamicColorBuilder 的三次探测

initState 中依次尝试三个来源,任一命中即 setState 更新并提前返回:

  1. getColorSchemes() —— Android U+ 系统直出的明暗两套方案;
  2. getCorePalette() —— Android S+ 调色板,命中后由 Dart 层调 toColorScheme() 转换;
  3. getAccentColor() —— 桌面端强调色,命中后用 ColorScheme.fromSeed() 播种。

三次都拿到 null(OHOS 即此情况)时打印
dynamic_color: Dynamic color not detected on this device.,
lightDynamic / darkDynamic 保持 null,交由业务方回退。
每一步都有 if (!mounted) return; 守卫,避免异步返回后组件已销毁的崩溃。

请添加图片描述

5.3 直取调色板:getCorePalette

想自己掌控颜色映射过程时,直接调静态方法:

FutureBuilder<CorePalette?>(
  future: DynamicColorPlugin.getCorePalette(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      final corePalette = snapshot.data;
      return ColoredSquare(
        // Android S+ 设备上取动态 primary 的 40 号色调;
        // 否则回退到 40 号色调的橙色
        Color(corePalette?.primary.get(40) ?? 0xFFFB8C00),
        corePalette != null
            ? 'corePalette.primary.get(40)'
            : 'Color(0xFFFB8C00)',
      );
    } else {
      return const CircularProgressIndicator();
    }
  },
)

代码逐段分析:CorePalette 的结构

CorePalette 内含五个 TonalPalette(primary / secondary / tertiary / neutral / neutralVariant),每个调色板提供 get(tone) 按色调号(0~100)取色。取色结果是 ARGB int,用 Color(...) 包一层即可。示例 App 的"色板预览"卡片就是用它把五行调色板 × TonalPalette.commonTones 逐格画出来的。

请添加图片描述

5.4 色板转配色:toColorScheme

拿到 CorePalette 后,一个扩展方法转出完整 ColorScheme:

final lightScheme = corePalette.toColorScheme(); // 默认 Brightness.light
final darkScheme = corePalette.toColorScheme(brightness: Brightness.dark);

代码逐段分析:_toLightColorScheme 的色调映射

明色方案按 Material 3 规范逐槽位映射:primary 取 primary 调色板 40 号、onPrimary 取 100 号、primaryContainer 取 90 号、onPrimaryContainer 取 10 号;暗色方案则换成 80/20/30/90。surfaceContainer 系列取 87/92/94/96 等非常见色调,这就轮到下面的 _guessTone 出场。

请添加图片描述

5.5 配色协调:harmonized 与 harmonizeWith

自定义颜色(尤其是品牌色、语义警示色)与动态主色直接混用容易"打架",协调能力让它们在色相、饱和度上相互靠拢:

// 单个颜色向 primary 协调
final dangerHarmonized = Colors.red.harmonizeWith(scheme.primary);

// 整个 scheme 的 error 系列向 primary 协调
final harmonized = scheme.harmonized();

harmonized() 的实现(Dart 层纯计算,各平台一致):

ColorScheme harmonized() {
  return copyWith(
    error: _harmonizeWithPrimary(error),
    onError: _harmonizeWithPrimary(onError),
    errorContainer: _harmonizeWithPrimary(errorContainer),
    onErrorContainer: _harmonizeWithPrimary(onErrorContainer),
  );
}

代码逐段分析:Blend.harmonize

底层调 material_color_utilities 的 Blend.harmonize(from, to):把 from 的色相向 to 拉近(保留明度层级),红色 Colors.red 协调后会向主题蓝微微偏移成柔和的玫红。示例 App 里 harmonizeWith(primary) 色块展示的就是这一效果。有自定义颜色的 App 可以参照文档实现 ThemeExtension 子类的 harmonized(),把自定义色也纳入协调。

请添加图片描述

5.6 鸿蒙上的回退策略

OpenHarmony 上三个接口都返回 null,推荐的处理套路:

DynamicColorBuilder(
  builder: (lightDynamic, darkDynamic) {
    // OHOS 上 lightDynamic / darkDynamic 均为 null
    final light = lightDynamic ?? ColorScheme.fromSeed(seedColor: brandColor);
    final dark = darkDynamic ??
        ColorScheme.fromSeed(seedColor: brandColor, brightness: Brightness.dark);
    return MaterialApp(
      theme: ThemeData(colorScheme: light),
      darkTheme: ThemeData(colorScheme: dark),
      home: const HomePage(),
    );
  },
)

更进一步,可以用系统图片取色模拟"壁纸动态色":ColorScheme.fromImageProvider() 从壁纸/图片提取种子色,再接 harmonized(),即可在鸿蒙上得到与 Android Material You 高度一致的视觉语言。

请添加图片描述

5.7 实战:品牌色与动态色的融合

一个典型的"既要品牌又要动态"的组装方式:

class ThemedApp extends StatelessWidget {
  const ThemedApp({super.key});

  
  Widget build(BuildContext context) {
    return DynamicColorBuilder(
      builder: (lightDynamic, darkDynamic) {
        final seed = brandBlue;
        // 动态色优先,品牌色兜底
        final light = (lightDynamic ?? ColorScheme.fromSeed(seedColor: seed))
            .harmonized()
            .copyWith(secondary: seed); // secondary 保留品牌色
        final dark =
            (darkDynamic ??
                    ColorScheme.fromSeed(
                        seedColor: seed, brightness: Brightness.dark))
                .harmonized()
                .copyWith(secondary: seed);
        return MaterialApp(theme: ThemeData(colorScheme: light), darkTheme: ThemeData(colorScheme: dark), home: const HomePage());
      },
    );
  }
}

要点三条:动态色优先、种子色兜底、品牌色定点覆盖——三类来源互为犄角,任何平台都不空窗。


六、运行与验证

Demo Bundle Name:io.material.plugins.dynamic_system_colors_example;设备:华为 TLR-AL00,HarmonyOS 6.1.0.145(API 24)。打包产物 entry-default-signed.hap(约 145 MB,debug 签名)。

6.1 验证一:安装与启动

hdc install -r entry-default-signed.hap
# [Info]App install path:...entry-default-signed.hap msg:install bundle successfully.

hdc shell aa start -a EntryAbility -b io.material.plugins.dynamic_system_colors_example
# start ability successfully.

启动后即见第一节截图的演示页。

6.2 验证二:插件注册日志

hdc shell hilog | grep DynamicColorPlugin
10-20 11:42:43.627 W A000FF/...Flutter: FlutterEngineCxnRegistry --> Adding plugin: DynamicColorPlugin
10-20 11:42:43.627 I A00000/...DynamicColorPlugin: attached to engine, channel [io.material.plugins/dynamic_color] registered

插件在引擎挂载时完成注册,通道名与 Dart 层完全一致。

6.3 验证三:三个接口的 null 行为

DynamicColorBuilder 在 initState 里依次探测,日志如下:

10-20 11:42:45.495 D ...DynamicColorPlugin: onMethodCall: getColorSchemes
10-20 11:42:45.495 I ...DynamicColorPlugin: getColorSchemes: OpenHarmony has no Material You equivalent, returning null
10-20 11:42:45.516 D ...DynamicColorPlugin: onMethodCall: getCorePalette
10-20 11:42:45.516 I ...DynamicColorPlugin: getCorePalette: OpenHarmony has no Material You equivalent, returning null
10-20 11:42:45.517 D ...DynamicColorPlugin: onMethodCall: getAccentColor
10-20 11:42:45.517 I ...DynamicColorPlugin: getAccentColor: OpenHarmony has no system accent color equivalent, returning null

三次调用全部返回 null,没有抛任何 PlatformException——这正是 OptionalMethodChannel 的 null 语义在起作用(见第七节)。

6.4 验证四:Dart 层回退分支

10-20 11:42:45.523 W ...flutter settings log message: dynamic_color: Dynamic color not detected on this device.

Dart 层确认"本设备无动态取色",DynamicColorBuilder 卡片显示回退配色,harmonizeWith 色块正常演示协调效果,色板预览用 CorePalette.of(0xFF1E88E5) 完整渲染——纯 Dart 能力在鸿蒙上工作正常。

实测结论表

#验证点结果
1HAP 真机安装成功(compatibleSdkVersion 6.1.1(24))通过
2插件注册与通道挂载通过
3getColorSchemes / getCorePalette / getAccentColor 返回 null 且不抛异常通过
4Dart 层回退分支触发通过
5色板预览(CorePalette.of + get(tone))渲染正常通过
6harmonizeWith 配色协调正常通过
7全程无需申请任何敏感权限通过

七、工作原理

调用链路

DynamicColorBuilder (initState)
  ├─ getColorSchemes() ── OptionalMethodChannel('io.material.plugins/dynamic_color')
  │     ├─ Android U+: 系统直出 ColorSchemes(List<int> 打平 43 色 × 明暗两套)
  │     └─ OHOS: ArkTS 插件 result.success(null)
  ├─ getCorePalette() ── 同一通道
  │     ├─ Android S+: 系统直出 CorePalette(List<int> 打平)
  │     └─ OHOS: result.success(null)
  ├─ getAccentColor() ── 同一通道
  │     ├─ macOS/Windows/Linux: 系统强调色 ARGB int
  │     └─ OHOS: result.success(null)
  └─ 三者皆 null → lightDynamic / darkDynamic = null → 业务方回退

拿到 CorePalette 后(各平台一致的纯 Dart 路径)
  └─ corePalette.toColorScheme(brightness)
        └─ _toLightColorScheme / _toDarkColorScheme(逐槽位映射色调)
              └─ 常见色调直接 get(tone)
              └─ 非常见色调 → _guessTone() HCT 反推

代码逐段分析:OptionalMethodChannel 的 null 语义

Dart 层声明的是:

static const channel = OptionalMethodChannel('io.material.plugins/dynamic_color');

OptionalMethodChannel 的语义是"通道不存在或方法未实现时返回 null,而不是抛 MissingPluginException"。同时 getCorePalette() 等三处在取到 null 时都走 null 分支:

final result = await channel.invokeMethod(corePaletteMethodName);
return result == null ? null : CorePalette.fromList(result.toList());

也就是说,返回 null 是 Dart 层既定的、被正常处理的合法返回值。ArkTS 插件对三个方法统一 result.success(null),与"在 iOS、Web 等不支持动态取色的平台上的行为完全一致",调用方零额外异常路径。

OHOS 插件实现

ArkTS 侧插件(ohos/src/main/ets/components/plugin/DynamicColorPlugin.ets)核心逻辑:

const CHANNEL_NAME: string = 'io.material.plugins/dynamic_color';

export default class DynamicColorPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    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);
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    // 置空 handler,避免引擎销毁后插件实例仍被通道持有而无法释放
    this.channel?.setMethodCallHandler(null);
    this.channel = null;
  }

  private getCorePalette(result: MethodResult): void {
    // OpenHarmony 无 Material You 等价 API,返回 null 与 Dart 层
    // OptionalMethodChannel 的 null 语义一致
    result.success(null);
  }
  // getColorSchemes / getAccentColor 同理
}

说明:这是一个"诚实桩"——不做任何伪造,不编造颜色数据;返回 null 让 Dart 层按既定语义走回退。注意 onDetachedFromEngine 里把 handler 置空,避免引擎销毁后插件实例被通道持有而无法释放。

插件的注册由工具链自动完成:pubspec.yaml 中的

flutter:
  plugin:
    platforms:
      ohos:
        pluginClass: DynamicColorPlugin

声明会让 ohos 版 Flutter 工具链生成 GeneratedPluginRegistrant,在 EntryAbility.configureFlutterEngine 中 registerWith(flutterEngine) 完成注册。

鸿蒙技术点:_guessTone 色调反推

TonalPalette 只预生成 0、10、20、…、100 等常见色调,但 Material 3 的 surfaceContainerLow(96)、surfaceContainer(94)、surfaceContainerHigh(92)、surfaceDim(87)等槽位需要中间色调。库里的 _guessTone 做了一件聪明事:遍历调色板已有颜色,用 HCT 色彩空间反推出"当初生成这组颜色的色相与最大色度",再用 Hct.from(hue, chroma, tone) 生成目标色调:

Color _guessTone(TonalPalette palette, double tone) {
  var bestHue = 0.0, bestChroma = 0.0;
  for (final argb in palette.asList) {
    final hct = Hct.fromInt(argb);
    if (hct.tone == tone) return Color(hct.toInt());
    if (hct.tone > 98.0) continue; // 接近白色时色度可能失真,跳过
    if (hct.chroma > bestChroma) { bestHue = hct.hue; bestChroma = hct.chroma; }
  }
  return Color(Hct.from(bestHue, bestChroma, tone).toInt());
}

这是 material_color_utilities 0.12 同款逻辑的移植,待 Flutter 升级到内置该能力后可移除。由于是纯 Dart 计算,鸿蒙上的表现与 Android 完全一致。

以下是操作的视屏,可以参考一下:

Example 启动授权 Example 启动授权 Example 启动授权 Example 启动授权


八、常见问题

Q1:getCorePalette 和 getColorSchemes 有什么区别?

getCorePalette() 返回原始调色板(Android S+),需要自己调 toColorScheme() 转换,适合想自定义映射的场景;getColorSchemes() 返回系统直接给出的完整明暗两套方案(Android U+),开箱即用。在鸿蒙上两者都返回 null。

Q2:鸿蒙上调用这些接口会抛异常吗?

不会。Dart 层用的是 OptionalMethodChannel,"平台不支持"的表达就是返回 null;ArkTS 插件对三个方法统一 result.success(null),与 iOS/Web 上"未实现即 null"的行为对齐。异常路径只在通道本身出错时才会走到。

Q3:鸿蒙上怎么实现"随壁纸变色"的效果?

系统没有 Material You 等价 API,原生动态色拿不到。替代方案:用 ColorScheme.fromImageProvider() 从壁纸/用户图片提取种子色生成方案,再叠加 harmonized() 协调,即可获得与 Material You 高度一致的视觉语言。

Q4:真机安装报"此应用暂不支持在当前设备安装"?

检查 example/ohos/build-profile.json5 的 compatibleSdkVersion,必须不高于目标设备的 API Level(如 API 24 设备对应 6.1.1(24),保留括号旧格式)。这与插件无关。

Q5:包名为什么是 dynamic_system_colors,目录却叫 flutter_dynamic_system_colors?

仓库目录名是 flutter_dynamic_system_colors,而 pubspec.yaml 的 name 字段是 dynamic_system_colors(原库改名历史所致)。import 用的是包名;OpenHarmony 侧 HAR 的包名与模块名也使用 pubspec 的 name。

Q6:为什么通道名叫 io.material.plugins/dynamic_color?

通道名是原库(上游 dynamic_color 的 Android 原生实现)定下的契约,package:io.material.plugins.dynamic_color 是历史包路径。为了与 Android 原生实现及各平台保持一致,鸿蒙适配版严格沿用该通道名,这也是"通道名 ≠ 包名"的原因。


九、结语

回顾一下:git TAG 引入依赖 → DynamicColorBuilder 包住 MaterialApp(或手动调 DynamicColorPlugin 三个静态方法)→ toColorScheme() / harmonized() / harmonizeWith() 组合使用。

核心结论:

  • OpenHarmony 无 Material You 等价 API,插件诚实返回 null,Dart 层按 OptionalMethodChannel 语义优雅回退,不伪造数据、不抛异常;
  • toColorScheme()、harmonized()、harmonizeWith() 等纯 Dart 能力在鸿蒙上完整可用,配合 ColorScheme.fromSeed() / fromImageProvider() 可复现 Material You 视觉语言;
  • 零权限、零第三方依赖,HAR 不申请任何权限,接入成本接近于零;
  • 已在华为真机(HarmonyOS 6.1.0.145 / API 24)完整实测,七个验证点全部通过。

欢迎提 Issue / PR 共建。


相关链接

  • 鸿蒙适配版仓库:https://atomgit.com/CPF-Flutter/fluttertpc_flutter_dynamic_system_colors(TAG 1.9.0-ohos-1.0.0-beta.1)
  • 原库:https://github.com/hasali19/flutter_dynamic_system_colors
  • Material 3 自定义颜色与协调:https://m3.material.io/styles/color/the-color-system/custom-colors#harmonization
  • Flutter 鸿蒙化开发环境:https://atomgit.com/oh-flutter/flutter_flutter

附:dynamic_system_colors 核心能力对照表

能力维度实现方式鸿蒙表现跨平台一致性
getColorSchemes(系统直出方案)原生通道返回 nullAndroid U+ 独有
getCorePalette(系统调色板)原生通道返回 nullAndroid S+ 独有
getAccentColor(系统强调色)原生通道返回 nullmacOS / Windows / Linux 独有
CorePalette.toColorScheme()纯 Dart(含 HCT 反推)✅ 完整可用五端一致
ColorScheme.harmonized()纯 Dart(Blend.harmonize)✅ 完整可用五端一致
Color.harmonizeWith()纯 Dart(Blend.harmonize)✅ 完整可用五端一致
DynamicColorBuilder 三次探测Dart 层串联✅ 完整可用(全 null → 回退)五端一致
null 回退语义OptionalMethodChannel✅ 不抛异常五端一致
插件注册GeneratedPluginRegistrant 自动生成✅ 引擎挂载时注册各端一致
权限要求不访问系统能力零权限各端一致
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐