HarmonyOS鸿蒙Next中基于Swiper的自定义Stepper组件案例

HarmonyOS鸿蒙Next中基于Swiper的自定义Stepper组件案例

一、项目概述

1.1 功能特性

  • 基于HarmonyOS 4.0+ API实现
  • 使用Swiper组件作为Stepper容器
  • 支持手势滑动和按钮切换步骤
  • 自定义步骤指示器和进度条
  • 流畅的页面切换动画
  • 完整的步骤状态管理

二、架构设计

2.1 核心组件结构

Swiper Stepper系统
├── SwiperStepper.ets (主组件)
├── StepperIndicator.ets (步骤指示器)
├── StepperContent.ets (步骤内容)
├── StepperNavigation.ets (导航控制)
└── StepperManager.ets (状态管理)

2.2 数据模型定义

// SwiperStepperModel.ets
// 步骤数据模型
export interface StepperItem {
  id: string;
  title: string;
  subtitle?: string;
  description?: string;
  icon?: Resource;
  status: 'pending' | 'active' | 'completed' | 'error' | 'disabled';
  content: CustomBuilder; // 步骤内容构建器
  validate?: () => boolean; // 验证函数
}

// Swiper Stepper配置
export interface SwiperStepperConfig {
  indicatorType: 'dots' | 'numbers' | 'progress' | 'custom'; // 指示器类型
  indicatorPosition: 'top' | 'bottom' | 'left' | 'right'; // 指示器位置
  showNavigation: boolean; // 显示导航按钮
  enableSwipe: boolean; // 启用手势滑动
  loop: boolean; // 循环切换
  autoPlay: boolean; // 自动播放(演示用)
  animationDuration: number; // 动画时长
  validateSteps: boolean; // 验证步骤
  showStepNumbers: boolean; // 显示步骤编号
}

// 默认配置
export class SwiperStepperDefaultConfig {
  static readonly DEFAULT_CONFIG: SwiperStepperConfig = {
    indicatorType: 'dots',
    indicatorPosition: 'top',
    showNavigation: true,
    enableSwipe: true,
    loop: false,
    autoPlay: false,
    animationDuration: 300,
    validateSteps: true,
    showStepNumbers: true
  };
}

这里定义了基于Swiper的Stepper组件数据模型。StepperItem接口包含步骤内容和验证函数。SwiperStepperConfig接口针对Swiper特性进行配置,如指示器类型、位置、循环切换等。

三、核心实现

3.1 主组件 - SwiperStepper

// SwiperStepper.ets
@Component
export struct SwiperStepper {
  // 步骤数据
  @Prop items: StepperItem[] = [];
  
  // 配置
  @Prop config: SwiperStepperConfig = SwiperStepperDefaultConfig.DEFAULT_CONFIG;
  
  // 事件回调
  @Prop onStepChange?: (currentStep: number, previousStep: number) => void;
  @Prop onStepComplete?: (stepIndex: number) => void;
  @Prop onStepError?: (stepIndex: number) => void;
  
  [@State](/user/State) private currentIndex: number = 0;
  [@State](/user/State) private completedSteps: number[] = [];
  [@State](/user/State) private errorSteps: number[] = [];
  
  private swiperController: SwiperController = new SwiperController();
  private autoPlayTimer: number = 0;
  
  aboutToAppear() {
    if (this.config.autoPlay) {
      this.startAutoPlay();
    }
  }

SwiperStepper是主组件,使用SwiperController控制页面切换。@State装饰器管理当前索引和步骤状态。支持自动播放功能用于演示。

// 开始自动播放
  private startAutoPlay(): void {
    this.stopAutoPlay();
    
    this.autoPlayTimer = setInterval(() => {
      if (this.currentIndex < this.items.length - 1 || this.config.loop) {
        this.next();
      } else {
        this.stopAutoPlay();
      }
    }, 3000);
  }
  
  // 停止自动播放
  private stopAutoPlay(): void {
    if (this.autoPlayTimer) {
      clearInterval(this.autoPlayTimer);
      this.autoPlayTimer = 0;
    }
  }
  
