找回密码
 立即注册
首页 业界区 安全 鸿蒙特效教程05-鸿蒙很开门特效

鸿蒙特效教程05-鸿蒙很开门特效

羡渥蛛 2025-6-1 20:44:24
鸿蒙特效教程05-鸿蒙很开门特效

本教程适合HarmonyOS初学者,通过简单到复杂的步骤,通过 Stack 层叠布局 + animation 动画,一步步实现这个"鸿蒙很开门"特效。
开发环境准备


  • DevEco Studio 5.0.3
  • HarmonyOS Next API 15
最终效果预览

屏幕上有一个双开门,点击中间的按钮后,门会向两侧打开,露出开门后面的内容。当用户再次点击按钮时,门会关闭。
实现步骤

我们将通过以下步骤逐步构建这个效果:

  • 用层叠布局搭建基础UI结构
  • 用层叠布局创建门的装饰
  • 实现开关门动画效果
1.gif

步骤1:搭建基础UI结构

首先,我们需要创建一个基本的页面结构。在这个效果中,最关键的是使用Stack组件来实现层叠效果。
  1. @Entry
  2. @Component
  3. struct OpenTheDoor {
  4.   build() {
  5.     Stack() {
  6.       // 背景层
  7.       Column() {
  8.         Text('鸿蒙很开门')
  9.           .fontSize(28)
  10.           .fontWeight(FontWeight.Bold)
  11.           .fontColor(Color.White)
  12.       }
  13.       .width('100%')
  14.       .height('100%')
  15.       .backgroundColor('#1E2247')
  16.       
  17.       // 按钮
  18.       Button({ type: ButtonType.Circle }) {
  19.         Text('开')
  20.           .fontSize(20)
  21.           .fontColor(Color.White)
  22.       }
  23.       .width(60)
  24.       .height(60)
  25.       .backgroundColor('#4CAF50')
  26.       .position({ x: '50%', y: '85%' })
  27.       .translate({ x: '-50%', y: '-50%' })
  28.     }
  29.     .width('100%')
  30.     .height('100%')
  31.     .backgroundColor(Color.Black)
  32.   }
  33. }
复制代码
代码说明:

  • Stack组件是一个层叠布局容器,子组件会按照添加顺序从底到顶叠放。
  • 我们首先放置了一个背景层,它包含了将来门打开后要显示的内容。
  • 然后放置了一个圆形按钮,用于触发开门动作。
  • 使用position和translate组合定位按钮在屏幕底部中间。
此时,只有一个简单的背景和按钮,还没有门的效果。
步骤2:创建门的设计

