鸿蒙 HarmonyOS 6 | SymbolGlyph动态视觉效果实战
图标这类组件,平时看起来很小,真正影响体验的地方却很多。按钮按下时有没有反馈,状态切换时顺不顺,禁用态是不是一眼能看懂,这些细节都会直接影响用户对应用质感的判断。
前言
图标这类组件,平时看起来很小,真正影响体验的地方却很多。按钮按下时有没有反馈,状态切换时顺不顺,禁用态是不是一眼能看懂,这些细节都会直接影响用户对应用质感的判断。
鸿蒙的 SymbolGlyph 从 API version 11 开始支持,主要用来渲染系统图标小符号。到了 API 20,这个组件补进了几项非常实用的能力,ReplaceEffectType.CROSS_FADE 用来做快速替换动效,SLASH_OVERLAY 用来做禁用态斜杠遮罩,symbolShadow 用来直接给图标加阴影,shaderStyle 则把渐变填充放进了图标渲染链路里。这样一来,很多原来要靠多层布局或自定义绘制才能做出来的效果,现在可以直接落在组件本身。

一、这次升级最值得关注的四项能力
先看快速替换动效。ReplaceEffectType.CROSS_FADE 的效果很直接,旧图标淡出的同时,新图标淡入,切换过程更连续,视觉上不会出现突然跳变。它很适合点赞、收藏、播放模式切换、勾选状态切换这类高频交互。华为开发者文档里对这个枚举的说明也很明确,CROSS_FADE 就是快速替换动效。
再看阴影。symbolShadow 从 API 20 开始可用,作用也很清楚,直接给 SymbolGlyph 增加原生阴影能力。开发时不需要额外包一层 Stack 或自己画影子,图标层次感可以直接交给组件本身处理。这个能力很适合用在重要操作按钮、深色背景上的主图标、需要做轻微悬浮感的入口图标。
第三项是禁用态动效。SLASH_OVERLAY 会在图标上覆盖一层斜杠遮罩,用来表示当前功能不可用或暂时不可操作。这个效果的表达很直接,用户看到后不需要额外理解成本。系统图标文档里对这一点写得很清楚,斜杠遮罩就是典型的禁用或非活动状态表达。
第四项是渐变填充。shaderStyle 在 API 20 这条线接进了 SymbolGlyph,线性渐变、纯色 shader 这些能力都可以直接作用在图标上。品牌色图标、活动页主视觉、需要强调情绪化表达的场景,都可以直接吃到这条能力。
二、快速替换动效和阴影
这两项能力最适合优先接到用户点得最多的地方。收藏按钮、点赞按钮、加购按钮、播放控制按钮,这些都是典型场景。状态变化一旦频繁,图标切换够不够顺,用户会立刻感知到。
下面这个例子就是一个比较稳的写法。点击图标时,用 CROSS_FADE 触发快速替换,同时保留状态变量,后面如果要接业务接口也方便扩展。
@Entry
@Component
struct FavoriteButtonDemo {
@State isFavorite: boolean = false
@State triggerValue: number = 0
build() {
Column({ space: 16 }) {
SymbolGlyph(this.isFavorite ? $r('sys.symbol.heart_fill') : $r('sys.symbol.heart'))
.fontSize(32)
.fontColor(this.isFavorite ? Color.Red : Color.Black)
.symbolEffect(
new ReplaceSymbolEffect(EffectScope.WHOLE, ReplaceEffectType.CROSS_FADE),
this.triggerValue
)
.onClick(() => {
this.isFavorite = !this.isFavorite
this.triggerValue++
})
}
.width('100%')
.padding(24)
}
}
阴影这项能力更适合做层次感和触感。比如一个加号按钮,普通状态下影子稍微重一点,按下时影子收缩,视觉上就会更像一个真实可按压的控件。
@Entry
@Component
struct ShadowButtonDemo {
@State isPressed: 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() {
SymbolGlyph($r('sys.symbol.plus_circle'))
.fontSize(48)
.symbolShadow(this.isPressed ? this.pressedShadow : this.normalShadow)
.gesture(
LongPressGesture({ repeat: false })
.onAction(() => {
this.isPressed = true
})
.onActionEnd(() => {
this.isPressed = false
})
)
}
.width('100%')
.padding(24)
}
}
这类写法很适合放到首页主操作入口和工具栏核心按钮上。层次出来之后,界面会显得更稳,也更容易形成视觉焦点。symbolShadow 既然已经进了组件本身,项目里就没有必要再为图标影子单独搭复杂布局。
三、禁用态和渐变填充,重点在状态表达和品牌化
禁用态最怕两件事。第一件事是表达不清,用户看不出为什么点不了。第二件事是处理太粗糙,整个图标一灰到底,信息量很弱。SLASH_OVERLAY 这类斜杠遮罩就比较适合做这件事,状态语义很强,也更贴近系统图标的表达方式。
下面这个例子适合用在权限受限、会员未解锁、功能暂不可用这类场景里:
@Entry
@Component
struct DisabledSymbolDemo {
@State hasPermission: boolean = false
@State triggerValue: number = 0
build() {
Column({ space: 12 }) {
SymbolGlyph($r('sys.symbol.lock_shield'))
.fontSize(40)
.symbolEffect(
new ReplaceSymbolEffect(EffectScope.WHOLE, ReplaceEffectType.SLASH_OVERLAY),
this.triggerValue
)
.fontColor(this.hasPermission ? '#007DFF' : '#666666')
Button('切换权限状态')
.onClick(() => {
this.hasPermission = !this.hasPermission
this.triggerValue++
})
}
.padding(24)
}
}
渐变填充更适合品牌化和视觉强调。普通工具页和设置页未必需要它,活动页、会员页、奖励页、品牌图标入口就很适合。shaderStyle 这项能力加进来之后,图标终于可以直接承载渐变色,而不是继续依赖外层容器或贴图方案。
@Entry
@Component
struct GradientSymbolDemo {
private brandGradient: LinearGradientOptions = {
angle: 135,
colors: [
['#FF4D4F', 0.0],
['#FF7A45', 0.5],
['#FFC53D', 1.0]
]
}
build() {
Column() {
SymbolGlyph($r('sys.symbol.star'))
.fontSize(64)
.shaderStyle([new LinearGradientStyle(this.brandGradient)])
}
.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
)
}
}
这种封法对项目很重要。图标的动态效果一旦散在各个页面里,后面统一改风格会非常痛苦。提前把通用交互组件收掉,后面维护成本会低很多。
总结
鸿蒙 6 API 20 给 SymbolGlyph 补进来的这四项能力,真正解决的是图标动态效果和状态表达的落地问题。CROSS_FADE 让状态切换更顺,symbolShadow 让图标层次更清楚,SLASH_OVERLAY 让禁用态更容易识别,shaderStyle 则把图标的品牌化表达能力拉高了一个层级。
项目里更稳的接法也很清楚。高频交互先接快速替换,核心入口优先接阴影,禁用态单独做规范,品牌图标再用渐变拉开层级。把这些规则收进组件层,后面的页面开发会顺很多,整体观感也会更统一。
更多推荐



所有评论(0)