  // 下一步
  next(): void {
    if (this.currentIndex < this.items.length - 1) {
      // 验证当前步骤
      if (this.config.validateSteps && !this.validateCurrentStep()) {
        return;
      }
      
      const previousIndex = this.currentIndex;
      this.currentIndex++;
      
      // 标记当前步骤为完成
      this.completeStep(previousIndex);
      
      // 切换到下一步
      this.swiperController.showNext();
      
      this.onStepChange?.(this.currentIndex, previousIndex);
    }
  }
  
  // 上一步
  previous(): void {
    if (this.currentIndex > 0) {
      const previousIndex = this.currentIndex;
      this.currentIndex--;
      
      this.swiperController.showPrevious();
      this.onStepChange?.(this.currentIndex, previousIndex);
    }
  }

startAutoPlay和stopAutoPlay方法控制自动播放。next方法包含步骤验证逻辑,验证通过后才切换到下一步。previous方法直接切换到上一步。

// 跳转到指定步骤
  goToStep(index: number): boolean {
    if (index < 0 || index >= this.items.length) return false;
    
    // 验证是否可以跳转
    if (this.config.validateSteps && !this.canGoToStep(index)) {
      return false;
    }
    
    const previousIndex = this.currentIndex;
    this.currentIndex = index;
    
    this.swiperController.showIndex(index);
    this.onStepChange?.(this.currentIndex, previousIndex);
    
    return true;
  }
  
  // 验证是否可以跳转到指定步骤
  private canGoToStep(targetIndex: number): boolean {
    // 只能跳转到已完成的步骤或相邻步骤
    if (targetIndex > this.currentIndex) {
      for (let i = this.currentIndex + 1; i < targetIndex; i++) {
        if (!this.completedSteps.includes(i)) {
          return false;
        }
      }
    }
    
    return true;
  }
  
  // 验证当前步骤
  private validateCurrentStep(): boolean {
    const item = this.items[this.currentIndex];
    
    if (item.validate) {
      const isValid = item.validate();
      
      if (!isValid) {
        this.markStepAsError(this.currentIndex);
        return false;
      }
    }
    
    return true;
  }

goToStep方法支持直接跳转到指定步骤,包含跳转验证逻辑。canGoToStep方法确保只能跳转到已完成的步骤或相邻步骤。validateCurrentStep方法执行步骤验证。

// 完成步骤
  private completeStep(index: number): void {
    if (!this.completedSteps.includes(index)) {
      this.completedSteps = [...this.completedSteps, index];
      this.onStepComplete?.(index);
    }
  }
  
  // 标记步骤为错误
  private markStepAsError(index: number): void {
    if (!this.errorSteps.includes(index)) {
      this.errorSteps = [...this.errorSteps, index];
      this.onStepError?.(index);
    }
  }
  
  // Swiper变化回调
  private onSwiperChange(index: number): void {
    if (index !== this.currentIndex) {
      const previousIndex = this.currentIndex;
      this.currentIndex = index;
      this.onStepChange?.(this.currentIndex, previousIndex);
    }
  }

completeStep和markStepAsError方法管理步骤状态。onSwiperChange方法处理Swiper页面切换事件,同步当前索引。

// 构建步骤指示器
  [@Builder](/user/Builder)
  private buildStepperIndicator() {
    if (this.config.indicatorType === 'dots') {
      this.buildDotsIndicator();
    } else if (this.config.indicatorType === 'numbers') {
      this.buildNumbersIndicator();
    } else if (this.config.indicatorType === 'progress') {
      this.buildProgressIndicator();
    }
  }
  