接下来,我们在Stack层叠布局中添加左右两扇门:
  1. @Entry
  2. @Component
  3. struct OpenTheDoor {
  4.   build() {
  5.     Stack() {
  6.       // 背景层
  7.       Column() {
  8.         Text('鸿蒙很开门')
  9.           .fontSize(28)
  10.           .fontWeight(FontWeight.Bold)
  11.           .fontColor(Color.White)
  12.       }
  13.       .width('100%')
  14.       .height('100%')
  15.       .backgroundColor('#1E2247')
  16.       
  17.       // 左门
  18.       Stack() {
  19.         // 门本体
  20.         Column()
  21.           .width('96%')
  22.           .height('100%')
  23.           .backgroundColor('#333333')
  24.           .borderWidth({ right: 2 })
  25.           .borderColor('#444444')
  26.          
  27.         // 门上装饰
  28.         Column() {
  29.           Circle()
  30.             .width(40)
  31.             .height(40)
  32.             .fill('#666666')
  33.             
  34.           Rect()
  35.             .width(120)
  36.             .height(200)
  37.             .radiusWidth(10)
  38.             .stroke('#555555')
  39.             .strokeWidth(2)
  40.             .fill('none')
  41.             .margin({ top: 40 })
  42.         }
  43.         .width('80%')
  44.         .alignItems(HorizontalAlign.Center)
  45.       }
  46.       .width('50%')
  47.       .height('100%')
  48.       
  49.       // 右门
  50.       Stack() {
  51.         // 门本体
  52.         Column()
  53.           .width('96%')
  54.           .height('100%')
  55.           .backgroundColor('#333333')
  56.           .borderWidth({ left: 2 })
  57.           .borderColor('#444444')
  58.          
  59.         // 门上装饰
  60.         Column() {
  61.           Circle()
  62.             .width(40)
  63.             .height(40)
  64.             .fill('#666666')
  65.             
  66.           Rect()
  67.             .width(120)
  68.             .height(200)
  69.             .radiusWidth(10)
  70.             .stroke('#555555')
  71.             .strokeWidth(2)
  72.             .fill('none')
  73.             .margin({ top: 40 })
  74.         }
  75.         .width('80%')
  76.         .alignItems(HorizontalAlign.Center)
  77.       }
  78.       .width('50%')
  79.       .height('100%')
  80.       
  81.       // 门框
  82.       Column()
  83.         .width('100%')
  84.         .height('100%')
  85.         .border({ width: 8, color: '#666' })
  86.       
  87.       // 按钮
  88.       Button({ type: ButtonType.Circle }) {
  89.         Text('开')
  90.           .fontSize(20)
  91.           .fontColor(Color.White)
  92.       }
  93.       .width(60)
  94.       .height(60)
  95.       .backgroundColor('#4CAF50')
  96.       .position({ x: '50%', y: '85%' })
  97.       .translate({ x: '-50%', y: '-50%' })
  98.     }
  99.     .width('100%')
  100.     .height('100%')
  101.     .backgroundColor(Color.Black)
  102.   }
  103. }
复制代码
代码说明:

  • 我们添加了左右两扇门,每扇门占屏幕宽度的50%。
  • 每扇门自身是一个Stack,包含门本体和装饰元素。
  • 门本体使用Column组件,设置背景色和边框。
  • 装饰元素包括圆形"门把手"和矩形装饰。
  • 添加门框作为装饰元素,增强立体感。
  • 使用zIndex控制层叠顺序(虽然代码中未显示,但在最终代码中会用到)。
此时我们有了一个静态的门的外观,但它还不能打开和关闭。
步骤3:实现开关门动画

现在我们需要添加状态变量和动画逻辑,使门能够打开和关闭:
  1. @Entry
  2. @Component
  3. struct OpenTheDoor {
  4.   // 门打开的最大位移(百分比)
  5.   private doorOpenMaxOffset: number = 110
  6.   // 当前门打开的位移
  7.   @State doorOpenOffset: number = 0
  8.   // 是否正在动画中
  9.   @State isAnimating: boolean = false
  10.   
  11.   // 切换门的状态
  12.   toggleDoor() {
  13.     this.isAnimating = true
  14.    
  15.     if (this.doorOpenOffset <= 0) {
  16.       // 开门动画
  17.       animateTo({
  18.         duration: 1500,
  19.         curve: Curve.EaseInOut,
  20.         iterations: 1,
  21.         playMode: PlayMode.Normal,
  22.         onFinish: () => {
  23.           this.isAnimating = false
  24.         }
  25.       }, () => {
  26.         this.doorOpenOffset = this.doorOpenMaxOffset
  27.       })
  28.     } else {
  29.       // 关门动画
  30.       animateTo({
  31.         duration: 1500,
  32.         curve: Curve.EaseInOut,
  33.         iterations: 1,
  34.         playMode: PlayMode.Normal,
  35.         onFinish: () => {
  36.           this.isAnimating = false
  37.         }
  38.       }, () => {
  39.         this.doorOpenOffset = 0
  40.       })
  41.     }
  42.   }
  43.   
  44.   build() {
  45.     Stack() {
  46.       // 背景层(保持不变)
  47.       ...
  48.       
  49.       // 左门
  50.       Stack() {
  51.         // 门本体和装饰(保持不变)
  52.         ...
  53.       }
  54.       .width('50%')
  55.       .height('100%')
  56.       .translate({ x: this.doorOpenOffset <= 0 ? '0%' : (-this.doorOpenOffset) + '%' })
  57.       
  58.       // 右门
  59.       Stack() {
  60.         // 门本体和装饰(保持不变)
  61.         ...
  62.       }
  63.       .width('50%')
  64.       .height('100%')
  65.       .translate({ x: this.doorOpenOffset <= 0 ? '0%' : this.doorOpenOffset + '%' })
  66.       
  67.       // 门框(保持不变)
  68.       ...
  69.       
  70.       // 按钮
  71.       Button({ type: ButtonType.Circle }) {
  72.         Text(this.doorOpenOffset > 0 ? '关' : '开')
  73.           .fontSize(20)
  74.           .fontColor(Color.White)
  75.       }
  76.       .width(60)
  77.       .height(60)
  78.       .backgroundColor(this.doorOpenOffset > 0 ? '#FF5252' : '#4CAF50')
  79.       .position({ x: '50%', y: '85%' })
  80.       .translate({ x: '-50%', y: '-50%' })
  81.       .onClick(() => {
  82.         if (!this.isAnimating) {
  83.           this.toggleDoor()
  84.         }
  85.       })
  86.     }
  87.     .width('100%')
  88.     .height('100%')
  89.     .backgroundColor(Color.Black)
  90.   }
  91. }
