HarmonyOS鸿蒙Next开发者技术支持首页下拉进入二楼效果案例

HarmonyOS鸿蒙Next开发者技术支持首页下拉进入二楼效果案例

一、项目概述

1.1 功能特性

  • 基于HarmonyOS 4.0+ API实现
  • 下拉手势触发二楼页面展开
  • 流畅的物理动画效果
  • 支持自定义二楼内容
  • 多种触发模式和动画曲线
  • 高性能手势识别和渲染

二、架构设计

2.1 核心组件结构

下拉二楼系统
├── SecondFloorContainer.ets (主容器)
├── PullToRefresh.ets (下拉刷新组件)
├── SecondFloorContent.ets (二楼内容)
├── PhysicsAnimation.ets (物理动画)
└── GestureRecognizer.ets (手势识别)

2.2 数据模型定义

// SecondFloorModel.ets
// 二楼配置
export interface SecondFloorConfig {
  triggerThreshold: number;        // 触发阈值(px)
  maxPullDistance: number;         // 最大下拉距离(px)
  animationDuration: number;       // 动画时长(ms)
  animationCurve: animation.Curve; // 动画曲线
  enablePhysics: boolean;          // 启用物理效果
  damping: number;                 // 阻尼系数
  stiffness: number;               // 刚度系数
  enableHaptic: boolean;           // 启用触觉反馈
  backgroundColor: ResourceColor;  // 背景颜色
  blurBackground: boolean;         // 模糊背景
  enableOverScroll: boolean;       // 启用越界滚动
}

// 二楼状态
export interface SecondFloorState {
  isActive: boolean;               // 是否激活
  isExpanded: boolean;             // 是否展开
  progress: number;                // 进度(0-1)
  pullDistance: number;            // 下拉距离
  velocity: number;                // 速度
  lastUpdateTime: number;          // 最后更新时间
}

// 默认配置
export class SecondFloorDefaultConfig {
  static readonly DEFAULT_CONFIG: SecondFloorConfig = {
    triggerThreshold: 150,
    maxPullDistance: 400,
    animationDuration: 400,
    animationCurve: animation.Curve.EaseOut,
    enablePhysics: true,
    damping: 15,
    stiffness: 200,
    enableHaptic: true,
    backgroundColor: '#1A1A2E',
    blurBackground: true,
    enableOverScroll: true
  };
}

这里定义了下拉二楼系统的核心数据模型。SecondFloorConfig接口包含所有可配置参数,如触发阈值、动画参数、物理效果等。SecondFloorState接口管理二楼的各种状态。SecondFloorDefaultConfig提供默认配置值。

三、核心实现

3.1 手势识别器

// GestureRecognizer.ets
export class GestureRecognizer {
  private startY: number = 0;
  private currentY: number = 0;
  private startTime: number = 0;
  private isTracking: boolean = false;
  private isVertical: boolean = false;
  
  // 手势回调
  private onGestureStart?: (y: number) => void;
  private onGestureMove?: (y: number, deltaY: number, velocity: number) => void;
  private onGestureEnd?: (y: number, velocity: number, shouldTrigger: boolean) => void;
  
  // 设置回调
  setCallbacks(
    onStart: (y: number) => void,
    onMove: (y: number, deltaY: number, velocity: number) => void,
    onEnd: (y: number, velocity: number, shouldTrigger: boolean) => void
  ): void {
    this.onGestureStart = onStart;
    this.onGestureMove = onMove;
    this.onGestureEnd = onEnd;
  }

GestureRecognizer类负责识别下拉手势。它跟踪触摸起始位置、当前位置和时间,计算移动距离和速度。通过回调函数将手势事件传递给上层组件。

// 触摸开始
  handleTouchStart(event: TouchEvent): void {
    if (event.touches.length !== 1) return;
    
    const touch = event.touches[0];
    this.startY = touch.y;
    this.currentY = touch.y;
    this.startTime = Date.now();
    this.isTracking = true;
    this.isVertical = false;
    
    this.onGestureStart?.(touch.y);
  }
  