  // 构建圆点指示器
  [@Builder](/user/Builder)
  private buildDotsIndicator() {
    Row({ space: 8 }) {
      ForEach(this.items, (_, index: number) => {
        Circle()
          .width(8)
          .height(8)
          .fill(index === this.currentIndex ? '#4D94FF' : 
                this.completedSteps.includes(index) ? '#96CEB4' :
                this.errorSteps.includes(index) ? '#FF6B6B' : '#E0E0E0')
          .animation({
            duration: this.config.animationDuration,
            curve: animation.Curve.EaseInOut
          })
      })
    }
    .padding(12)
    .backgroundColor('#FFFFFF')
    .borderRadius(20)
    .shadow({ radius: 2, color: '#00000010', offsetX: 0, offsetY: 2 })
  }

buildStepperIndicator方法根据配置构建不同类型的指示器。buildDotsIndicator构建圆点指示器,不同状态使用不同颜色。

// 构建数字指示器
  [@Builder](/user/Builder)
  private buildNumbersIndicator() {
    Row({ space: 4 }) {
      ForEach(this.items, (item, index: number) => {
        Column({ space: 2 }) {
          Text((index + 1).toString())
            .fontSize(14)
            .fontColor(index === this.currentIndex ? '#FFFFFF' : 
                      this.completedSteps.includes(index) ? '#96CEB4' :
                      this.errorSteps.includes(index) ? '#FF6B6B' : '#666666')
            .backgroundColor(index === this.currentIndex ? '#4D94FF' : 
                            this.completedSteps.includes(index) ? '#E8F7F6' :
                            this.errorSteps.includes(index) ? '#FFE8E8' : '#F5F5F5')
            .padding({ left: 8, right: 8, top: 4, bottom: 4 })
            .borderRadius(12)
          
          if (this.config.showStepNumbers && item.title) {
            Text(item.title)
              .fontSize(10)
              .fontColor('#666666')
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
          }
        }
        .width(60)
      })
    }
    .padding(8)
  }
  
