HarmonyOS 5 手势系统与高级交互动效开发实战

👆 一、HarmonyOS手势系统概述

HarmonyOS提供了强大的手势识别能力,支持从简单的点击到复杂的多指操作,为创建直观且响应式的用户界面奠定了基础。

1. 手势类型与核心API

手势类型 识别内容 典型应用场景 核心API
点击手势 (Click) 单次轻触屏幕 按钮操作、项目选择 .onClick()
双击手势 (DoubleClick) 快速连续两次点击 放大/缩小、快速操作 .onDoubleClick()
长按手势 (LongPress) 长时间按压 上下文菜单、拖拽准备 .onLongPress()
拖拽手势 (Pan) 单指滑动 元素移动、滑动操作 PanGesture()
捏合手势 (Pinch) 两指缩放 图片缩放、地图缩放 PinchGesture()
旋转手势 (Rotation) 两指旋转 图片旋转、元素旋转 RotationGesture()

2. 开发准备与配置

在ArkTS文件中导入必要的手势模块:

复制代码
import gesture from '@ohos.multimodalInput.gesture';
import { GestureEvent, GestureGroup, GestureMode } from '@ohos.ultimodalInput.gesture';

✋ 二、基础手势识别与处理

1. 简单手势处理

使用内置的便捷手势处理方法:

复制代码
@Component
struct BasicGestureExample {
  @State tapCount: number = 0;
  @State isLongPressed: boolean = false;
  @State scaleValue: number = 1.0;