  // 触摸移动
  handleTouchMove(event: TouchEvent): void {
    if (!this.isTracking || event.touches.length !== 1) return;
    
    const touch = event.touches[0];
    const deltaY = touch.y - this.currentY;
    this.currentY = touch.y;
    
    // 判断是否为垂直滑动
    if (!this.isVertical) {
      const deltaX = Math.abs(touch.x - this.startY);
      this.isVertical = deltaY > deltaX && deltaY > 10;
    }
    
    if (this.isVertical) {
      event.stopPropagation();
      
      const currentTime = Date.now();
      const deltaTime = currentTime - this.startTime;
      const velocity = deltaY / Math.max(deltaTime, 1);
      
      this.onGestureMove?.(touch.y, deltaY, velocity);
    }
  }

handleTouchStart方法记录触摸起始位置和时间。handleTouchMove方法跟踪触摸移动,判断是否为垂直滑动,计算移动速度和距离,并通过回调通知上层组件。

// 触摸结束
  handleTouchEnd(event: TouchEvent): void {
    if (!this.isTracking) return;
    
    this.isTracking = false;
    
    const currentTime = Date.now();
    const deltaTime = currentTime - this.startTime;
    const totalDeltaY = this.currentY - this.startY;
    const velocity = totalDeltaY / Math.max(deltaTime, 1);
    
    // 判断是否应该触发二楼
    const shouldTrigger = this.isVertical && totalDeltaY > 0 && Math.abs(velocity) > 0.5;
    
    this.onGestureEnd?.(this.currentY, velocity, shouldTrigger);
  }
  
  // 重置状态
  reset(): void {
    this.isTracking = false;
    this.isVertical = false;
    this.startY = 0;
    this.currentY = 0;
  }
}

handleTouchEnd方法处理触摸结束事件,计算最终速度和移动距离,判断是否应该触发二楼展开。reset方法重置手势识别器状态。

3.2 物理动画引擎

// PhysicsAnimation.ets
export class PhysicsAnimation {
  private animationController: animation.Animator = new animation.Animator();
  private currentValue: number = 0;
  private targetValue: number = 0;
  private velocity: number = 0;
  private damping: number = 15;
  private stiffness: number = 200;
  private lastTime: number = 0;
  
  // 动画回调
  private onUpdate?: (value: number, velocity: number) => void;
  private onComplete?: (value: number) => void;
  
  // 设置参数
  setParameters(damping: number, stiffness: number): void {
    this.damping = damping;
    this.stiffness = stiffness;
  }
  
  // 设置回调
  setCallbacks(
    onUpdate: (value: number, velocity: number) => void,
    onComplete: (value: number) => void
  ): void {
    this.onUpdate = onUpdate;
    this.onComplete = onComplete;
  }

PhysicsAnimation类实现基于物理的动画效果,使用弹簧模型计算平滑的动画轨迹。通过阻尼系数控制动画衰减,刚度系数控制回弹力度。

// 开始动画
  start(fromValue: number, toValue: number, initialVelocity: number = 0): void {
    this.currentValue = fromValue;
    this.targetValue = toValue;
    this.velocity = initialVelocity;
    this.lastTime = Date.now();
    
    this.animationController.stop();
    this.animationController.update({
      duration: 0, // 持续动画
      curve: animation.Curve.Linear
    });
    
    this.animationController.onFrame(() => {
      this.updatePhysics();
    });
    
    this.animationController.play();
  }
  
  // 更新物理计算
  private updatePhysics(): void {
    const currentTime = Date.now();
    const deltaTime = Math.min(currentTime - this.lastTime, 50) / 1000; // 转换为秒
    this.lastTime = currentTime;
    
    if (deltaTime <= 0) return;
    
    // 弹簧物理计算
    const displacement = this.targetValue - this.currentValue;
    const springForce = this.stiffness * displacement;
    const dampingForce = this.damping * this.velocity;
    const acceleration = (springForce - dampingForce) / 1; // 质量设为1
    
    this.velocity += acceleration * deltaTime;
    this.currentValue += this.velocity * deltaTime;
    
    // 检查是否完成
    const isAtRest = Math.abs(this.velocity) < 0.1 && Math.abs(displacement) < 0.1;
    
    this.onUpdate?.(this.currentValue, this.velocity);
    
    if (isAtRest) {
      this.stop();
      this.onComplete?.(this.currentValue);
    }
  }
  