复制代码
代码说明:

  • 添加了状态变量:

    • doorOpenMaxOffset: 门打开的最大位移
    • doorOpenOffset: 当前门的位移状态
    • isAnimating: 标记动画是否正在进行

  • 使用translate属性绑定到doorOpenOffset状态,实现门的移动效果:

    • 左门向左移动:translate({ x: (-this.doorOpenOffset) + '%' })
    • 右门向右移动:translate({ x: this.doorOpenOffset + '%' })

  • 实现toggleDoor方法,使用animateTo函数创建动画:

    • animateTo是HarmonyOS中用于创建显式动画的API
    • 设置动画时长1500毫秒
    • 使用EaseInOut曲线使动画更加平滑
    • 通过改变doorOpenOffset状态触发UI更新

  • 按钮样式和文本随门的状态变化:

    • 门关闭时显示"开",背景绿色
    • 门打开时显示"关",背景红色
    • 添加点击事件调用toggleDoor方法
    • 使用isAnimating防止动画进行中重复触发

此时,门可以通过动画打开和关闭,但门后的内容没有渐变效果。
步骤4:添加门后内容和渐变效果

现在我们为门后的内容添加渐变显示效果:
  1. @Entry
  2. @Component
  3. struct OpenTheDoor {
  4.   // 已有的状态变量
  5.   private doorOpenMaxOffset: number = 110
  6.   @State doorOpenOffset: number = 0
  7.   @State isAnimating: boolean = false
  8.   // 新增状态变量
  9.   @State showContent: boolean = false
  10.   @State backgroundOpacity: number = 0
  11.   
  12.   toggleDoor() {
  13.     this.isAnimating = true
  14.    
  15.     if (this.doorOpenOffset <= 0) {
  16.       // 开门动画
  17.       animateTo({
  18.         duration: 1500,
  19.         curve: Curve.EaseInOut,
  20.         iterations: 1,
  21.         playMode: PlayMode.Normal,
  22.         onFinish: () => {
  23.           this.isAnimating = false
  24.           this.showContent = true
  25.         }
  26.       }, () => {
  27.         this.doorOpenOffset = this.doorOpenMaxOffset
  28.         this.backgroundOpacity = 1
  29.       })
  30.     } else {
  31.       // 关门动画
  32.       this.showContent = false
  33.       animateTo({
  34.         duration: 1500,
  35.         curve: Curve.EaseInOut,
  36.         iterations: 1,
  37.         playMode: PlayMode.Normal,
  38.         onFinish: () => {
  39.           this.isAnimating = false
  40.         }
  41.       }, () => {
  42.         this.doorOpenOffset = 0
  43.         this.backgroundOpacity = 0
  44.       })
  45.     }
  46.   }
  47.   
  48.   build() {
  49.     Stack() {
  50.       // 背景层 - 门后内容
  51.       Column() {
  52.         Text('鸿蒙很开门')
  53.           .fontSize(28)
  54.           .fontWeight(FontWeight.Bold)
  55.           .fontColor(Color.White)
  56.           .opacity(this.backgroundOpacity)
  57.           .margin({ bottom: 20 })
  58.         
  59.         Image($r('app.media.startIcon'))
  60.           .width(100)
  61.           .height(100)
  62.           .objectFit(ImageFit.Contain)
  63.           .opacity(this.backgroundOpacity)
  64.           .animation({
  65.             duration: 800,
  66.             curve: Curve.EaseOut,
  67.             delay: 500,
  68.             iterations: 1,
  69.             playMode: PlayMode.Normal
  70.           })
  71.         
  72.         Text('探索无限可能')
  73.           .fontSize(20)
  74.           .fontColor(Color.White)
  75.           .opacity(this.backgroundOpacity)
  76.           .margin({ top: 20 })
  77.           .visibility(this.showContent ? Visibility.Visible : Visibility.Hidden)
  78.           .animation({
  79.             duration: 800,
  80.             curve: Curve.EaseOut,
  81.             delay: 100,
  82.             iterations: 1,
  83.             playMode: PlayMode.Normal
  84.           })
  85.       }
  86.       .width('100%')
  87.       .height('100%')
  88.       .justifyContent(FlexAlign.Center)
  89.       .alignItems(HorizontalAlign.Center)
  90.       .backgroundColor('#1E2247')
  91.       
  92.       // 其他部分(左门、右门、按钮等)保持不变
  93.       ...
  94.     }
  95.     .width('100%')
  96.     .height('100%')
  97.     .backgroundColor(Color.Black)
  98.   }
  99. }
