前言

图标组件在界面里看起来很小,实际承担的任务并不少。状态切换要清楚,禁用态要直观,重点入口要有层次,品牌图标还得有足够的辨识度。鸿蒙 6 在 API 20 这条线上,把 SymbolGlyph 的表现力往前推了一步。symbolShadow 让图标可以直接带阴影,ReplaceEffectType.CROSS_FADE 让状态替换更顺,斜杠遮罩效果可以更自然地表达禁用态,shaderStyle 则把渐变填充接进了图标渲染链路。

这几项能力放在一起,解决的是四类高频问题。图标切换不够顺,重点按钮层次不够,禁用态表达太弱,品牌化图标只能靠纯色硬顶。API 20 之后,这些能力都可以直接落在 SymbolGlyph 本身,不需要继续靠外层容器、自定义绘制或手写动画去拼。

一、先把这四项能力分清楚

先看快速替换动效。ReplaceEffectType.CROSS_FADE 的作用很直接,旧图标淡出的同时,新图标淡入,切换过程连续,不会出现生硬跳变。点赞、收藏、播放模式切换、勾选状态切换,这类场景最适合先接这个能力。CROSS_FADE 已经进入 ArkUI 6.0.0(20) 的 SymbolGlyph API 变更范围。

再看阴影。symbolShadow 从 API 20 开始支持,图标本身就能直接挂阴影参数。半径、颜色、偏移这些属性都交给 ShadowOptions,不需要再包一层 Stack 或自己画影子。重点入口、悬浮按钮、深色背景上的主图标,都很适合用它来做层次感。

第三项是禁用态。斜杠遮罩效果已经进了 SymbolGlyph 的替换效果体系里,作用很明确,就是在图标上叠一层对角斜杠,用来表达当前功能不可用或暂时不可操作。这类表达比单纯把图标变灰更直接,用户理解成本也更低。

第四项是渐变填充。shaderStyle 已经接进 SymbolGlyph,可以直接给图标挂 LinearGradientStyleRadialGradientStyle 或纯色 shader。这样一来,品牌图标、活动页图标、会员页入口图标终于不用继续卡在单色表达里。

二、快速替换和阴影

这两项能力最适合先放进高频交互。点赞、收藏、勾选、模式切换,这些地方的图标变化最频繁,用户也最容易感知到细节差异。状态切换一旦顺下来,页面质感会立刻提升。

下面这个例子适合直接做收藏按钮的基础实现。状态切换时,通过 triggerValue 触发 CROSS_FADE,逻辑清楚,也方便后面接业务状态。

@Entry
@Component
struct FavoriteSymbolDemo {
  @State active: boolean = false
  @State triggerValue: number = 0

  build() {
    Column({ space: 20 }) {
      SymbolGlyph(this.active ? $r('sys.symbol.heart_fill') : $r('sys.symbol.heart'))
        .fontSize(32)
        .fontColor(this.active ? '#FF4D4F' : '#222222')
        .symbolEffect(
          new ReplaceSymbolEffect(EffectScope.WHOLE, ReplaceEffectType.CROSS_FADE),
          this.triggerValue
        )

      Button('切换收藏状态')
        .onClick(() => {
          this.active = !this.active
          this.triggerValue++
        })
    }
    .width('100%')
    .padding(24)
  }
}

阴影更适合处理层次感。普通状态下影子略深一点,按下时影子收一点,用户会更容易感知到按钮的空间关系。symbolShadow 进了组件本身之后,这件事就不需要额外容器参与了。

@Entry
@Component
struct ShadowSymbolDemo {
  @State pressed: boolean = false

  private normalShadow: ShadowOptions = {
    radius: 8,
    color: '#33000000',
    offsetX: 2,
    offsetY: 2
  }

  private pressedShadow: ShadowOptions = {
    radius: 4,
    color: '#22000000',
    offsetX: 1,
    offsetY: 1
  }

  build() {
    Column({ space: 20 }) {
      SymbolGlyph($r('sys.symbol.plus_circle'))
        .fontSize(56)
        .symbolShadow(this.pressed ? this.pressedShadow : this.normalShadow)
        .gesture(
          LongPressGesture({ repeat: false })
            .onAction(() => {
              this.pressed = true
            })
            .onActionEnd(() => {
              this.pressed = false
            })
        )

      Text('长按图标观察阴影变化')
        .fontSize(14)
        .fontColor('#666666')
    }
    .width('100%')
    .padding(24)
  }
}

这类用法很适合首页核心入口、工具栏主按钮和带主操作含义的图标。影子轻一点,焦点会更集中,页面层次也更容易立住。

三、禁用态和渐变