  // 停止动画
  stop(): void {
    this.animationController.stop();
  }
  
  // 获取当前值
  getCurrentValue(): number {
    return this.currentValue;
  }
}

start方法开始物理动画,设置起始值、目标值和初始速度。updatePhysics方法基于弹簧模型计算动画的物理特性,每帧更新位置和速度。当速度足够小且接近目标值时停止动画。

3.3 下拉刷新组件

// PullToRefresh.ets
[@Component](/user/Component)
export struct PullToRefresh {
  [@Prop](/user/Prop) config: SecondFloorConfig = SecondFloorDefaultConfig.DEFAULT_CONFIG;
  [@Prop](/user/Prop) onRefresh?: () => void;
  [@Prop](/user/Prop) onSecondFloorTrigger?: () => void;
  
  [@State](/user/State) private pullDistance: number = 0;
  [@State](/user/State) private progress: number = 0;
  [@State](/user/State) private isRefreshing: boolean = false;
  [@State](/user/State) private isSecondFloorTriggered: boolean = false;
  
  private gestureRecognizer: GestureRecognizer = new GestureRecognizer();
  private physicsAnimation: PhysicsAnimation = new PhysicsAnimation();
  private vibration: vibrator.Vibrator | null = null;
  
  aboutToAppear() {
    this.setupGestureCallbacks();
    this.physicsAnimation.setParameters(this.config.damping, this.config.stiffness);
    
    if (this.config.enableHaptic) {
      this.vibration = vibrator.createVibrator();
    }
  }

PullToRefresh组件是下拉二楼系统的核心组件。@State装饰器管理下拉距离、进度、刷新状态等。gestureRecognizer处理手势识别,physicsAnimation处理物理动画。

private setupGestureCallbacks(): void {
    this.gestureRecognizer.setCallbacks(
      // 手势开始
      (y: number) => {
        this.physicsAnimation.stop();
      },
      
      // 手势移动
      (y: number, deltaY: number, velocity: number) => {
        if (this.isRefreshing || this.isSecondFloorTriggered) return;
        
        // 计算下拉距离
        let newPullDistance = this.pullDistance + deltaY;
        
        if (!this.config.enableOverScroll && newPullDistance < 0) {
          newPullDistance = 0;
        }
        
        this.pullDistance = newPullDistance;
        this.progress = Math.min(newPullDistance / this.config.triggerThreshold, 1);
        
        // 触发触觉反馈
        if (this.progress >= 1 && this.config.enableHaptic) {
          this.triggerHapticFeedback();
        }
      },
      
      // 手势结束
      (y: number, velocity: number, shouldTrigger: boolean) => {
        if (this.isRefreshing || this.isSecondFloorTriggered) return;
        
        if (shouldTrigger && this.pullDistance >= this.config.triggerThreshold) {
          // 触发二楼
          this.triggerSecondFloor();
        } else {
          // 回弹动画
          this.animateRebound(velocity);
        }
      }
    );
  }

setupGestureCallbacks方法设置手势识别的回调函数。手势移动时更新下拉距离和进度,达到阈值时触发触觉反馈。手势结束时判断是否触发二楼或执行回弹动画。

// 触发二楼
  private triggerSecondFloor(): void {
    this.isSecondFloorTriggered = true;
    
    // 执行展开动画
    this.physicsAnimation.setCallbacks(
      (value: number) => {
        this.pullDistance = value;
      },
      () => {
        this.onSecondFloorTrigger?.();
      }
    );
    
    this.physicsAnimation.start(
      this.pullDistance,
      this.config.maxPullDistance,
      2 // 初始速度
    );
    
    // 触觉反馈
    if (this.config.enableHaptic) {
      this.triggerHapticFeedback('medium');
    }
  }
  
