实战:用 ColorPicker 做"主题色自适应"

你有没有想过,为什么有些 APP 能根据用户设置的壁纸自动调整主题色?比如壁纸是蓝色的海洋,APP 的按钮和标题栏就变成蓝色;壁纸是绿色的森林,主题色就变成绿色。

这背后的原理很简单:从图片中提取主要颜色,然后用这个颜色作为主题色。HarmonyOS 的 ColorPicker 就是干这个的。

主题色自适应整体流程

下面是主题色自适应的完整工作流程:

用户设置壁纸/专辑封面

加载图片资源

创建PixelMap

创建ColorPicker取色器

分析图片像素

提取主色调getMainColor

获取RGB颜色值

应用到UI组件

按钮背景色

文字颜色

渐变背景

场景设定

假设你在做一个音乐 APP,用户可以设置专辑封面作为背景。你希望 APP 的主题色能自动匹配专辑封面的主色调——这样每次换专辑,整个 APP 的风格都会跟着变。

第一步:加载图片

import { image } from '@kit.ImageKit';
import { effectKit } from '@kit.ArkGraphics2D';
import { common } from '@kit.AbilityKit';

导入需要的模块。

async aboutToAppear() {
  const context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  const fileData: Uint8Array = await context.resourceManager.getRawFileContent('album.jpg');
  const buffer: ArrayBuffer = fileData.buffer.slice(0);

  let imageSource = image.createImageSource(buffer);
  this.albumCover = await imageSource.createPixelMap();

rawfile 加载专辑封面图片,转成 PixelMap

  // 提取主题色
  this.extractThemeColor(buffer);
}

调用提取主题色的方法。

第二步:提取主色调

async extractThemeColor(buffer: ArrayBuffer) {
  let imageSource = image.createImageSource(buffer);
  let pixelMap = await imageSource.createPixelMap();

先创建 PixelMap

  // 创建 ColorPicker
  let colorPicker = effectKit.createColorPicker(pixelMap);

createColorPicker 创建一个取色器,传入图片。这个取色器会分析图片的所有像素,找出主要颜色。

  // 获取主色调
  let mainColor = colorPicker.getMainColor();

getMainColor() 返回图片中最主要的颜色。这个颜色是一个 Color 对象,包含 redgreenbluealpha 四个分量,每个分量是 0-255 的整数。

  // 把颜色值转成 CSS 格式
  let r = Math.round(mainColor.red);
  let g = Math.round(mainColor.green);
  let b = Math.round(mainColor.blue);
  this.themeColor = `rgb(${r}, ${g}, ${b})`;

  console.info(`主题色: ${this.themeColor}`);
}

Color 对象转成 CSS 格式的字符串,方便在 UI 里使用。

第三步:应用主题色

拿到主色调后,你可以把它用在任何 UI 组件上:

// 按钮背景色
Button('播放')
  .backgroundColor(this.themeColor)

// 文字颜色
Text('歌曲 1')
  .fontColor(this.themeColor)

// 带透明度的变体
Column()
  .backgroundColor(this.themeColor)
  .opacity(0.6)  // 60% 不透明度

完整的页面代码

@Entry
@Component
struct ThemeColorApp {
  @State themeColor: string = '#666666';  // 默认灰色主题
  @State albumCover: image.PixelMap | null = null;
  @State albumName: string = '点击选择专辑';

定义状态变量:主题色、专辑封面、专辑名。

  async aboutToAppear() {
    const context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    const fileData: Uint8Array = await context.resourceManager.getRawFileContent('album.jpg');
    const buffer: ArrayBuffer = fileData.buffer.slice(0);

    let imageSource = image.createImageSource(buffer);
    this.albumCover = await imageSource.createPixelMap();

    this.extractThemeColor(buffer);
  }

加载图片并提取主题色。

  async extractThemeColor(buffer: ArrayBuffer) {
    let imageSource = image.createImageSource(buffer);
    let pixelMap = await imageSource.createPixelMap();

    let colorPicker = effectKit.createColorPicker(pixelMap);
    if (colorPicker != null) {
      let mainColor = colorPicker.getMainColor();

      let r = Math.round(mainColor.red);
      let g = Math.round(mainColor.green);
      let b = Math.round(mainColor.blue);
      this.themeColor = `rgb(${r}, ${g}, ${b})`;
    }
  }

提取主题色的核心逻辑。

