HarmonyOS APP开发---“动效君“UI动效素材库App,需要用到这个库
·
HarmonyOS APP开发—"动效君"UI动效素材库App,需要用到这个库
素材库要一屏展示 20 个 Lottie 动画同时播放,还要准确还原 AE 的 Tint、阴影、遮罩特效。一代 Lottie 库在这种场景直接卡死主线程——
@ohos/lottie-turbo的子线程渲染能扛住,但 controller 管理不当会内存泄漏。
📦 仓库地址 |
ohpm install @ohos/lottie-turbo
场景
"动效君"是给设计师和开发者用的动效素材库:
- 素材瀑布流:一屏 12~20 个动画缩略预览,全部同时播放
- 特效分类:按 AE 特效分(着色 Tint、阴影 Drop Shadow、遮罩 Set Matte、文字动画)
- 详情预览:全屏播放,可调速度、逐帧查看、片段播放
- 代码导出:展示该动画在鸿蒙上的接入代码
三个极限要求:
- 多动画同屏:20 个动画同时渲染,主线程方案必然卡死
- 特效还原准:Tint 不生效、阴影位置错,设计师一眼就看出来
- 内存不泄漏:用户浏览几百个素材,controller 管理不当内存持续涨
为什么是 lottie-turbo
| 需求 | @ohos/lottie | @ohos/lottie-turbo |
|---|---|---|
| 20 个动画同屏 | ❌ 主线程卡死 | ✅ 子线程渲染 |
| 多动画加载 | ⚠️ 排队 | ✅ 并行加载 |
| 滑回已看过的 | ❌ 重新解析 | ✅ 内存缓存 |
| 滑出屏幕的 | ⚠️ 继续渲染 | ✅ 自动跳过绘制 |
| 逐帧/片段播放 | ⚠️ | ✅ 完整控制 |
关键实现
controller 生命周期管理(不泄漏的关键)
规则:一个 LottieView 配一个独立 controller。 多个 View 共享同一个 controller 时,销毁只处理单个动画,其余的会泄漏。
import { LottieController, LottieView } from '@ohos/lottie-turbo'
@Entry
@Component
struct AssetLibraryPage {
@State assets: AnimAsset[] = loadAssets()
// ✅ 用 Map 管理:一个素材一个 controller,可精确清理
private controllers: Map<string, LottieController> = new Map()
private getController(id: string): LottieController {
if (!this.controllers.has(id)) {
this.controllers.set(id, new LottieController())
}
return this.controllers.get(id)!
}
// ✅ 页面销毁时统一停止并清理
aboutToDisappear() {
this.controllers.forEach((c, id) => {
try { c.stop() } catch (err) { console.warn(`停止 ${id} 失败`) }
})
this.controllers.clear()
}
build() {
WaterFlow() {
ForEach(this.assets, (asset: AnimAsset) => {
FlowItem() {
Column() {
LottieView({
lottieId: asset.id, // 唯一 id
resource: asset.resource,
loop: true,
autoplay: true,
controller: this.getController(asset.id), // ✅ 独立 controller
})
.width('100%').aspectRatio(1)
Text(asset.name).fontSize(13).margin({ top: 6 }).maxLines(1)
}.padding(8)
}
.onClick(() => router.pushUrl({ url: 'pages/Detail', params: { id: asset.id } }))
}, (a: AnimAsset) => a.id)
}
.columnsTemplate('1fr 1fr 1fr') // 三列,一屏约 12~15 个动画
.columnsGap(8).rowsGap(8)
}
}
为什么这里能扛住 20 个动画同屏:
| 机制 | 作用 |
|---|---|
| 并行加载 | JSON 解析不排队,20 个同时开始 |
| 子线程渲染 | 帧计算不占主线程,主线程只合成 |
| 内存缓存 | 滑回去的动画不重新解析 |
| 不可见跳过绘制 | 滑出屏幕的自动停渲染 |
列表快速滑动的启停优化
private visibleIds: Set<string> = new Set()
onVisibleAreaChange(visibleIds: string[]) {
// 新进入可见区 → 播放
visibleIds.forEach((id) => {
if (!this.visibleIds.has(id)) this.controllers.get(id)?.start()
})
// 滑出可见区 → 暂停省资源
this.visibleIds.forEach((id) => {
if (!visibleIds.includes(id)) this.controllers.get(id)?.pause()
})
this.visibleIds = new Set(visibleIds)
}
onPageHide() { this.controllers.forEach((c) => c.pause()) } // 页面切走全停
onPageShow() { this.visibleIds.forEach((id) => this.controllers.get(id)?.start()) }
详情页:逐帧与片段播放
@Entry
@Component
struct AssetDetailPage {
private controller: LottieController = new LottieController() // ✅ 独立
@State speed: number = 1.0
@State progress: number = 0
aboutToDisappear() { this.controller.stop() } // ✅ 先停再销毁,避免时序问题
build() {
Column() {
LottieView({
lottieId: this.assetId,
resource: $rawfile(`assets/${this.assetId}.json`),
loop: true, autoplay: true,
controller: this.controller,
contentMode: 'AdaptiveHeight',
}).width('100%').height(400)
Row({ space: 12 }) {
Button('上一帧').onClick(() => {
this.controller.pause()
this.progress = Math.max(0, this.progress - 0.01)
this.controller.setProgress(this.progress)
})
Button('下一帧').onClick(() => {
this.controller.pause()
this.progress = Math.min(1, this.progress + 0.01)
this.controller.setProgress(this.progress)
})
}
Slider({ value: this.progress * 100 })
.onChange((v: number) => {
this.progress = v / 100
this.controller.setProgress(this.progress)
})
// ✅ 片段播放:嵌套数组格式,不是平铺参数
Button('播放前 30 帧').onClick(() => {
this.controller.playSegments([[0, 30]])
})
Button('多段播放').onClick(() => {
this.controller.playSegments([[0, 30], [60, 90]])
})
}
}
}
特效验收清单(素材库的命门)
素材库的核心价值是"所见即所得"。社区修过一批 AE 特效还原问题——Tint 对描边不生效、Drop Shadow 位置不对、Set Matte 异常、天城文渲染错乱。
这类问题肉眼很难自查(动画"在动",只是细节不对),必须建验收清单:
const EFFECT_CATEGORIES = [
{
key: 'tint', name: '着色 Tint',
testAssets: ['tint_fill.json', 'tint_stroke.json'], // ⚠️ 描边着色要单独测
checkpoints: ['填充着色生效', '描边着色生效', '颜色值准确'],
},
{
key: 'shadow', name: '阴影 Drop Shadow',
testAssets: ['shadow_basic.json', 'shadow_blur.json'],
checkpoints: ['阴影位置正确', '模糊半径准确', '透明度正确'],
},
{
key: 'matte', name: '遮罩 Set Matte',
testAssets: ['matte_alpha.json', 'matte_luma.json'],
checkpoints: ['Alpha 遮罩生效', 'Luma 遮罩生效', 'Stroke All Masks 正常'],
},
{
key: 'text', name: '文字动画',
testAssets: ['text_chinese.json', 'text_emoji.json', 'text_devanagari.json'],
checkpoints: ['中文字体正常', 'Emoji 不缺字', '特殊字符正常', '逐字时序正确'],
},
]
升级 lottie-turbo 版本后,这套清单要重跑一遍——渲染细节的修复只有肉眼验收能确认。
同理,GIF 类素材建议准备已知帧率的标准文件做回归(如 10fps 每帧 100ms),帧时序错位这种问题靠感觉发现不了。
小结
"动效君"这个场景里,@ohos/lottie-turbo 解决了三件事:并行加载 + 子线程渲染扛住 20 个动画同屏、独立 controller + Map 管理 + 页面销毁清理保证不泄漏、完整的播放控制让设计师能逐帧验收细节。
动效素材库是 Lottie 库的极限压力测试场。如果你的 App 有多动画同屏、复杂 AE 特效、长时间浏览的场景,选它不会错——但记得建那份特效验收清单。
更多推荐


所有评论(0)