复制代码
代码说明:

  • 添加新的状态变量:

    • showContent: 控制额外内容的显示与隐藏
    • backgroundOpacity: 控制背景内容的透明度

  • 在toggleDoor方法中同时控制门的位移和内容的透明度:

    • 开门时,门位移增加到最大值,同时透明度从0变为1
    • 关门时,门位移减少到0,同时透明度从1变为0
    • 在开门动画完成后设置showContent为true,显示额外内容

  • 为内容元素添加动画效果:

    • 使用opacity属性绑定到backgroundOpacity状态
    • 为图片添加animation属性,设置渐入效果
    • 为第二段文本添加条件显示visibility属性
    • 两个元素使用不同的延迟时间,创造错落有致的动画效果

这样,当门打开时,背景内容会平滑地渐入,创造更加连贯的用户体验。
步骤5:优化交互体验

最后,我们添加一些细节来增强交互体验:
  1. @Entry
  2. @Component
  3. struct OpenTheDoor {
  4.   // 状态变量保持不变
  5.   private doorOpenMaxOffset: number = 110
  6.   @State doorOpenOffset: number = 0
  7.   @State isAnimating: boolean = false
  8.   @State showContent: boolean = false
  9.   @State backgroundOpacity: number = 0
  10.   
  11.   // toggleDoor方法保持不变
  12.   ...
  13.   
  14.   build() {
  15.     Stack() {
  16.       // 背景层保持不变
  17.       ...
  18.       
  19.       // 左门和右门保持不变,但添加zIndex
  20.       Stack() { ... }
  21.       .width('50%')
  22.       .height('100%')
  23.       .translate({ x: this.doorOpenOffset <= 0 ? '0%' : (-this.doorOpenOffset) + '%' })
  24.       .zIndex(3)
  25.       
  26.       Stack() { ... }
  27.       .width('50%')
  28.       .height('100%')
  29.       .translate({ x: this.doorOpenOffset <= 0 ? '0%' : this.doorOpenOffset + '%' })
  30.       .zIndex(3)
  31.       
  32.       // 门框
  33.       Column()
  34.         .width('100%')
  35.         .height('100%')
  36.         .zIndex(5)
  37.         .opacity(0.7)
  38.         .border({ width: 8, color: '#666' })
  39.       
  40.       // 按钮
  41.       Button({ type: ButtonType.Circle, stateEffect: true }) {
  42.         Stack() {
  43.           Circle()
  44.             .width(60)
  45.             .height(60)
  46.             .fill('#00000060')
  47.          
  48.           if (!this.isAnimating) {
  49.             // 用文本替代图片
  50.             Text(this.doorOpenOffset > 0 ? '关' : '开')
  51.               .fontSize(20)
  52.               .fontColor(Color.White)
  53.               .fontWeight(FontWeight.Bold)
  54.           } else {
  55.             // 加载动效
  56.             LoadingProgress()
  57.               .width(30)
  58.               .height(30)
  59.               .color(Color.White)
  60.           }
  61.         }
  62.       }
  63.       .width(60)
  64.       .height(60)
  65.       .backgroundColor(this.doorOpenOffset > 0 ? '#FF5252' : '#4CAF50')
  66.       .position({ x: '50%', y: '85%' })
  67.       .translate({ x: '-50%', y: '-50%' })
  68.       .zIndex(10)
  69.       .onClick(() => {
  70.         if (!this.isAnimating) {
  71.           this.toggleDoor()
  72.         }
  73.       })
  74.     }
  75.     .width('100%')
  76.     .height('100%')
  77.     .backgroundColor(Color.Black)
  78.     .expandSafeArea()
  79.   }
  80. }