禁用态最怕两件事,一是看不出为什么点不了,二是做得太粗糙,整套交互都显得发虚。斜杠遮罩这种表达方式,优点就在于状态语义很直接。它不是单纯把图标压灰,而是明确告诉用户,这个入口目前不允许用。

下面这个写法,适合做权限受限、功能未解锁、会员未开通这类场景。状态一切,图标直接走斜杠遮罩替换。

@Entry
@Component
struct DisabledStateDemo {
  @State enabled: boolean = true
  @State triggerValue: number = 0

  build() {
    Column({ space: 20 }) {
      SymbolGlyph($r('sys.symbol.eye'))
        .fontSize(40)
        .fontColor(this.enabled ? '#007DFF' : '#666666')
        .symbolEffect(
          new ReplaceSymbolEffect(EffectScope.WHOLE, ReplaceEffectType.SLASH_OVERLAY),
          this.triggerValue
        )

      Button(this.enabled ? '切换为禁用态' : '恢复可用态')
        .onClick(() => {
          this.enabled = !this.enabled
          this.triggerValue++
        })
    }
    .width('100%')
    .padding(24)
  }
}

渐变填充更适合放在品牌入口、活动页图标、会员权益页和视觉强调区。普通设置页和工具页未必需要它,大量使用反而会让焦点变乱。shaderStyle 现在已经支持接进 SymbolGlyph,直接给图标挂 LinearGradientStyleRadialGradientStyle 就行。

@Entry
@Component
struct GradientSymbolDemo {
  private brandGradient: LinearGradientOptions = {
    angle: 135,
    colors: [
      ['#FF4D4F', 0.0],
      ['#FF7A45', 0.5],
      ['#FFC53D', 1.0]
    ]
  }

  private glowGradient: RadialGradientOptions = {
    center: ['50%', '50%'],
    radius: '50%',
    colors: [
      ['#FFFFFF', 0.0],
      ['#6A5ACD', 1.0]
    ],
    repeating: false
  }

  build() {
    Column({ space: 30 }) {
      SymbolGlyph($r('sys.symbol.star'))
        .fontSize(72)
        .shaderStyle([new LinearGradientStyle(this.brandGradient)])

      SymbolGlyph($r('sys.symbol.sparkles'))
        .fontSize(72)
        .shaderStyle([new RadialGradientStyle(this.glowGradient)])
    }
    .width('100%')
    .padding(24)
  }
}

线性渐变适合有方向感的图标,径向渐变更适合做光感和聚焦。品牌页和活动页这类视觉强场景,线性渐变通常更稳;强调光晕、奖章、能量感时,径向渐变会更合适。(华为开发者官网)

四、最佳实践

这四项能力最适合按场景拆开用。高频交互先接 CROSS_FADE,主操作入口优先接 symbolShadow,禁用态单独走斜杠遮罩,品牌和活动图标再用 shaderStyle 做强调。这样分层处理之后,图标系统会很快从能用变成好用。

项目里更稳的做法,是先把几类常用图标组件封出来。收藏按钮一套写法,主操作按钮一套写法,禁用态一套写法,品牌图标再单独一套。状态和视觉规则一旦收进组件层,业务页面就不会到处散落不同写法,后面统一调整风格也会轻很多。

@Component
export struct AppFavoriteSymbol {
  @Prop active: boolean
  @Prop triggerValue: number

  build() {
    SymbolGlyph(this.active ? $r('sys.symbol.heart_fill') : $r('sys.symbol.heart'))
      .fontSize(28)
      .fontColor(this.active ? '#FF4D4F' : '#222222')
      .symbolEffect(
        new ReplaceSymbolEffect(EffectScope.WHOLE, ReplaceEffectType.CROSS_FADE),
        this.triggerValue
      )
  }
}

@Component
export struct AppPrimarySymbol {
  @Prop pressed: boolean

  private normalShadow: ShadowOptions = {
    radius: 6,
    color: '#26000000',
    offsetX: 2,
    offsetY: 2
  }

  private pressedShadow: ShadowOptions = {
    radius: 3,
    color: '#1A000000',
    offsetX: 1,
    offsetY: 1
  }

  build() {
    SymbolGlyph($r('sys.symbol.plus_circle'))
      .fontSize(36)
      .symbolShadow(this.pressed ? this.pressedShadow : this.normalShadow)
  }
}

这类封法的价值很现实。图标的动态效果一旦散在业务页面里,后面统一改风格会非常痛苦。组件层先收住,后面的维护成本会低很多。

总结

鸿蒙 6 API 20 给 SymbolGlyph 补进来的这几项能力,真正解决的是图标状态表达和视觉层次的落地问题。CROSS_FADE 让状态切换更顺,斜杠遮罩让禁用态更直观,symbolShadow 把层次感放回了组件本身,shaderStyle 则把渐变填充正式接进了图标系统。

Logo

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

更多推荐