  build() {
    Column() {
      // 专辑封面
      Text(this.albumName)
        .fontSize(18)
        .fontColor('#ffffff')
        .margin({ top: 40 })

      if (this.albumCover) {
        Image(this.albumCover)
          .width(200)
          .height(200)
          .margin({ top: 20 })
          .borderRadius(10)
      }

显示专辑封面。

      // 主题色预览
      Text(`主题色: ${this.themeColor}`)
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 20 })

显示主题色的值。

      // 主题色展示
      Row() {
        Column()
          .width(60)
          .height(60)
          .backgroundColor(this.themeColor)
          .borderRadius(8)
          .margin(10)

        Column()
          .width(60)
          .height(60)
          .backgroundColor(this.themeColor)
          .opacity(0.6)
          .borderRadius(8)
          .margin(10)

        Column()
          .width(60)
          .height(60)
          .backgroundColor(this.themeColor)
          .opacity(0.3)
          .borderRadius(8)
          .margin(10)
      }
      .margin({ top: 20 })

展示三种不同透明度的主题色变体——100%、60%、30%。这样你可以看到主题色在不同场景下的效果。

      // 模拟 APP 元素
      Button('播放')
        .width(200)
        .height(50)
        .backgroundColor(this.themeColor)
        .fontColor('#ffffff')
        .margin({ top: 30 })

      Row() {
        Text('歌曲 1')
          .fontSize(16)
          .fontColor(this.themeColor)
          .margin(10)

        Text('歌曲 2')
          .fontSize(16)
          .fontColor('#666666')
          .margin(10)

        Text('歌曲 3')
          .fontSize(16)
          .fontColor('#666666')
          .margin(10)
      }
      .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#1a1a1a')
  }
}

模拟一个音乐 APP 的界面:播放按钮用主题色背景,第一个歌曲名用主题色高亮。

多色主题提取流程

ColorPicker支持提取多个主要颜色,用于更丰富的UI效果:

单一主色

多色方案

加载图片

创建ColorPicker

选择提取方式

getMainColor

getTopProportionColors

获取最主要颜色

获取前N个主要颜色

按占比排序

颜色1: 占比最高

颜色2: 次高

颜色3: 第三

应用于单一主题色

应用于渐变背景

应用于辅助元素

应用于装饰元素

进阶:用 getTopProportionColors 做多色主题

如果你想做得更丰富,可以用 getTopProportionColors 提取多个颜色:

async extractMultipleColors(buffer: ArrayBuffer) {
  let imageSource = image.createImageSource(buffer);
  let pixelMap = await imageSource.createPixelMap();

  let colorPicker = effectKit.createColorPicker(pixelMap);
  if (colorPicker != null) {
    // 提取占比最多的 5 个颜色
    let topColors = colorPicker.getTopProportionColors(5);

getTopProportionColors(5) 返回一个数组,包含占比最多的 5 个颜色。

    for (let i = 0; i < topColors.length; i++) {
      let color = topColors[i].color;
      let proportion = topColors[i].proportion;
      console.info(`颜色 ${i + 1}: rgb(${color.red}, ${color.green}, ${color.blue}), 占比: ${proportion}`);
    }
  }
}

每个元素有 color(颜色对象)和 proportion(占比,0-1 之间)。

你可以用这些颜色做渐变背景、多色图标等更丰富的 UI 效果。

两种取色方法对比

getMainColor()getLargestProportionColor() 有不同特点:

getMainColor

getLargestProportionColor

ColorPicker取色方法

选择方法

返回最主要颜色

返回占比最大颜色

考虑视觉重心

更符合人眼感知

仅考虑像素数量

可能忽略醒目颜色

适合主题色场景

适合统计分析

  • getMainColor():返回图片中"最主要"的颜色,通常是面积最大、最醒目的颜色
  • getLargestProportionColor():返回占比最大的颜色

对于主题色场景,getMainColor() 更合适——它能抓住图片的"视觉重心",而不仅仅是面积最大的颜色。

小结

主题色自适应的核心流程:

  1. 加载图片 → image.createPixelMap()
  2. 创建取色器 → effectKit.createColorPicker()
  3. 提取主色 → getMainColor()
  4. 应用到 UI → backgroundColor() / fontColor()

就这么简单。ColorPicker 帮你从图片里"读懂"颜色,你的 APP 就能跟着图片变色了。

Logo

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

更多推荐