复制代码
代码说明:

  • 添加了zIndex属性来控制组件的层叠顺序:

    • 背景内容:默认层级最低
    • 左右门:zIndex为3
    • 门框:zIndex为5,确保在门的上层
    • 按钮:zIndex为10,确保始终在最上层

  • 改进按钮状态反馈:

    • 添加stateEffect: true使按钮有按下效果
    • 在动画过程中显示LoadingProgress加载指示器
    • 非动画状态下显示"开"或"关"文本

  • 添加expandSafeArea()以全屏显示效果,覆盖刘海屏、挖孔屏的安全区域
完整代码

以下是完整的实现代码:
  1. @Entry
  2. @Component
  3. struct OpenTheDoor {
  4.   // 门打开的位移
  5.   private doorOpenMaxOffset: number = 110
  6.   // 门打开的幅度
  7.   @State doorOpenOffset: number = 0
  8.   // 是否正在动画
  9.   @State isAnimating: boolean = false
  10.   // 是否显示内容
  11.   @State showContent: boolean = false
  12.   // 背景透明度
  13.   @State backgroundOpacity: number = 0
  14.   toggleDoor() {
  15.     this.isAnimating = true
  16.     if (this.doorOpenOffset <= 0) {
  17.       // 开门动画
  18.       animateTo({
  19.         duration: 1500,
  20.         curve: Curve.EaseInOut,
  21.         iterations: 1,
  22.         playMode: PlayMode.Normal,
  23.         onFinish: () => {
  24.           this.isAnimating = false
  25.           this.showContent = true
  26.         }
  27.       }, () => {
  28.         this.doorOpenOffset = this.doorOpenMaxOffset
  29.         this.backgroundOpacity = 1
  30.       })
  31.     } else {
  32.       // 关门动画
  33.       this.showContent = false
  34.       animateTo({
  35.         duration: 1500,
  36.         curve: Curve.EaseInOut,
  37.         iterations: 1,
  38.         playMode: PlayMode.Normal,
  39.         onFinish: () => {
  40.           this.isAnimating = false
  41.         }
  42.       }, () => {
  43.         this.doorOpenOffset = 0
  44.         this.backgroundOpacity = 0
  45.       })
  46.     }
  47.   }
  48.   build() {
  49.     // 层叠布局
  50.     Stack() {
  51.       // 背景层 - 门后内容
  52.       Column() {
  53.         Text('鸿蒙很开门')
  54.           .fontSize(28)
  55.           .fontWeight(FontWeight.Bold)
  56.           .fontColor(Color.White)
  57.           .opacity(this.backgroundOpacity)
  58.           .margin({ bottom: 20 })
  59.         // 图片
  60.         Image($r('app.media.startIcon'))
  61.           .width(100)
  62.           .height(100)
  63.           .objectFit(ImageFit.Contain)
  64.           .opacity(this.backgroundOpacity)
  65.           .animation({
  66.             duration: 800,
  67.             curve: Curve.EaseOut,
  68.             delay: 500,
  69.             iterations: 1,
  70.             playMode: PlayMode.Normal
  71.           })
  72.         Text('探索无限可能')
  73.           .fontSize(20)
  74.           .fontColor(Color.White)
  75.           .opacity(this.backgroundOpacity)
  76.           .margin({ top: 20 })
  77.           .visibility(this.showContent ? Visibility.Visible : Visibility.Hidden)
  78.           .animation({
  79.             duration: 800,
  80.             curve: Curve.EaseOut,
  81.             delay: 100,
  82.             iterations: 1,
  83.             playMode: PlayMode.Normal
  84.           })
  85.       }
  86.       .width('100%')
  87.       .height('100%')
  88.       .justifyContent(FlexAlign.Center)
  89.       .alignItems(HorizontalAlign.Center)
  90.       .backgroundColor('#1E2247')
  91.       .expandSafeArea()
  92.       // 左门
  93.       Stack() {
  94.         // 门
  95.         Column()
  96.           .width('96%')
  97.           .height('100%')
  98.           .backgroundColor('#333333')
  99.           .borderWidth({ right: 2 })
  100.           .borderColor('#444444')
  101.         // 装饰图案
  102.         Column() {
  103.           // 简单的门把手和几何图案设计
  104.           Circle()
  105.             .width(40)
  106.             .height(40)
  107.             .fill('#666666')
  108.             .opacity(0.8)
  109.           Rect()
  110.             .width(120)
  111.             .height(200)
  112.             .radiusWidth(10)
  113.             .stroke('#555555')
  114.             .strokeWidth(2)
  115.             .fill('none')
  116.             .margin({ top: 40 })
  117.           // 添加门上的小装饰
  118.           Grid() {
  119.             ForEach(Array.from({ length: 4 }), () => {
  120.               GridItem() {
  121.                 Circle()
  122.                   .width(8)
  123.                   .height(8)
  124.                   .fill('#777777')
  125.               }
  126.             })
  127.           }
  128.           .columnsTemplate('1fr 1fr')
  129.           .rowsTemplate('1fr 1fr')
  130.           .width(60)
  131.           .height(60)
  132.           .margin({ top: 20 })
  133.         }
  134.         .width('80%')
  135.         .alignItems(HorizontalAlign.Center)
  136.       }
  137.       .width('50%')
  138.       .height('100%')
  139.       .translate({ x: this.doorOpenOffset <= 0 ? '0%' : (-this.doorOpenOffset) + '%' })
  140.       .zIndex(3)
  141.       // 右门
  142.       Stack() {
  143.         // 门
  144.         Column()
  145.           .width('96%')
  146.           .height('100%')
  147.           .backgroundColor('#333333')
  148.           .borderWidth({ left: 2 })
  149.           .borderColor('#444444')
  150.         // 装饰图案
  151.         Column() {
  152.           // 简单的门把手和几何图案设计
  153.           Circle()
  154.             .width(40)
  155.             .height(40)
  156.             .fill('#666666')
  157.             .opacity(0.8)
  158.           Rect()
  159.             .width(120)
  160.             .height(200)
  161.             .radiusWidth(10)
  162.             .stroke('#555555')
  163.             .strokeWidth(2)
  164.             .fill('none')
  165.             .margin({ top: 40 })
  166.           // 添加门上的小装饰
  167.           Grid() {
  168.             ForEach(Array.from({ length: 4 }), () => {
  169.               GridItem() {
  170.                 Circle()
  171.                   .width(8)
  172.                   .height(8)
  173.                   .fill('#777777')
  174.               }
  175.             })
  176.           }
  177.           .columnsTemplate('1fr 1fr')
  178.           .rowsTemplate('1fr 1fr')
  179.           .width(60)
  180.           .height(60)
  181.           .margin({ top: 20 })
  182.         }
  183.         .width('80%')
  184.         .alignItems(HorizontalAlign.Center)
  185.       }
  186.       .width('50%')
  187.       .height('100%')
  188.       .translate({ x: this.doorOpenOffset <= 0 ? '0%' : this.doorOpenOffset + '%' })
  189.       .zIndex(3)
  190.       // 门框
  191.       Column()
  192.         .width('100%')
  193.         .height('100%')
  194.         .zIndex(5)
  195.         .opacity(0.7)
  196.         .border({ width: 8, color: '#666' })
  197.       // 控制按钮
  198.       Button({ type: ButtonType.Circle, stateEffect: true }) {
  199.         Stack() {
  200.           Circle()
  201.             .width(60)
  202.             .height(60)
  203.             .fill('#00000060')
  204.           if (!this.isAnimating) {
  205.             // 用文本替代图片
  206.             Text(this.doorOpenOffset > 0 ? '关' : '开')
  207.               .fontSize(20)
  208.               .fontColor(Color.White)
  209.               .fontWeight(FontWeight.Bold)
  210.           } else {
  211.             // 加载动效
  212.             LoadingProgress()
  213.               .width(30)
  214.               .height(30)
  215.               .color(Color.White)
  216.           }
  217.         }
  218.       }
  219.       .width(60)
  220.       .height(60)
  221.       .backgroundColor(this.doorOpenOffset > 0 ? '#FF5252' : '#4CAF50')
  222.       .position({ x: '50%', y: '85%' })
  223.       .translate({ x: '-50%', y: '-50%' })
  224.       .zIndex(10) // 按钮位置在最上方
  225.       .onClick(() => {
  226.         // 防止多点
  227.         if (!this.isAnimating) {
  228.           this.toggleDoor()
  229.         }
  230.       })
  231.     }
  232.     .width('100%')
  233.     .height('100%')
  234.     .backgroundColor(Color.Black)
  235.     .expandSafeArea()
  236.   }
  237. }