  build() {
    Column() {
      // 点击手势
      Text(`点击次数: ${this.tapCount}`)
        .fontSize(18)
        .padding(20)
        .backgroundColor(Color.Blue)
        .onClick(() => {
          this.tapCount++;
        })

      // 双击手势
      Text('双击放大')
        .fontSize(18)
        .padding(20)
        .backgroundColor(Color.Green)
        .scale({ x: this.scaleValue, y: this.scaleValue })
        .onDoubleClick(() => {
          animateTo({ duration: 300 }, () => {
            this.scaleValue = this.scaleValue === 1.0 ? 1.5 : 1.0;
          });
        })

      // 长按手势
      Text(this.isLongPressed ? '已长按' : '长按我')
        .fontSize(18)
        .padding(20)
        .backgroundColor(this.isLongPressed ? Color.Red : Color.Gray)
        .onLongPress(() => {
          this.isLongPressed = true;
          setTimeout(() => {
            this.isLongPressed = false;
          }, 1000);
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

2. 高级手势配置

对于更复杂的手势需求,可以使用Gesture构造函数:

复制代码
@Component
struct AdvancedGestureExample {
  @State panX: number = 0;
  @State panY: number = 0;
  @State rotationAngle: number = 0;
  @State pinchScale: number = 1.0;

  build() {
    Stack() {
      // 可拖拽、旋转、缩放的组件
      Image($r('app.media.draggable_image'))
        .width(200)
        .height(200)
        .translate({ x: this.panX, y: this.panY })
        .rotate({ angle: this.rotationAngle })
        .scale({ x: this.pinchScale, y: this.pinchScale })
        .gesture(
          // 拖拽手势
          PanGesture({ fingers: 1 })
            .onActionStart((event: GestureEvent) => {
              console.info('拖拽开始');
            })
            .onActionUpdate((event: GestureEvent) => {
              this.panX += event.offsetX;
              this.panY += event.offsetY;
            })
            .onActionEnd(() => {
              console.info('拖拽结束');
              // 添加回弹动画
              animateTo({ duration: 300, curve: Curve.Spring }, () => {
                this.panX = 0;
                this.panY = 0;
              });
            })
        )
        .gesture(
          // 旋转手势
          RotationGesture({ fingers: 2 })
            .onActionUpdate((event: GestureEvent) => {
              this.rotationAngle += event.angle;
            })
        )
        .gesture(
          // 缩放手势
          PinchGesture({ fingers: 2 })
            .onActionUpdate((event: GestureEvent) => {
              this.pinchScale *= event.scale;
              // 限制缩放范围
              this.pinchScale = Math.max(0.5, Math.min(3, this.pinchScale));
            })
        )
    }
    .width('100%')
    .height('100%')
    .onClick(() => {
      // 点击重置
      animateTo({ duration: 500 }, () => {
        this.panX = 0;
        this.panY = 0;
        this.rotationAngle = 0;
        this.pinchScale = 1.0;
      });
    })
  }
}

🔄 三、复杂手势组合与冲突处理

1. 手势组合与优先级

使用GestureGroup管理多个手势的优先级和组合方式:

复制代码
@Component
struct GesturePriorityExample {
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State scale: number = 1.0;
  @State isScrolling: boolean = false;

  build() {
    Column() {
      // 复杂手势组合示例
      Column() {
        Text('手势优先级示例')
          .fontSize(20)
          .margin({ bottom: 20 })
        
        // 可交互区域
        Stack() {
          // 可缩放、拖拽的内容
          Column() {
            ForEach([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], (index) => {
              Text(`列表项 ${index}`)
                .fontSize(16)
                .padding(12)
                .backgroundColor(index % 2 === 0 ? '#F0F0F0' : '#FFFFFF')
                .width('100%')
            })
          }
          .width('100%')
          .height(400)
          .translate({ x: this.offsetX, y: this.offsetY })
          .scale({ x: this.scale, y: this.scale })
          .gesture(
            GestureGroup(GestureMode.Exclusive,
              // 优先级1: 捏合缩放
              PinchGesture({ fingers: 2 })
                .onActionStart(() => {
                  this.isScrolling = false;
                })
                .onActionUpdate((event: GestureEvent) => {
                  this.scale *= event.scale;
                  this.scale = Math.max(0.5, Math.min(3, this.scale));
                }),
              
              // 优先级2: 拖拽滚动
              PanGesture({ fingers: 1 })
                .onActionStart(() => {
                  this.isScrolling = true;
                })
                .onActionUpdate((event: GestureEvent) => {
                  if (this.isScrolling) {
                    this.offsetX += event.offsetX;
                    this.offsetY += event.offsetY;
                  }
                })
                .onActionEnd(() => {
                  // 添加边界回弹
                  this.applyBoundaryRebound();
                })
            )
          )
        }
        .border(1, Color.Gray)
        .width(300)
        .height(400)
        .clip(true) // 确保超出部分不显示
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }

  // 边界回弹处理
  private applyBoundaryRebound(): void {
    const maxOffset = 100;
    
    animateTo({ duration: 300, curve: Curve.Spring }, () => {
      if (this.offsetX > maxOffset) {
        this.offsetX = maxOffset;
      } else if (this.offsetX < -maxOffset) {
        this.offsetX = -maxOffset;
      }
      
      if (this.offsetY > maxOffset) {
        this.offsetY = maxOffset;
      } else if (this.offsetY < -maxOffset) {
        this.offsetY = -maxOffset;
      }
    });
  }
}

2. 嵌套手势冲突解决

处理父子组件之间的手势冲突:

复制代码
@Component
struct NestedGestureExample {
  @State parentOffset: number = 0;
  @State childOffset: number = 0;
  @State activeGesture: string = 'none';

  build() {
    Column() {
      Text(`当前手势: ${this.activeGesture}`)
        .fontSize(16)
        .margin({ bottom: 20 })
      
      // 父级可滚动区域
      Scroll() {
        Column() {
          Text('父级滚动区域')
            .fontSize(18)
            .margin({ bottom: 20 })
          
          // 子级可拖拽组件
          Column() {
            Text('子级拖拽区域')
              .fontSize(16)
              .padding(20)
              .backgroundColor('#E3F2FD')
              .translate({ x: this.childOffset, y: 0 })
              .gesture(
                PanGesture({ fingers: 1 })
                  .onActionStart(() => {
                    this.activeGesture = 'child-drag';
                  })
                  .onActionUpdate((event: GestureEvent) => {
                    // 只在水平方向拖拽
                    this.childOffset += event.offsetX;
                  })
                  .onActionEnd(() => {
                    this.activeGesture = 'none';
                    // 回弹动画
                    animateTo({ duration: 300 }, () => {
                      this.childOffset = 0;
                    });
                  })
              )
          }
          .height(100)
          .width('100%')
          .margin({ bottom: 20 })
          .onClick(() => {
            console.info('子区域被点击');
          })

          // 其他内容
          ForEach([1, 2, 3, 4, 5], (index) => {
            Text(`内容项 ${index}`)
              .fontSize(14)
              .padding(16)
              .backgroundColor('#F5F5F5')
              .width('100%')
              .margin({ bottom: 8 })
          })
        }
        .width('100%')
      }
      .height(500)
      .gesture(
        PanGesture({ fingers: 1 })
          .onActionStart(() => {
            this.activeGesture = 'parent-scroll';
          })
          .onActionUpdate((event: GestureEvent) => {
            // 只有子级没有活动手势时,父级才处理滚动
            if (this.activeGesture === 'parent-scroll') {
              this.parentOffset += event.offsetY;
            }
          })
          .onActionEnd(() => {
            this.activeGesture = 'none';
          })
      )
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

🎯 四、手势驱动动画实战

1. 手势与动画的平滑衔接

创建基于手势输入的直接操作动画:

复制代码
@Component
struct GestureDrivenAnimation {
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State scale: number = 1.0;
  @State isAnimating: boolean = false;
  private startX: number = 0;
  private startY: number = 0;

  build() {
    Stack() {
      // 可手势操作的卡片
      Column() {
        Text('手势驱动动画')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        
        Text('拖拽、缩放、松手回弹')
          .fontSize(14)
          .opacity(0.7)
          .margin({ top: 8 })
      }
      .padding(24)
      .backgroundColor(Color.White)
      .borderRadius(16)
      .shadow(10)
      .translate({ x: this.offsetX, y: this.offsetY })
      .scale({ x: this.scale, y: this.scale })
      .gesture(
        GestureGroup(GestureMode.Parallel,
          // 拖拽手势
          PanGesture({ fingers: 1 })
            .onActionStart((event: GestureEvent) => {
              this.startX = this.offsetX;
              this.startY = this.offsetY;
              this.isAnimating = false;
            })
            .onActionUpdate((event: GestureEvent) => {
              if (!this.isAnimating) {
                this.offsetX = this.startX + event.offsetX;
                this.offsetY = this.startY + event.offsetY;
              }
            })
            .onActionEnd(() => {
              this.startSpringAnimation();
            }),
          
          // 缩放手势
          PinchGesture({ fingers: 2 })
            .onActionUpdate((event: GestureEvent) => {
              this.scale *= event.scale;
              this.scale = Math.max(0.5, Math.min(3, this.scale));
            })
        )
      )
    }
    .width('100%')
    .height('100%')
    .padding(40)
  }

  // 弹簧回弹动画
  private startSpringAnimation(): void {
    this.isAnimating = true;
    
    animateTo({
      duration: 600,
      curve: Curve.Spring,
      delay: 0
    }, () => {
      this.offsetX = 0;
      this.offsetY = 0;
      this.scale = 1.0;
    });
  }
}

2. 高级手势反馈系统

创建基于手势速度、方向的智能反馈系统:

复制代码
@Component
struct SmartGestureFeedback {
  @State positionX: number = 0;
  @State positionY: number = 0;
  @State rotation: number = 0;
  @State scale: number = 1.0;
  private velocityX: number = 0;
  private velocityY: number = 0;
  private lastTimestamp: number = 0;
  private lastX: number = 0;
  private lastY: number = 0;

  build() {
    Stack() {
      // 智能反馈卡片
      Column() {
        Text('智能手势反馈')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
        
        Text('基于速度的动画反馈')
          .fontSize(12)
          .opacity(0.6)
          .margin({ top: 4 })
      }
      .padding(20)
      .backgroundColor(Color.White)
      .borderRadius(12)
      .shadow(5)
      .translate({ x: this.positionX, y: this.positionY })
      .rotate({ angle: this.rotation })
      .scale({ x: this.scale, y: this.scale })
      .gesture(
        PanGesture({ fingers: 1 })
          .onActionStart((event: GestureEvent) => {
            this.lastTimestamp = Date.now();
            this.lastX = event.offsetX;
            this.lastY = event.offsetY;
            this.velocityX = 0;
            this.velocityY = 0;
          })
          .onActionUpdate((event: GestureEvent) => {
            const now = Date.now();
            const deltaTime = now - this.lastTimestamp;
            
            if (deltaTime > 0) {
              // 计算速度
              this.velocityX = (event.offsetX - this.lastX) / deltaTime;
              this.velocityY = (event.offsetY - this.lastY) / deltaTime;
              
              this.lastX = event.offsetX;
              this.lastY = event.offsetY;
              this.lastTimestamp = now;
            }
            
            this.positionX = event.offsetX;
            this.positionY = event.offsetY;
            
            // 基于速度的旋转效果
            this.rotation = this.velocityX * 2;
            
            // 基于速度的缩放效果
            const speed = Math.sqrt(this.velocityX * this.velocityX + this.velocityY * this.velocityY);
            this.scale = 1 + Math.min(speed * 0.1, 0.3);
          })
          .onActionEnd(() => {
            this.applyMomentumAnimation();
          })
      )
    }
    .width('100%')
    .height('100%')
    .padding(40)
  }

  // 基于动量的动画
  private applyMomentumAnimation(): void {
    const momentumX = this.velocityX * 50;
    const momentumY = this.velocityY * 50;
    
    animateTo({
      duration: 800,
      curve: Curve.Friction,
      delay: 0
    }, () => {
      this.positionX += momentumX;
      this.positionY += momentumY;
      this.rotation = 0;
      this.scale = 1.0;
    }).then(() => {
      // 最终回弹到中心
      animateTo({
        duration: 400,
        curve: Curve.Spring
      }, () => {
        this.positionX = 0;
        this.positionY = 0;
      });
    });
  }
}

📱 五、多指手势高级应用

1. 复杂多指手势识别

实现高级的多指手势识别和处理:

复制代码
@Component
struct MultiFingerGesture {
  @State scale: number = 1.0;
  @State rotation: number = 0;
  @State translationX: number = 0;
  @State translationY: number = 0;
  @State fingerCount: number = 0;
  private initialDistance: number = 0;
  private initialAngle: number = 0;

  build() {
    Column() {
      Text(`手指数量: ${this.fingerCount}`)
        .fontSize(16)
        .margin({ bottom: 20 })
      
      Text('缩放: ' + this.scale.toFixed(2))
        .fontSize(14)
        .margin({ bottom: 8 })
      
      Text('旋转: ' + this.rotation.toFixed(1) + '°')
        .fontSize(14)
        .margin({ bottom: 8 })
      
      Text('平移: X=' + this.translationX.toFixed(1) + ', Y=' + this.translationY.toFixed(1))
        .fontSize(14)
        .margin({ bottom: 20 })

      // 多指操作区域
      Column() {
        Text('多指操作区域')
          .fontSize(16)
          .fontColor(Color.White)
      }
      .width(300)
      .height(300)
      .backgroundColor('#1277ED')
      .scale({ x: this.scale, y: this.scale })
      .rotate({ angle: this.rotation })
      .translate({ x: this.translationX, y: this.translationY })
      .gesture(
        GestureGroup(GestureMode.Parallel,
          // 手指数量跟踪
          Gesture({ fingers: 3 })
            .onActionStart((event: GestureEvent) => {
              this.fingerCount = event.touches.length;
            })
            .onActionUpdate((event: GestureEvent) => {
              this.fingerCount = event.touches.length;
            })
            .onActionEnd(() => {
              this.fingerCount = 0;
            }),
          
          // 捏合缩放
          PinchGesture({ fingers: 2 })
            .onActionStart((event: GestureEvent) => {
              if (event.touches.length >= 2) {
                const dx = event.touches[1].x - event.touches[0].x;
                const dy = event.touches[1].y - event.touches[0].y;
                this.initialDistance = Math.sqrt(dx * dx + dy * dy);
              }
            })
            .onActionUpdate((event: GestureEvent) => {
              if (event.touches.length >= 2) {
                const dx = event.touches[1].x - event.touches[0].x;
                const dy = event.touches[1].y - event.touches[0].y;
                const currentDistance = Math.sqrt(dx * dx + dy * dy);
                
                this.scale *= currentDistance / this.initialDistance;
                this.initialDistance = currentDistance;
                this.scale = Math.max(0.3, Math.min(5, this.scale));
              }
            }),
          
          // 旋转手势
          RotationGesture({ fingers: 2 })
            .onActionStart((event: GestureEvent) => {
              if (event.touches.length >= 2) {
                const dx = event.touches[1].x - event.touches[0].x;
                const dy = event.touches[1].y - event.touches[0].y;
                this.initialAngle = Math.atan2(dy, dx) * 180 / Math.PI;
              }
            })
            .onActionUpdate((event: GestureEvent) => {
              if (event.touches.length >= 2) {
                const dx = event.touches[1].x - event.touches[0].x;
                const dy = event.touches[1].y - event.touches[0].y;
                const currentAngle = Math.atan2(dy, dx) * 180 / Math.PI;
                
                this.rotation += currentAngle - this.initialAngle;
                this.initialAngle = currentAngle;
              }
            }),
          
          // 平移手势
          PanGesture({ fingers: 3 })
            .onActionUpdate((event: GestureEvent) => {
              this.translationX += event.offsetX;
              this.translationY += event.offsetY;
            })
        )
      )
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

2. 手势识别与机器学习集成

集成简单的手势模式识别(概念性示例):

复制代码
@Component
struct GestureRecognition {
  @State recognizedGesture: string = '无';
  @State confidence: number = 0;
  @State trailPoints: Array<{x: number, y: number}> = [];
  private gestureHistory: Array<{x: number, y: number, t: number}> = [];

  build() {
    Column() {
      Text(`识别结果: ${this.recognizedGesture}`)
        .fontSize(18)
        .margin({ bottom: 8 })
      
      Text(`置信度: ${(this.confidence * 100).toFixed(1)}%`)
        .fontSize(14)
        .opacity(0.7)
        .margin({ bottom: 20 })

      // 手势绘制区域
      Canvas(this.getContext())
        .width(300)
        .height(300)
        .backgroundColor('#F8F9FA')
        .border(1, Color.Gray)
        .onTouch((event: TouchEvent) => {
          if (event.type === TouchType.Down) {
            this.trailPoints = [];
            this.gestureHistory = [];
          }
          
          if (event.type === TouchType.Move && event.touches.length > 0) {
            const point = {
              x: event.touches[0].x,
              y: event.touches[0].y,
              t: Date.now()
            };
            
            this.trailPoints.push(point);
            this.gestureHistory.push(point);
            
            // 实时绘制轨迹
            this.drawTrail();
            
            // 每10个点尝试识别一次
            if (this.gestureHistory.length % 10 === 0) {
              this.recognizeGesture();
            }
          }
          
          if (event.type === TouchType.Up) {
            this.finalizeRecognition();
          }
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }

  // 绘制手势轨迹
  private drawTrail(): void {
    const context = this.getContext();
    context.clearRect(0, 0, 300, 300);
    
    if (this.trailPoints.length > 1) {
      context.beginPath();
      context.moveTo(this.trailPoints[0].x, this.trailPoints[0].y);
      
      for (let i = 1; i < this.trailPoints.length; i++) {
        context.lineTo(this.trailPoints[i].x, this.trailPoints[i].y);
      }
      
      context.strokeStyle = '#1277ED';
      context.lineWidth = 3;
      context.stroke();
    }
  }

  // 简单手势识别
  private recognizeGesture(): void {
    if (this.gestureHistory.length < 5) return;
    
    // 简单的手势识别逻辑(实际项目中会使用更复杂的算法)
    const firstPoint = this.gestureHistory[0];
    const lastPoint = this.gestureHistory[this.gestureHistory.length - 1];
    
    const dx = lastPoint.x - firstPoint.x;
    const dy = lastPoint.y - firstPoint.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    
    if (distance < 20) {
      this.recognizedGesture = '点击/轻触';
      this.confidence = 0.6;
      return;
    }
    
    const angle = Math.atan2(dy, dx) * 180 / Math.PI;
    
    if (Math.abs(dx) > Math.abs(dy) * 2) {
      this.recognizedGesture = dx > 0 ? '向右滑动' : '向左滑动';
      this.confidence = 0.8;
    } else if (Math.abs(dy) > Math.abs(dx) * 2) {
      this.recognizedGesture = dy > 0 ? '向下滑动' : '向上滑动';
      this.confidence = 0.8;
    } else {
      this.recognizedGesture = '斜向滑动';
      this.confidence = 0.7;
    }
  }

  // 最终识别
  private finalizeRecognition(): void {
    this.recognizeGesture();
    
    // 添加最终动画反馈
    animateTo({ duration: 300 }, () => {
      this.confidence = Math.min(this.confidence + 0.1, 0.95);
    });
    
    // 2秒后重置
    setTimeout(() => {
      this.recognizedGesture = '无';
      this.confidence = 0;
      this.trailPoints = [];
      this.gestureHistory = [];
    }, 2000);
  }
}

⚡ 六、性能优化与最佳实践

1. 手势性能优化策略

确保手势操作的流畅性和响应性:

复制代码
@Component
struct OptimizedGestureHandling {
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  private lastUpdate: number = 0;
  private updateInterval: number = 16; // ~60fps

  build() {
    Column() {
      Text('优化手势性能')
        .fontSize(18)
        .margin({ bottom: 20 })
      
      Text(`位置: X=${this.offsetX.toFixed(1)}, Y=${this.offsetY.toFixed(1)}`)
        .fontSize(14)
        .margin({ bottom: 20 })

      // 优化后的手势区域
      Column() {
        Text('60FPS流畅拖拽')
          .fontSize(16)
          .fontColor(Color.White)
      }
      .width(200)
      .height(200)
      .backgroundColor('#FF5722')
      .translate({ x: this.offsetX, y: this.offsetY })
      .gesture(
        PanGesture({ fingers: 1 })
          .onActionUpdate((event: GestureEvent) => {
            const now = Date.now();
            
            // 限制更新频率,确保60FPS
            if (now - this.lastUpdate >= this.updateInterval) {
              this.offsetX += event.offsetX;
              this.offsetY += event.offsetY;
              this.lastUpdate = now;
              
              // 使用willChange提示浏览器优化
              this.applyWillChange();
            }
          })
      )
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }

  // 应用性能优化提示
  private applyWillChange(): void {
    // 在实际项目中,这里会使用willChange属性提示浏览器优化
    // 例如:.willChange(WillChange.Transform)
  }

  // 使用Web Worker处理复杂计算
  private processComplexGestureInWorker(): void {
    // 在实际项目中,复杂的手势识别计算可以在Web Worker中执行
    // 避免阻塞主线程,确保UI流畅性
  }
}

2. 内存管理与资源清理

确保手势相关资源的正确管理:

复制代码
class GestureMemoryManager {
  private static activeGestures: Set<gesture.Gesture> = new Set();
  private static gestureListeners: Map<string, Function> = new Map();

  // 注册手势监听器
  static registerGesture(gestureObj: gesture.Gesture, callback: Function): void {
    this.activeGestures.add(gestureObj);
    this.gestureListeners.set(gestureObj.id, callback);
  }

  // 清理不再使用的手势
  static cleanupUnusedGestures(): void {
    for (const gesture of this.activeGestures) {
      if (gesture.isFinished || !gesture.isActive) {
        gesture.destroy();
        this.activeGestures.delete(gesture);
        this.gestureListeners.delete(gesture.id);
      }
    }
  }

  // 紧急停止所有手势
  static emergencyStopAllGestures(): void {
    for (const gesture of this.activeGestures) {
      try {
        gesture.cancel();
        gesture.destroy();
      } catch (error) {
        console.warn('Failed to stop gesture:', error);
      }
    }
    this.activeGestures.clear();
    this.gestureListeners.clear();
  }

  // 预防内存泄漏
  static setupMemoryMonitoring(): void {
    // 定期检查手势内存使用情况
    setInterval(() => {
      this.cleanupUnusedGestures();
    }, 30000);
  }
}

通过掌握这些手势开发技术,你可以在HarmonyOS应用中创建丰富、直观且响应迅速的交互体验,显著提升用户体验和应用品质。

需要参加鸿蒙认证的请点击 鸿蒙认证链接

相关推荐
chensi_072 小时前
uniapp x 鸿蒙开发之调试证书签名配置
服务器·uni-app·harmonyos
梓贤Vigo4 小时前
【Axure高保真原型】休假审批列表
交互·产品经理·axure·原型
豆豆豆大王4 小时前
HTML 与 JavaScript 结合 “点击按钮弹出提示” 的交互功能
javascript·html·交互
Axure组件4 小时前
Axure: 多级多选可交互树状列表
交互·axure·ux
搬砖的小码农_Sky5 小时前
鸿蒙(HarmonyOS)应用开发技能栈
harmonyos·鸿蒙系统
D.....l6 小时前
Hi3861 OpenHarmony鸿蒙开发(嵌入式方向) (一)
华为·harmonyos
代码79729 小时前
【无标题】使用 Playwright 实现跨 Chromium、Firefox、WebKit 浏览器自动化操作
运维·前端·深度学习·华为·自动化
yenggd9 小时前
华为bgp路由的各种控制和团体属性及orf使用案例
网络·华为
COWORKSHOP9 小时前
华为芯片泄密案警示:用Curtain e-locker阻断内部数据泄露
运维·服务器·前端·数据库·安全·华为