HarmonyOS APP<<古今职鉴定>>开源教程第28篇:【完整案例】握姿祝福完整功能开发
本篇开发握姿祝福功能,结合握姿感应与隔空投送

图:【完整案例】握姿祝福完整功能开发 的关键流程与实现要点。
学习目标
- ✅ 实现握姿感应检测
- ✅ 开发祝福卡片展示
- ✅ 集成隔空投送分享
- ✅ 处理权限与异常
预计学习时间
约 150 分钟
实战一:准备祝福卡片资源
第一步:定义祝福卡片数据
// 祝福卡片数据接口
interface BlessingCard {
name: string; // 资源名称
title: string; // 祝福标题
subtitle: string; // 祝福副标题
emoji: string; // 装饰 emoji
resource: Resource; // 图片资源
}
// 吉祥寓意祝福卡片(10张)
const BLESSING_CARDS: BlessingCard[] = [
{ name: 'blessing_horse_money', title: '马上有钱', subtitle: '金银财宝滚滚来', emoji: '🐴💰', resource: $r('app.media.blessing_horse_money') },
{ name: 'blessing_gold_ingot', title: '金玉满堂', subtitle: '富贵荣华福满门', emoji: '🏆✨', resource: $r('app.media.blessing_gold_ingot') },
{ name: 'blessing_fish_surplus', title: '年年有余', subtitle: '富足安康年年好', emoji: '🐟🧧', resource: $r('app.media.blessing_fish_surplus') },
{ name: 'blessing_fortune_god', title: '财神到', subtitle: '招财进宝福星照', emoji: '🧧💎', resource: $r('app.media.blessing_fortune_god') },
{ name: 'blessing_spring_arrives', title: '春到福来', subtitle: '春暖花开万象新', emoji: '🌸🍀', resource: $r('app.media.blessing_spring_arrives') },
// ... 更多卡片
];
// 诗句祝福卡片(10张)
const POEM_CARDS: BlessingCard[] = [
{ name: 'blessing_poem_spring_wind', title: '春风得意', subtitle: '春风得意马蹄疾,一日看尽长安花', emoji: '🌺🐴', resource: $r('app.media.blessing_poem_spring_wind') },
{ name: 'blessing_poem_new_year', title: '元日祝福', subtitle: '千门万户曈曈日,总把新桃换旧符', emoji: '🌅🏮', resource: $r('app.media.blessing_poem_new_year') },
// ... 更多卡片
];
// 合并所有祝福卡片
const ALL_BLESSING_CARDS: BlessingCard[] = [...BLESSING_CARDS, ...POEM_CARDS];
原理解释:
- 准备 20 张祝福卡片,分为吉祥寓意和诗句两类
- 每张卡片包含标题、副标题、emoji 和图片资源
- 隔空投送时随机抽取一张发送
原理解释:
- 准备 20 张祝福卡片,分为吉祥寓意和诗句两类
- 每张卡片包含标题、副标题、emoji 和图片资源
- 隔空投送时随机抽取一张发送
案例效果:祝福卡片资源概览:
┌── 吉祥寓意卡片 (10张) ───────────────┐
│ 🐴💰 马上有钱 │ 🏆✨ 金玉满堂 │
│ 🐟🧧 年年有余 │ 🧧💎 财神到 │
│ 🌸🍀 春到福来 │ ... │
├── 诗句祝福卡片 (10张) ───────────────┤
│ 🌺🐴 春风得意 │ 🌅🏮 元日祝福 │
│ ... │ ... │
└──────────────────────────────────────┘
→ 每次随机抽取一张用于隔空投送
实战二:实现握姿感应
第一步:导入必要模块
import { motion } from '@kit.MultimodalAwarenessKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { harmonyShare, systemShare } from '@kit.ShareKit';
import { uniformTypeDescriptor as utd } from '@kit.ArkData';
import { fileUri } from '@kit.CoreFileKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
第二步:创建页面状态
@Component
struct GripBlessingPage {
@Consume('mainNavPathStack') mainNavPathStack: NavPathStack;
@StorageLink('isDarkMode') isDarkMode: boolean = true;
// 握姿状态
@State holdingHand: string = 'unknown'; // 'left', 'right', 'unknown'
@State isDetecting: boolean = false;
@State showBlessing: boolean = false;
// 动画状态
@State blessingScale: number = 0.3;
@State blessingOpacity: number = 0;
@State blessingRotate: number = -15;
@State particleOpacity: number = 0;
@State textOffsetY: number = 50;
// 隔空传送状态
@State isGesturesShareReady: boolean = false;
@State shareStatusText: string = '';
@State currentBlessingCard: BlessingCard | null = null;
// 错误状态
@State errorMessage: string = '';
@State deviceNotSupported: boolean = false;
@State permissionDenied: boolean = false;
// 回调引用
private holdingHandCallback: ((data: motion.HoldingHandStatus) => void) | null = null;
private gesturesShareCallback: ((target: harmonyShare.SharableTarget) => void) | null = null;
private currentImagePath: string = '';
private mainWindowId: number = -1;
}
第三步:实现握姿检测
// 开始握持手检测
private startHoldingHandDetection() {
this.isDetecting = true;
this.errorMessage = '';
this.deviceNotSupported = false;
try {
// 保存回调函数引用
this.holdingHandCallback = (data: motion.HoldingHandStatus) => {
console.info('握持手状态变化: ' + JSON.stringify(data));
this.handleHoldingHandChange(data);
};
// 订阅握持手变化事件
motion.on('holdingHandChanged', this.holdingHandCallback);
console.info('握持手感知订阅成功');
} catch (err) {
let error = err as BusinessError;
console.error('握持手感知订阅失败: ' + error.code);
if (error.code === 801) {
// 设备不支持
this.deviceNotSupported = true;
this.errorMessage = '当前设备不支持握持感知功能';
} else if (error.code === 201) {
// 权限被拒绝
this.permissionDenied = true;
this.errorMessage = '请授权握姿感应权限';
}
this.isDetecting = false;
}
}
// 停止握持手检测
private stopHoldingHandDetection() {
try {
if (this.holdingHandCallback) {
motion.off('holdingHandChanged', this.holdingHandCallback);
this.holdingHandCallback = null;
console.info('握持手感知取消订阅成功');
}
} catch (err) {
console.error('取消订阅失败');
}
}
// 处理握持手变化
private handleHoldingHandChange(data: motion.HoldingHandStatus) {
let newHand = 'unknown';
// HoldingHandStatus 是枚举值
if (data === motion.HoldingHandStatus.LEFT_HAND_HELD) {
newHand = 'left';
} else if (data === motion.HoldingHandStatus.RIGHT_HAND_HELD) {
newHand = 'right';
}
if (newHand !== 'unknown' && newHand !== this.holdingHand) {
this.holdingHand = newHand;
this.showBlessingAnimation();
// 切换祝福内容后,重新准备隔空传送
this.prepareGesturesShare();
}
}
原理解释:
motion.on('holdingHandChanged', callback)订阅握持手变化HoldingHandStatus枚举包含LEFT_HAND_HELD、RIGHT_HAND_HELD- 错误码 801 表示设备不支持,201 表示权限未授权
原理解释:
motion.on('holdingHandChanged', callback)订阅握持手变化HoldingHandStatus枚举包含LEFT_HAND_HELD、RIGHT_HAND_HELD- 错误码 801 表示设备不支持,201 表示权限未授权
案例效果:握姿检测的三种状态:
┌── 检测中 ─────────┐ ┌── 左手握持 ────────┐ ┌── 右手握持 ────────┐
│ │ │ │ │ │
│ 正在检测握姿… │ │ 🤚 左手握持 │ │ 右手握持 🤚 │
│ │ │ 切换祝福内容 │ │ 切换祝福内容 │
│ ⏳ │ │ → 触发动画 │ │ → 触发动画 │
└────────────────────┘ └────────────────────┘ └────────────────────┘
┌── 设备不支持 ──────┐ ┌── 权限未授权 ──────┐
│ │ │ │
│ 📱 设备不支持 │ │ 🔐 需要授权 │
│ 当前设备不支持 │ │ 握姿感应功能需要 │
│ 握持感知功能 │ │ 获取手势识别权限 │
│ │ │ │
│ [ 返回首页 ] │ │ [立即授权] │
│ │ │ [去设置中开启] │
└────────────────────┘ └────────────────────┘
实战三:实现祝福卡片动画
第一步:显示祝福动画
private showBlessingAnimation() {
// 重置动画状态
this.showBlessing = false;
this.blessingScale = 0.3;
this.blessingOpacity = 0;
this.blessingRotate = -15;
this.particleOpacity = 0;
this.textOffsetY = 50;
// 延迟启动动画
setTimeout(() => {
this.showBlessing = true;
// 主卡片弹出动画
animateTo({
duration: 600,
curve: Curve.EaseOut
}, () => {
this.blessingScale = 1;
this.blessingOpacity = 1;
this.blessingRotate = 0;
});
// 粒子效果动画(延迟200ms)
setTimeout(() => {
animateTo({
duration: 400,
curve: Curve.EaseOut
}, () => {
this.particleOpacity = 1;
});
}, 200);
// 文字上移动画(延迟300ms)
setTimeout(() => {
animateTo({
duration: 500,
curve: Curve.EaseOut
}, () => {
this.textOffsetY = 0;
});
}, 300);
}, 100);
}
第二步:构建祝福卡片 UI
@Builder
BlessingCard() {
Column({ space: 20 }) {
// 祝福图片
Stack() {
// 光晕效果
Column()
.width(280)
.height(280)
.borderRadius(140)
.backgroundColor(this.holdingHand === 'left' ? 'rgba(255,215,0,0.3)' : 'rgba(50,205,50,0.3)')
.blur(30)
// 主图片
Image(this.holdingHand === 'left' ? $r('app.media.blessing_happy_newyear') : $r('app.media.blessing_fortune'))
.width(240)
.height(240)
.borderRadius(120)
.objectFit(ImageFit.Cover)
.shadow({ radius: 20, color: 'rgba(0,0,0,0.3)', offsetY: 10 })
}
.scale({ x: this.blessingScale, y: this.blessingScale })
.rotate({ angle: this.blessingRotate })
.opacity(this.blessingOpacity)
// 祝福文字
Column({ space: 8 }) {
Text(this.holdingHand === 'left' ? '🎊 新年快乐 🎊' : '💰 财源滚滚 💰')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor(this.holdingHand === 'left' ? '#ffd700' : '#32cd32')
.textShadow({ radius: 10, color: 'rgba(0,0,0,0.5)', offsetY: 2 })
Text(this.holdingHand === 'left' ? '万事如意,阖家幸福' : '财源滚滚随春到')
.fontSize(18)
.fontColor(Color.White)
}
.offset({ y: this.textOffsetY })
.opacity(this.blessingOpacity)
// 隔空投送提示
if (this.isGesturesShareReady) {
Row({ space: 8 }) {
Text('📡')
.fontSize(14)
Text('隔空投递已就绪')
.fontSize(12)
.fontColor(Color.White)
}
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor('rgba(50,205,50,0.3)')
.borderRadius(16)
}
}
.padding(24)
}
第三步:添加粒子背景效果
@Builder
ParticleBackground() {
Stack() {
// 烟花/星星粒子
ForEach([0, 1, 2, 3, 4, 5, 6, 7], (index: number) => {
Text(this.holdingHand === 'left' ? '🎆' : '💫')
.fontSize(24 + index * 4)
.opacity(this.particleOpacity * (0.3 + index * 0.1))
.position({
x: `${10 + index * 12}%`,
y: `${15 + (index % 4) * 20}%`
})
.rotate({ angle: index * 45 })
})
// 四叶草/星星
ForEach([0, 1, 2, 3, 4], (index: number) => {
Text(this.holdingHand === 'left' ? '✨' : '🍀')
.fontSize(20 + index * 3)
.opacity(this.particleOpacity * (0.4 + index * 0.1))
.position({
x: `${70 + index * 5}%`,
y: `${25 + index * 15}%`
})
})
}
.width('100%')
.height('100%')
}
**案例效果**:祝福卡片完整动画效果:
┌────────── 深色渐变背景 ──────────────┐<br />│ │<br />│ 🎆 ✨ 💫 │ ← 粒子背景效果<br />│ 🎆 ✨ │ (延迟200ms淡入)<br />│ │<br />│ ╭───────────────╮ │<br />│ │ ╭─────────╮ │ │<br />│ │ │ 🧧 │ │ ← 光晕效果(金色/绿色)<br />│ │ │ 新年 │ │ 根据左右手切换颜色<br />│ │ │ 快乐 │ │ │<br />│ │ ╰─────────╯ │ ← 主图片:弹出+旋转动画<br />│ ╰───────────────╯ scale:0.3→1<br />│ rotate:-15°→0°<br />│ opacity:0→1<br />│ 🎊 新年快乐 🎊 │ ← 文字上移动画<br />│ 万事如意,阖家幸福 │ offsetY:50→0<br />│ │ (延迟300ms)<br />│ ┌──────────────────┐ │<br />│ │ 📡 隔空投递已就绪 │ │ ← 绿色半透明背景<br />│ └──────────────────┘ │<br />│ │<br />│ ── 左手握持 ── ── 右手握持 ── │<br />│ 🎊 新年快乐 💰 财源滚滚 │<br />│ 金色光晕 绿色光晕 │<br />│ 🎆烟花粒子 💫星星粒子 │<br />└──────────────────────────────────────┘
> **效果说明**:
> - 动画分三阶段:① 主卡片弹出(600ms) → ② 粒子淡入(延迟200ms) → ③ 文字上移(延迟300ms)
> - 左手握持:金色光晕 + 🎊新年快乐 + 🎆烟花粒子
> - 右手握持:绿色光晕 + 💰财源滚滚 + 💫星星粒子
> - 每次切换握姿都会重置并重新播放完整动画
---
## 实战四:实现隔空投送
### 第一步:准备分享图片
// 准备隔空传送图片(随机抽取祝福卡片)<br />private async prepareGesturesShare() {<br />try {<br />// 先注销之前的监听<br />this.unregisterGesturesShare();
// 随机抽取一张祝福卡片<br />const randomIndex = Math.floor(Math.random() * ALL_BLESSING_CARDS.length);<br />this.currentBlessingCard = ALL_BLESSING_CARDS[randomIndex];<br />console.info('[GesturesShare] 随机抽取祝福卡片: ' + this.currentBlessingCard.title);
// 将资源图片复制到临时目录<br />const context = getContext(this);<br />const resourceManager = context.resourceManager;
// 获取图片数据<br />const imageData = await resourceManager.getMediaContent(this.currentBlessingCard.resource);
// 写入临时文件<br />const tempDir = context.tempDir;<br />const tempFilePath = ${tempDir}/${this.currentBlessingCard.name}_${Date.now()}.png;
const file = fs.openSync(tempFilePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);<br />fs.writeSync(file.fd, imageData.buffer);<br />fs.closeSync(file);
this.currentImagePath = tempFilePath;<br />console.info('[GesturesShare] 图片准备完成: ' + tempFilePath);
// 注册隔空传送<br />this.registerGesturesShare();<br />} catch (err) {<br />let error = err as BusinessError;<br />console.error('[GesturesShare] 准备图片失败: ' + error.message);<br />this.shareStatusText = '图片准备失败';<br />}<br />}
**原理解释**:
- `resourceManager.getMediaContent()` 读取资源文件内容
- 将图片写入临时目录,获取文件路径
- 隔空投送需要文件 URI,不能直接使用 Resource
### 第二步:注册隔空传送监听
// 注册隔空传送监听<br />private registerGesturesShare() {<br />try {<br />console.info('[GesturesShare] 开始注册隔空传送分享');
// 获取当前窗口 ID<br />window.getLastWindow(getContext(this)).then((windowInstance) => {<br />this.mainWindowId = windowInstance.getWindowProperties().id;<br />console.info('[GesturesShare] 获取窗口成功,windowId: ' + this.mainWindowId);
// 定义隔空传送回调(官方建议3秒内调用share方法)<br />this.gesturesShareCallback = (sharableTarget: harmonyShare.SharableTarget) => {<br />console.info('[GesturesShare] 隔空传送手势触发!');<br />this.shareStatusText = '正在传送...';<br />this.shareImageNative(sharableTarget);<br />// 每次触发后准备下一张随机卡片<br />this.prepareNextRandomCard();<br />};
// 构建能力注册对象,绑定到当前窗口<br />const capability: harmonyShare.SendCapabilityRegistry = {<br />windowId: this.mainWindowId<br />};
// 注册监听<br />harmonyShare.on('gesturesShare', capability, this.gesturesShareCallback);<br />this.isGesturesShareReady = true;<br />this.shareStatusText = '';<br />console.info('[GesturesShare] 隔空传送监听注册成功!');<br />}).catch((err: BusinessError) => {<br />console.error('[GesturesShare] 获取窗口失败: ' + err.message);<br />this.isGesturesShareReady = false;<br />});<br />} catch (err) {<br />let error = err as BusinessError;<br />console.error('[GesturesShare] 注册失败: ' + error.message);<br />this.isGesturesShareReady = false;<br />}<br />}
### 第三步:执行图片分享
// 执行图片分享<br />private shareImageNative(sharableTarget: harmonyShare.SharableTarget) {<br />try {<br />console.info('[GesturesShare] 开始分享: ' + this.currentBlessingCard?.title);
// 处理文件路径<br />let filePath = this.currentImagePath;<br />if (filePath.startsWith('file://')) {<br />filePath = filePath.substring(7);<br />}
// 获取文件 URI<br />const imageUri = fileUri.getUriFromPath(filePath);<br />console.info('[GesturesShare] 文件URI: ' + imageUri);
// 构建分享数据<br />const shareDataRecord: systemShare.SharedRecord = {<br />utd: utd.UniformDataType.IMAGE,<br />uri: imageUri<br />};
// 创建 SharedData 并分享<br />const shareData = new systemShare.SharedData(shareDataRecord);<br />sharableTarget.share(shareData);
this.shareStatusText = '分享成功!';<br />console.info('[GesturesShare] 分享成功');<br />} catch (err) {<br />let error = err as BusinessError;<br />console.error('[GesturesShare] 分享失败: ' + error.message);<br />this.shareStatusText = '分享失败';<br />}<br />}
**原理解释**:
- `fileUri.getUriFromPath()` 将文件路径转换为 URI
- `utd.UniformDataType.IMAGE` 指定分享类型为图片
- `sharableTarget.share()` 必须在手势触发后 3 秒内调用
### 第四步:注销监听
// 注销隔空传送监听<br />private unregisterGesturesShare() {<br />try {<br />if (this.gesturesShareCallback && this.mainWindowId !== -1) {<br />const capability: harmonyShare.SendCapabilityRegistry = {<br />windowId: this.mainWindowId<br />};<br />harmonyShare.off('gesturesShare', capability);<br />console.info('[GesturesShare] 隔空传送监听注销成功');<br />}<br />this.gesturesShareCallback = null;<br />this.isGesturesShareReady = false;<br />} catch (err) {<br />console.error('[GesturesShare] 注销失败');<br />}<br />}
**案例效果**:隔空投送的完整流程:
┌── 步骤1: 准备图片 ────────────────────┐<br />│ │<br />│ 随机抽取祝福卡片 → 读取资源图片 │<br />│ ↓ │<br />│ 写入临时文件 → 获取文件路径 │<br />│ ↓ │<br />│ 注册隔空传送监听 │<br />└───────────────────────────────────────┘<br />↓<br />┌── 步骤2: 等待触发 ────────────────────┐<br />│ │<br />│ ┌──────────────────────────────┐ │<br />│ │ 📡 隔空投递已就绪 │ │ ← 绿色提示<br />│ └──────────────────────────────┘ │<br />│ │<br />│ 用户执行:双指捏合向上滑动 │<br />└───────────────────────────────────────┘<br />↓<br />┌── 步骤3: 执行分享 ────────────────────┐<br />│ │<br />│ 手势触发! → 构建SharedData │<br />│ ↓ │<br />│ sharableTarget.share(shareData) │<br />│ ↓ │<br />│ ┌──────────────┐ │<br />│ │ 正在传送... │ → │ 分享成功!│ │<br />│ └──────────────┘ └──────────┘ │<br />│ ↓ │<br />│ 准备下一张随机卡片(循环) │<br />└───────────────────────────────────────┘
> **效果说明**:
> - 图片准备:`resourceManager.getMediaContent()` → 写入临时目录
> - 注册监听:`harmonyShare.on('gesturesShare')` 绑定到当前窗口
> - 触发分享:手势触发后 3 秒内必须调用 `share()` 方法
> - 每次分享后自动准备下一张随机祝福卡片
---
## 实战五:处理异常情况
### 第一步:设备不支持提示
@Builder<br />DeviceNotSupportedView() {<br />Column({ space: 24 }) {<br />Column() {<br />Text('📱')<br />.fontSize(48)<br />}<br />.width(100)<br />.height(100)<br />.borderRadius(50)<br />.backgroundColor('rgba(255,255,255,0.15)')<br />.justifyContent(FlexAlign.Center)
Text('设备不支持')<br />.fontSize(24)<br />.fontWeight(FontWeight.Bold)<br />.fontColor(Color.White)
Text('当前设备不支持握持感知功能')<br />.fontSize(14)<br />.fontColor(Color.White)
// 设置路径提示<br />Column({ space: 8 }) {<br />Text('请在系统设置中检查:')<br />.fontSize(13)<br />.fontColor('#d1d5db')<br />Text('设置 > 系统 > 快捷启动和手势')<br />.fontSize(13)<br />.fontColor('#ffd700')<br />.fontWeight(FontWeight.Medium)<br />}<br />.padding(16)<br />.backgroundColor('rgba(255,255,255,0.1)')<br />.borderRadius(12)
Button('返回首页')<br />.fontSize(16)<br />.fontColor(Color.White)<br />.backgroundColor('#c41e3a')<br />.borderRadius(24)<br />.height(48)<br />.width(160)<br />.onClick(() => this.mainNavPathStack.pop())<br />}<br />.padding(32)<br />}
### 第二步:权限未授权提示
@Builder<br />PermissionDeniedView() {<br />Column({ space: 24 }) {<br />Column() {<br />Text('🔐')<br />.fontSize(48)<br />}<br />.width(100)<br />.height(100)<br />.borderRadius(50)<br />.backgroundColor('rgba(255,255,255,0.15)')<br />.justifyContent(FlexAlign.Center)
Text('需要授权')<br />.fontSize(24)<br />.fontWeight(FontWeight.Bold)<br />.fontColor(Color.White)
Text('握姿感应功能需要获取手势识别权限')<br />.fontSize(14)<br />.fontColor(Color.White)
// 授权按钮<br />Button('立即授权')<br />.fontSize(16)<br />.fontColor(Color.White)<br />.backgroundColor('#c41e3a')<br />.borderRadius(24)<br />.height(48)<br />.width(160)<br />.onClick(() => this.requestMotionPermission())
// 去设置按钮<br />Button('去设置中开启')<br />.fontSize(14)<br />.fontColor(Color.White)<br />.backgroundColor('rgba(255,255,255,0.15)')<br />.borderRadius(24)<br />.height(40)<br />.width(140)<br />.onClick(() => this.openSettings())<br />}<br />.padding(32)<br />}
---
## 完整生命周期管理
aboutToAppear() {<br />this.startAnimations();<br />this.checkPermissionAndStart();<br />}
aboutToDisappear() {<br />this.stopHoldingHandDetection();<br />this.unregisterGesturesShare();<br />this.stopAnimations();<br />}
onPageShow(): void {<br />// 页面显示时启动握姿感应<br />this.startHoldingHandDetection();<br />if (this.showBlessing) {<br />this.prepareGesturesShare();<br />}<br />}
onPageHide(): void {<br />// 页面隐藏时停止<br />this.stopHoldingHandDetection();<br />this.unregisterGesturesShare();<br />}
---
## 本课小结
| 功能 | 实现方式 |
|---|---|
| 握姿感应 | motion.on('holdingHandChanged') |
| 祝福动画 | animateTo + 多阶段延迟 |
| 隔空投送 | harmonyShare.on('gesturesShare') |
| 图片准备 | resourceManager + 临时文件 |
| 异常处理 | 错误码判断 + 引导页面 |
---
## 隔空投送使用条件
1. 设置 > 系统 > 快捷启动和手势 > 隔空传送 已开启
2. 两台设备登录同一华为账号
3. 蓝牙和 WiFi 已开启
4. 触发方式:双指捏合向上滑动(隔空投递手势)
---
## 课后练习
1. 添加更多祝福卡片样式
2. 实现祝福卡片的本地保存功能
3. 添加分享成功的音效反馈
---
## 下一课预告
第29课开发桌面卡片完整功能,包括卡片配置、数据更新、点击跳转。
---
## 项目开源地址
https://gitcode.com/daleishen/gujinzhijian
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)