复制代码
总结与技术要点

涉及了以下HarmonyOS开发中的重要技术点:
1. Stack布局

Stack组件是实现这种叠加效果,允许子组件按照添加顺序从底到顶叠放。使用时有以下注意点:

  • 使用 zIndex 属性控制层叠顺序
  • 使用 alignContent 参数控制子组件对齐
2. 动画系统

本教程中使用了两种动画机制:

  • animateTo:显式动画API,用于创建状态变化时的过渡效果
    1. animateTo({
    2.   duration: 1500,
    3.   curve: Curve.EaseInOut,
    4.   iterations: 1,
    5.   playMode: PlayMode.Normal,
    6.   onFinish: () => { /* 动画完成回调 */ }
    7. }, () => {
    8.   // 状态变化,触发动画
    9.   this.doorOpenOffset = this.doorOpenMaxOffset
    10. })
    复制代码
  • animation:属性动画,直接在组件上定义
    1. .animation({
    2.   duration: 800,
    3.   curve: Curve.EaseOut,
    4.   delay: 500,
    5.   iterations: 1,
    6.   playMode: PlayMode.Normal
    7. })
    复制代码
3. 状态管理

我们使用以下几个状态来控制整个效果:

  • doorOpenOffset:控制门的位移
  • isAnimating:标记动画状态,防止重复触发
  • backgroundOpacity:控制背景内容的透明度
  • showContent:控制特定内容的显示与隐藏
4. translate 位移

使用translate属性实现门的移动效果:
[code].translate({ x: this.doorOpenOffset
您需要登录后才可以回帖 登录 | 立即注册