  // 回弹动画
  private animateRebound(initialVelocity: number = 0): void {
    this.physicsAnimation.setCallbacks(
      (value: number) => {
        this.pullDistance = value;
        this.progress = value / this.config.triggerThreshold;
      },
      () => {
        this.pullDistance = 0;
        this.progress = 0;
      }
    );
    
    this.physicsAnimation.start(this.pullDistance, 0, initialVelocity);
  }
  
  // 触觉反馈
  private triggerHapticFeedback(type: 'light' | 'medium' | 'heavy' = 'light'): void {
    if (!this.vibration) return;
    
    const duration = type === 'light' ? 10 : type === 'medium' ? 20 : 30;
    
    try {
      this.vibration.vibrate(duration);
    } catch (error) {
      console.warn('触觉反馈不可用:', error);
    }
  }

triggerSecondFloor方法触发二楼展开,执行展开动画并调用回调函数。animateRebound方法执行回弹动画,将下拉距离恢复为0。triggerHapticFeedback方法提供不同强度的触觉反馈。

// 构建下拉指示器
  [@Builder](/user/Builder)
  private buildPullIndicator() {
    if (this.pullDistance <= 0) return;
    
    Column({ space: 8 }) {
      // 进度圆环
      Stack({ alignContent: Alignment.Center }) {
        Circle()
          .width(30)
          .height(30)
          .stroke('#FFFFFF40')
          .strokeWidth(2)
          .fill(Color.Transparent)
        
        Circle()
          .width(30)
          .height(30)
          .stroke('#4D94FF')
          .strokeWidth(2)
          .fill(Color.Transparent)
          .strokeDashArray([Math.PI * 30 * this.progress, Math.PI * 30])
          .rotation({ angle: -90 })
        
        // 箭头图标
        if (this.progress < 1) {
          Image($r('app.media.arrow_down'))
            .width(16)
            .height(16)
            .rotate({ angle: this.progress * 180 })
        } else {
          // 二楼图标
          Image($r('app.media.second_floor'))
            .width(16)
            .height(16)
        }
      }
      
      // 提示文本
      Text(this.progress < 1 ? '下拉刷新' : '释放进入二楼')
        .fontSize(12)
        .fontColor('#FFFFFF')
        .opacity(this.progress * 0.8)
    }
    .position({ x: '50%', y: 20 })
    .translate({ y: -this.pullDistance / 2 })
    .opacity(Math.min(this.pullDistance / 50, 1))
  }

buildPullIndicator方法构建下拉指示器,显示进度圆环、箭头图标和提示文本。进度圆环使用strokeDashArray实现动态进度效果,箭头根据进度旋转,达到阈值时切换为二楼图标。

build() {
    Stack({ alignContent: Alignment.TopStart }) {
      // 内容区域
      Column()
        .width('100%')
        .height('100%')
        .translate({ y: this.pullDistance })
        .clip(true)
      {
        // 这里放置主要内容
        this.buildMainContent()
      }
      
      // 下拉指示器
      this.buildPullIndicator()
    }
    .width('100%')
    .height('100%')
    .onTouch((event: TouchEvent) => {
      if (event.type === TouchType.Down) {
        this.gestureRecognizer.handleTouchStart(event);
      } else if (event.type === TouchType.Move) {
        this.gestureRecognizer.handleTouchMove(event);
      } else if (event.type === TouchType.Up) {
        this.gestureRecognizer.handleTouchEnd(event);
      }
    })
  }
}

build方法创建组件布局,主要内容区域根据下拉距离平移,下拉指示器固定在顶部。绑定触摸事件处理函数,将事件传递给手势识别器。

3.4 二楼内容组件

// SecondFloorContent.ets
[@Component](/user/Component)
export struct SecondFloorContent {
  [@Prop](/user/Prop) config: SecondFloorConfig = SecondFloorDefaultConfig.DEFAULT_CONFIG;
  [@Prop](/user/Prop) isExpanded: boolean = false;
  [@Prop](/user/Prop) onClose?: () => void;
  
  [@State](/user/State) private contentHeight: number = 0;
  [@State](/user/State) private scrollOffset: number = 0;
  [@State](/user/State) private isScrolling: boolean = false;
  