  // 构建进度条指示器
  [@Builder](/user/Builder)
  private buildProgressIndicator() {
    const progress = (this.currentIndex + 1) / this.items.length * 100;
    
    Column({ space: 8 }) {
      Row({ space: 4 }) {
        Text('进度')
          .fontSize(12)
          .fontColor('#666666')
        
        Text(`${Math.round(progress)}%`)
          .fontSize(12)
          .fontColor('#4D94FF')
          .fontWeight(FontWeight.Bold)
      }
      
      Row()
        .width('100%')
        .height(4)
        .backgroundColor('#E0E0E0')
        .borderRadius(2)
        .overlay(
          Row()
            .width(`${progress}%`)
            .height('100%')
            .backgroundColor('#4D94FF')
            .borderRadius(2)
            .animation({
              duration: this.config.animationDuration,
              curve: animation.Curve.EaseInOut
            })
        )
    }
    .padding(12)
    .backgroundColor('#FFFFFF')
    .borderRadius(8)
  }

buildNumbersIndicator构建数字指示器,显示步骤编号和标题。buildProgressIndicator构建进度条指示器,显示整体进度百分比。

// 构建导航控制
  [@Builder](/user/Builder)
  private buildNavigation() {
    if (!this.config.showNavigation) return;
    
    Row({ space: 12 }) {
      Button('上一步')
        .layoutWeight(1)
        .backgroundColor(this.currentIndex > 0 ? '#4D94FF' : '#CCCCCC')
        .enabled(this.currentIndex > 0)
        .onClick(() => this.previous())
      
      Button(this.currentIndex === this.items.length - 1 ? '完成' : '下一步')
        .layoutWeight(1)
        .backgroundColor(this.currentIndex === this.items.length - 1 ? '#96CEB4' : '#4D94FF')
        .onClick(() => {
          if (this.currentIndex === this.items.length - 1) {
            this.completeStep(this.currentIndex);
            console.log('流程完成!');
          } else {
            this.next();
          }
        })
    }
    .padding(16)
    .backgroundColor('#FFFFFF')
  }

buildNavigation方法构建导航按钮,根据当前步骤显示不同的按钮文本和状态。最后一步显示"完成"按钮。

// 构建步骤内容
  [@Builder](/user/Builder)
  private buildStepContent(item: StepperItem, index: number) {
    Column({ space: 16 }) {
      // 步骤标题
      Column({ space: 8 }) {
        Text(item.title)
          .fontSize(20)
          .fontColor(Color.Black)
          .fontWeight(FontWeight.Bold)
        
        if (item.subtitle) {
          Text(item.subtitle)
            .fontSize(14)
            .fontColor('#666666')
        }
        
        if (item.description) {
          Text(item.description)
            .fontSize(12)
            .fontColor('#999999')
            .margin({ top: 8 })
        }
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#F8F8F8')
      .borderRadius(12)
      
      // 步骤内容
      item.content()
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }

buildStepContent方法构建步骤内容区域,包含标题、副标题、描述和自定义内容构建器。

build() {
    Column() {
      // 步骤指示器(顶部)
      if (this.config.indicatorPosition === 'top') {
        this.buildStepperIndicator()
          .margin({ top: 16, bottom: 8 })
      }
      
      // Swiper内容区域
      Swiper(this.swiperController) {
        ForEach(this.items, (item: StepperItem, index: number) => {
          SwiperItem() {
            this.buildStepContent(item, index)
          }
        })
      }
      .index(this.currentIndex)
      .autoPlay(this.config.autoPlay)
      .interval(3000)
      .duration(this.config.animationDuration)
      .loop(this.config.loop)
      .vertical(false)
      .indicator(false) // 使用自定义指示器
      .enableSwipe(this.config.enableSwipe)
      .onChange((index: number) => {
        this.onSwiperChange(index);
      })
      .layoutWeight(1)
      
      // 步骤指示器(底部)
      if (this.config.indicatorPosition === 'bottom') {
        this.buildStepperIndicator()
          .margin({ top: 8, bottom: 16 })
      }
      
      // 导航控制
      this.buildNavigation()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

build方法创建完整的Stepper布局,根据配置显示顶部或底部指示器。Swiper组件作为步骤内容容器,支持所有Swiper原生功能。

3.2 使用示例

// SwiperStepperDemo.ets
@Entry
@Component
export struct SwiperStepperDemo {
  [@State](/user/State) private currentStep: number = 0;
  
  // 自定义配置
  private customConfig: SwiperStepperConfig = {
    ...SwiperStepperDefaultConfig.DEFAULT_CONFIG,
    indicatorType: 'numbers',
    indicatorPosition: 'top',
    enableSwipe: true,
    validateSteps: true
  };
  
  // 步骤数据
  [@State](/user/State) private stepperItems: StepperItem[] = [
    {
      id: '1',
      title: '基本信息',
      subtitle: '填写您的基本信息',
      description: '请确保信息准确无误',
      status: 'active',
      content: this.buildBasicInfoContent(),
      validate: () => this.validateBasicInfo()
    },
    {
      id: '2',
      title: '详细资料',
      subtitle: '完善您的详细资料',
      description: '这些信息将用于个性化推荐',
      status: 'pending',
      content: this.buildDetailInfoContent(),
      validate: () => this.validateDetailInfo()
    },
    {
      id: '3',
      title: '偏好设置',
      subtitle: '设置您的个人偏好',
      description: '根据您的喜好定制体验',
      status: 'pending',
      content: this.buildPreferenceContent(),
      validate: () => true
    },
    {
      id: '4',
      title: '确认信息',
      subtitle: '确认所有信息正确',
      description: '请仔细核对以下信息',
      status: 'pending',
      content: this.buildConfirmationContent(),
      validate: () => true
    }
  ];

SwiperStepperDemo是演示入口组件,定义自定义配置和步骤数据。每个步骤包含内容构建器和验证函数。

// 构建基本信息内容
  [@Builder](/user/Builder)
  buildBasicInfoContent() {
    Column({ space: 16 }) {
      TextInput({ placeholder: '请输入姓名' })
        .width('100%')
        .padding(12)
        .backgroundColor(Color.White)
        .borderRadius(8)
        .onChange((value: string) => {
          this.basicInfo.name = value;
        })
      
      TextInput({ placeholder: '请输入邮箱' })
        .width('100%')
        .padding(12)
        .backgroundColor(Color.White)
        .borderRadius(8)
        .onChange((value: string) => {
          this.basicInfo.email = value;
        })
      
      TextInput({ placeholder: '请输入电话' })
        .width('100%')
        .padding(12)
        .backgroundColor(Color.White)
        .borderRadius(8)
        .onChange((value: string) => {
          this.basicInfo.phone = value;
        })
      
      Text('所有字段均为必填项')
        .fontSize(12)
        .fontColor('#666666')
        .alignSelf(ItemAlign.Start)
    }
  }
  
  // 验证基本信息
  private validateBasicInfo(): boolean {

更多关于HarmonyOS鸿蒙Next中基于Swiper的自定义Stepper组件案例的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

写的真好,支持

更多关于HarmonyOS鸿蒙Next中基于Swiper的自定义Stepper组件案例的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


厉害,期待收录到官方codelab里

HarmonyOS Next中自定义Stepper组件可通过Swiper实现。在Swiper的每个子项中定义Stepper的UI布局,结合状态管理(如@State)控制当前步骤索引。使用Swiper的onChange事件监听步骤切换,并更新索引状态。通过条件渲染或样式绑定高亮当前步骤。

这是一个非常专业且完整的HarmonyOS Next自定义Stepper组件实现案例。你巧妙地利用了Swiper组件的核心特性(如SwiperController、手势滑动、循环切换)来构建一个功能强大的多步骤流程组件,架构设计清晰,代码质量很高。

核心亮点分析:

  1. 架构设计优秀:组件职责分离明确(SwiperStepper主控、状态管理、指示器、导航、内容构建),符合ArkUI的最佳实践,易于维护和扩展。
  2. 状态管理完善:通过@State装饰器清晰管理了当前步骤索引(currentIndex)、已完成步骤(completedSteps)和错误步骤(errorSteps),状态驱动UI更新的模式运用得当。
  3. 功能全面
    • 导航灵活:支持手势滑动、按钮切换(上一步/下一步)以及直接跳转(goToStep)。
    • 验证机制:集成了步骤级验证函数(validate),并在next()goToStep()方法中实现了验证逻辑,确保了流程的严谨性。
    • 丰富的指示器:提供了圆点(dots)、数字(numbers)、进度条(progress)三种内置样式,并通过CustomIndicator展示了完全自定义的能力。
    • 配置化SwiperStepperConfig接口使得组件行为高度可配置,适应不同场景。
  4. 用户体验细节到位
    • 不同步骤状态(待处理、激活、完成、错误)有明确的视觉区分(颜色)。
    • 集成了流畅的动画效果(animation)。
    • ResponsiveSwiperStepper中考虑了响应式设计,根据屏幕尺寸适配布局。
    • 提供了可访问性(accessibility)支持,这点尤为重要。

代码与HarmonyOS Next的契合度:

  • 正确使用了ArkTS语法,如@Component@State@Prop@Builder装饰器。
  • 合理运用了ArkUI声明式UI范式。
  • SwiperController的使用是控制Swiper页面的标准方式。
  • 事件回调(onStepChange, onStepComplete)的设计符合HarmonyOS的事件通信模式。

潜在优化点探讨:

  1. 性能:对于包含复杂内容或大量步骤的场景,可以考虑启用Swiper的cachedCount属性来预加载相邻项,或对步骤内容进行更精细的懒加载优化。
  2. 状态同步:在SwiperStepper.etsonSwiperChange方法中,直接通过手势滑动的索引更新了currentIndex。如果enableSwipetruevalidateSteps也为true,这里可能需要加入与next()方法类似的验证逻辑,以保持行为一致性。
  3. 类型安全StepperItem中的content类型为CustomBuilder,这提供了灵活性。在更严格的场景下,可以定义更具体的Builder签名。

总结: 你提供的这个案例远超一个简单的示例,它是一套具备生产可用性的、工程化的组件解决方案。它清晰地展示了如何在HarmonyOS Next中,基于现有基础组件(Swiper)构建出更高级、更复杂的自定义组件,涵盖了从数据模型、状态管理、UI构建到事件处理、响应式设计和可访问性的完整开发生命周期。这对于其他开发者学习HarmonyOS Next的复杂组件开发具有很高的参考价值。

回到顶部