本篇开发握姿祝福功能,结合握姿感应与隔空投送

【完整案例】握姿祝福完整功能开发 教程结构图

图:【完整案例】握姿祝福完整功能开发 的关键流程与实现要点。

学习目标

  • ✅ 实现握姿感应检测
  • ✅ 开发祝福卡片展示
  • ✅ 集成隔空投送分享
  • ✅ 处理权限与异常

预计学习时间

约 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_HELDRIGHT_HAND_HELD
  • 错误码 801 表示设备不支持,201 表示权限未授权

原理解释

  • motion.on('holdingHandChanged', callback) 订阅握持手变化
  • HoldingHandStatus 枚举包含 LEFT_HAND_HELDRIGHT_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
Logo

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

更多推荐