  private scroller: Scroller = new Scroller();
  private closeThreshold: number = 100;
  
  // 构建二楼头部
  [@Builder](/user/Builder)
  private buildSecondFloorHeader() {
    Column({ space: 12 }) {
      // 标题栏
      Row({ space: 8 }) {
        Text('二楼')
          .fontSize(20)
          .fontColor(Color.White)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
        
        Button('关闭')
          .fontSize(14)
          .backgroundColor('#FFFFFF20')
          .onClick(() => {
            this.onClose?.();
          })
      }
      
      // 搜索栏
      Row({ space: 8 }) {
        TextInput({ placeholder: '搜索二楼内容...' })
          .layoutWeight(1)
          .backgroundColor('#FFFFFF10')
          .borderRadius(20

更多关于HarmonyOS鸿蒙Next开发者技术支持首页下拉进入二楼效果案例的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

鸿蒙Next实现首页下拉进入二楼效果

实现原理

主要使用ArkTS的弹性布局和手势事件。通过Scroll组件嵌套Column,结合onScrollEdge监听滚动边界事件。当滚动到顶部继续下拉时,触发二楼界面显示。

关键技术

  • 条件渲染:利用if/else条件渲染控制二楼内容显隐
  • 动画过渡:配合animateTo动画实现平滑过渡
  • 手势识别:使用PanGesture识别下拉动作,计算偏移量控制动画执行

关键属性配置

Scroll组件中需要配置以下关键属性:

  • edgeEffect
  • scrollBar
  • scrollable

实现步骤

  1. 创建Scroll组件嵌套Column布局
  2. 设置onScrollEdge事件监听滚动边界
  3. 实现下拉手势识别和偏移量计算
  4. 使用条件渲染控制二楼界面显示
  5. 添加animateTo动画实现平滑过渡效果

更多关于HarmonyOS鸿蒙Next开发者技术支持首页下拉进入二楼效果案例的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这是一个非常详细且专业的HarmonyOS Next下拉二楼效果实现方案。从架构设计、核心组件到高级特性都覆盖得很全面,代码结构清晰,遵循了ArkTS的最佳实践。

技术亮点分析:

  1. 架构清晰:组件职责划分明确(GestureRecognizerPhysicsAnimationPullToRefreshSecondFloorContent),符合高内聚、低耦合的设计原则。
  2. 物理动画引擎:自定义的 PhysicsAnimation 类基于弹簧模型实现,提供了阻尼、刚度等可调参数,是实现流畅、自然回弹效果的核心。
  3. 完整的手势识别GestureRecognizer 类不仅处理了垂直滑动的判断,还计算了移动速度,这对于决定是否触发二楼以及回弹动画的初始速度至关重要。
  4. 状态管理:通过 SecondFloorStateSecondFloorConfig 接口清晰地管理了组件的状态和配置,易于扩展和维护。
  5. 用户体验细节
    • 触觉反馈:在达到触发阈值和成功触发二楼时提供了不同强度的振动反馈。
    • 视觉反馈:下拉指示器中的进度圆环、箭头旋转和文本提示,清晰地引导了用户操作。
    • 关闭手势:在二楼页面内提供了向下滑动关闭的便捷操作。
  6. 性能考虑:提到了使用 transform 进行动画优化、手势节流等最佳实践,并提供了 PerformanceMonitor 的示例,显示了良好的性能意识。
  7. 可访问性:考虑了为屏幕阅读器添加标签和提示,提升了应用的无障碍使用体验。

代码质量:

  • 使用了ArkTS的装饰器(@Component, @State, @Prop, @Builder)来声明式地构建UI和管理状态。
  • 动画使用了HarmonyOS的 animation API,并支持自定义曲线。
  • 交互事件处理(onTouch, PanGesture)与手势识别器结合得当。

总结: 这份实现方案已经达到了生产级应用的标准。它不仅仅是一个简单的UI效果,而是一个完整的、可复用的交互模块,包含了手势、动画、状态、反馈和性能的全面考量。开发者可以基于此方案快速集成到自己的HarmonyOS Next应用中,并根据实际需求调整配置和二楼内容。

回到顶部