HarmonyOS鸿蒙Next开发者技术支持骨架屏实现案例

HarmonyOS鸿蒙Next开发者技术支持骨架屏实现案例

案例概述

骨架屏(Skeleton Screen)是一种在数据加载期间显示的页面框架,提升用户体验。本案例演示如何使用HarmonyOS最新API实现一个优雅的骨架屏效果。

一、架构设计

1.1 核心组件

// SkeletonScreen.ets
// 骨架屏核心组件,包含动画和布局
@Component
export struct SkeletonScreen { }

// ShimmerEffect.ets  
// 流光动画效果组件
@Component
export struct ShimmerEffect { }

// SkeletonManager.ets
// 骨架屏状态管理器
export class SkeletonManager { }

二、实现步骤详解

步骤1:定义数据模型和配置

// 1. 定义骨架屏项类型
export interface SkeletonItem {
  type: 'rectangle' | 'circle' | 'text-line' | 'custom';
  width: Length;
  height: Length;
  borderRadius?: number;
  margin?: Padding | Margin;
  animationDelay?: number;  // 动画延迟
}

// 2. 动画配置
export interface SkeletonAnimationConfig {
  shimmerDuration: number;      // 流光动画时长
  fadeDuration: number;         // 淡入淡出时长
  shimmerWidth: number;         // 流光宽度
  shimmerColor: ResourceColor;  // 流光颜色
  baseColor: ResourceColor;     // 基础颜色
  highlightColor: ResourceColor; // 高亮颜色
}

// 3. 骨架屏配置
export class SkeletonConfig {
  static readonly DEFAULT_CONFIG: SkeletonAnimationConfig = {
    shimmerDuration: 1500,
    fadeDuration: 500,
    shimmerWidth: 100,
    shimmerColor: '#FFFFFF40',
    baseColor: '#F0F0F0',
    highlightColor: '#F5F5F5'
  };
}
  • 定义骨架屏的数据结构,支持多种形状类型
  • 配置动画参数,便于统一管理
  • 使用TypeScript接口确保类型安全

步骤2:实现流光动画效果

// ShimmerEffect.ets
@Component
export struct ShimmerEffect {
  @State private shimmerOffset: number = -100;  // 流光位置
  
  // 动画控制器
  private animationController: animation.Animator = new animation.Animator();
  
  @Prop config: SkeletonAnimationConfig = SkeletonConfig.DEFAULT_CONFIG;
  @Prop isActive: boolean = true;  // 是否激活动画
  
  aboutToAppear() {
    if (this.isActive) {
      this.startShimmerAnimation();
    }
  }
  
  // 启动流光动画
  private startShimmerAnimation(): void {
    this.animationController.update({
      duration: this.config.shimmerDuration,
      iterations: -1,  // 无限循环
      curve: animation.Curve.Linear
    });
    
    this.animationController.onFrame((value: number) => {
      this.shimmerOffset = 200 * value - 100;  // 计算流光位置
    });
    
    this.animationController.play();
  }
  
  build() {
    // 使用LinearGradient实现流光效果
    Row()
      .width(this.config.shimmerWidth)
      .height('100%')
      .backgroundImage(
        new LinearGradient({
          angle: 0,
          colors: [
            [Color.Transparent, 0],
            [this.config.shimmerColor, 0.5],
            [Color.Transparent, 1]
          ]
        })
      )
      .translate({ x: `${this.shimmerOffset}%` })
  }
}
  • 使用HarmonyOS的animation.Animator实现平滑动画
  • 通过LinearGradient创建渐变流光效果
  • 支持无限循环动画,提升加载体验

步骤3:实现基础骨架屏项

// SkeletonItem.ets
@Component
export struct SkeletonItem {
  @State private isAnimating: boolean = false;
  
  @Prop itemConfig: SkeletonItem;  // 骨架项配置
  @Prop animationConfig: SkeletonAnimationConfig = SkeletonConfig.DEFAULT_CONFIG;
  @Prop enableShimmer: boolean = true;  // 是否启用流光效果
  
  aboutToAppear() {
    // 延迟启动动画
    setTimeout(() => {
      this.isAnimating = true;
    }, this.itemConfig.animationDelay || 0);
  }
  
  @Builder
  private buildSkeletonContent() {
    // 根据类型构建不同的骨架形状
    switch (this.itemConfig.type) {
      case 'rectangle':
        this.buildRectangle();
        break;
      case 'circle':
        this.buildCircle();
        break;
      case 'text-line':
        this.buildTextLine();
        break;
      default:
        this.buildRectangle();
    }
  }
  
  @Builder
  private buildRectangle() {
    Column()
      .width(this.itemConfig.width)
      .height(this.itemConfig.height)
      .backgroundColor(this.animationConfig.baseColor)
      .borderRadius(this.itemConfig.borderRadius || 4)
      .overflow(Overflow.Hidden)  // 重要:确保流光不溢出
      .overlay(
        // 流光效果叠加层
        this.enableShimmer && this.isAnimating ?
        ShimmerEffect({
          config: this.animationConfig,
          isActive: this.isAnimating
        }) : null
      )
  }
  
  @Builder
  private buildCircle() {
    Circle()
      .width(this.itemConfig.width)
      .height(this.itemConfig.height)
      .fill(this.animationConfig.baseColor)
      .overlay(
        this.enableShimmer && this.isAnimating ?
        ShimmerEffect({
          config: this.animationConfig,
          isActive: this.isAnimating
        }) : null
      )
  }
  
  @Builder
  private buildTextLine() {
    // 文本行骨架,模拟段落
    Column({ space: 4 }) {
      Rectangle()
        .width('100%')
        .height(16)
        .fill(this.animationConfig.baseColor)
        .borderRadius(8)
      
      Rectangle()
        .width('80%')
        .height(16)
        .fill(this.animationConfig.baseColor)
        .borderRadius(8)
      
      Rectangle()
        .width('60%')
        .height(16)
        .fill(this.animationConfig.baseColor)
        .borderRadius(8)
    }
  }
  
  build() {
    Column()
      .margin(this.itemConfig.margin || {})
      .opacity(this.isAnimating ? 1 : 0)
      .animation({
        duration: this.animationConfig.fadeDuration,
        curve: animation.Curve.EaseOut
      })
    {
      this.buildSkeletonContent()
    }
  }
}
  • 支持多种骨架形状:矩形、圆形、文本行
  • 使用overlay属性叠加流光效果
  • 实现淡入动画,提升视觉体验
  • 可配置延迟动画,创建错落有致的加载效果

步骤4:实现完整骨架屏组件

// SkeletonScreen.ets
@Component
export struct SkeletonScreen {
  @State private isLoading: boolean = true;  // 加载状态
  
  // 骨架布局配置
  @Prop skeletonLayout: SkeletonItem[] = [];
  @Prop contentBuilder: CustomBuilder;  // 实际内容构建器
  @Prop animationConfig: SkeletonAnimationConfig = SkeletonConfig.DEFAULT_CONFIG;
  @Prop showShimmer: boolean = true;  // 是否显示流光
  
  // 加载完成回调
  @Prop onLoadComplete?: () => void;
  
  // 模拟数据加载
  async loadData(): Promise<void> {
    // 显示骨架屏
    this.isLoading = true;
    
    try {
      // 模拟异步数据加载
      await this.simulateDataFetch();
      
      // 数据加载完成
      this.isLoading = false;
      this.onLoadComplete?.();
    } catch (error) {
      // 处理错误
      this.isLoading = false;
    }
  }
  
  private async simulateDataFetch(): Promise<void> {
    return new Promise(resolve => {
      setTimeout(resolve, 2000);  // 模拟2秒加载
    });
  }
  
  @Builder
  private buildSkeletonLayout() {
    Column({ space: 12 }) {
      ForEach(this.skeletonLayout, (item: SkeletonItem, index: number) => {
        SkeletonItem({
          itemConfig: {
            ...item,
            animationDelay: index * 100  // 错开动画延迟
          },
          animationConfig: this.animationConfig,
          enableShimmer: this.showShimmer
        })
      })
    }
    .width('100%')
    .padding(16)
  }
  
  @Builder
  private buildContent() {
    // 使用@BuilderParam构建实际内容
    this.contentBuilder()
  }
  
  aboutToAppear() {
    // 组件出现时开始加载
    this.loadData();
  }
  
  build() {
    Stack({ alignContent: Alignment.TopStart }) {
      // 实际内容
      this.buildContent()
        .opacity(this.isLoading ? 0 : 1)
        .animation({
          duration: 300,
          curve: animation.Curve.EaseInOut
        })
      
      // 骨架屏
      this.buildSkeletonLayout()
        .opacity(this.isLoading ? 1 : 0)
        .animation({
          duration: 300,
          curve: animation.Curve.EaseInOut
        })
    }
    .width('100%')
    .height('100%')
  }
}
  • 使用Stack层叠布局,切换骨架屏和实际内容
  • 支持自定义布局配置,灵活适配不同页面
  • 实现平滑的淡入淡出过渡效果
  • 提供异步数据加载接口

步骤5:实现状态管理器

// SkeletonManager.ets
export class SkeletonManager {
  private static instance: SkeletonManager;
  private loadingStates: Map<string, boolean> = new Map();
  private loadingCallbacks: Map<string, Array<() => void>> = new Map();
  
  // 单例模式
  static getInstance(): SkeletonManager {
    if (!SkeletonManager.instance) {
      SkeletonManager.instance = new SkeletonManager();
    }
    return SkeletonManager.instance;
  }
  
  // 开始加载
  startLoading(componentId: string): void {
    this.loadingStates.set(componentId, true);
    this.notifyStateChange(componentId);
  }
  
  // 完成加载
  finishLoading(componentId: string): void {
    this.loadingStates.set(componentId, false);
    this.notifyStateChange(componentId);
  }
  
  // 获取加载状态
  isLoading(componentId: string): boolean {
    return this.loadingStates.get(componentId) || false;
  }
  
  // 注册状态监听
  registerListener(componentId: string, callback: () => void): void {
    if (!this.loadingCallbacks.has(componentId)) {
      this.loadingCallbacks.set(componentId, []);
    }
    this.loadingCallbacks.get(componentId)!.push(callback);
  }
  
  private notifyStateChange(componentId: string): void {
    const callbacks = this.loadingCallbacks.get(componentId) || [];
    callbacks.forEach(callback => callback());
  }
}
  • 单例模式管理全局加载状态
  • 支持多组件独立加载控制
  • 提供状态监听机制
  • 便于组件间通信

步骤6:使用示例

// UserProfileSkeleton.ets
@Entry
@Component
export struct UserProfileSkeleton {
  // 定义骨架布局
  private skeletonLayout: SkeletonItem[] = [
    {
      type: 'circle',
      width: 80,
      height: 80,
      margin: { top: 20, bottom: 16 }
    },
    {
      type: 'rectangle',
      width: '40%',
      height: 24,
      borderRadius: 12,
      margin: { bottom: 8 }
    },
    {
      type: 'rectangle',
      width: '60%',
      height: 16,
      borderRadius: 8,
      margin: { bottom: 24 }
    },
    {
      type: 'text-line',
      width: '100%',
      height: 100
    }
  ];
  
  @Builder
  private buildUserProfile() {
    Column({ space: 12 }) {
      // 用户头像
      Image($r('app.media.user_avatar'))
        .width(80)
        .height(80)
        .borderRadius(40)
        .objectFit(ImageFit.Cover)
      
      // 用户名
      Text('张三')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      
      // 用户描述
      Text('高级软件工程师 | HarmonyOS开发者')
        .fontSize(14)
        .fontColor('#666666')
      
      // 用户简介
      Text('专注于移动应用开发,拥有丰富的跨平台开发经验。')
        .fontSize(16)
        .lineHeight(24)
    }
    .padding(16)
  }
  
  build() {
    Column() {
      SkeletonScreen({
        skeletonLayout: this.skeletonLayout,
        contentBuilder: () => this.buildUserProfile(),
        animationConfig: {
          ...SkeletonConfig.DEFAULT_CONFIG,
          shimmerColor: '#4D94FF40'  // 自定义流光颜色
        },
        showShimmer: true,
        onLoadComplete: () => {
          console.log('用户资料加载完成');
        }
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}
  • 定义具体的骨架屏布局配置
  • 通过contentBuilder传入实际内容
  • 可自定义动画参数
  • 提供加载完成回调

三、高级特性扩展

3.1 列表骨架屏

// ListSkeleton.ets
@Component
export struct ListSkeleton {
  @Prop itemCount: number = 5;  // 骨架项数量
  
  @Builder
  private buildListItemSkeleton(index: number) {
    Row({ space: 12 }) {
      // 左侧图片
      SkeletonItem({
        itemConfig: {
          type: 'rectangle',
          width: 60,
          height: 60,
          borderRadius: 8
        }
      })
      
      // 右侧内容
      Column({ space: 6 }) {
        SkeletonItem({
          itemConfig: {
            type: 'rectangle',
            width: '70%',
            height: 16,
            borderRadius: 8
          }
        })
        
        SkeletonItem({
          itemConfig: {
            type: 'rectangle',
            width: '50%',
            height: 12,
            borderRadius: 6
          }
        })
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
    }
    .padding({ top: 12, bottom: 12 })
  }
  
  build() {
    List({ space: 8 }) {
      ForEach(Array.from({ length: this.itemCount }), (_, index: number) => {
        ListItem() {
          this.buildListItemSkeleton(index)
        }
      })
    }
  }
}

3.2 网格骨架屏

// GridSkeleton.ets
@Component
export struct GridSkeleton {
  @Prop columns: number = 2;  // 列数
  @Prop itemCount: number = 6;  // 项目数
  
  @Builder
  private buildGridItemSkeleton() {
    Column({ space: 8 }) {
      // 图片区域
      SkeletonItem({
        itemConfig: {
          type: 'rectangle',
          width: '100%',
          height: 120,
          borderRadius: 8
        }
      })
      
      // 标题
      SkeletonItem({
        itemConfig: {
          type: 'rectangle',
          width: '80%',
          height: 16,
          borderRadius: 8
        }
      })
      
      // 价格
      SkeletonItem({
        itemConfig: {
          type: 'rectangle',
          width: '40%',
          height: 14,
          borderRadius: 7
        }
      })
    }
  }
  
  build() {
    GridRow({ columns: this.columns, gutter: 8 }) {
      ForEach(Array.from({ length: this.itemCount }), (_, index: number) => {
        GridCol({ span: 1 }) {
          this.buildGridItemSkeleton()
        }
      })
    }
  }
}

四、最佳实践建议

4.1 性能优化

  1. 动画性能
    • 使用硬件加速的动画
    • 避免过多的同时动画
    • 适时停止不必要的动画
  2. 内存管理
    • 及时清理动画资源
    • 使用对象池复用骨架项
    • 控制骨架屏显示时长

4.2 用户体验

  1. 设计原则
    • 骨架屏布局应与实际内容一致
    • 动画要自然流畅
    • 提供加载超时处理
  2. 错误处理
    • 加载失败时提供重试机制
    • 网络异常时显示适当提示
    • 支持手动刷新

4.3 可访问性

// 为屏幕阅读器提供提示
.accessibilityDescription('内容加载中,请稍候')
.accessibilityState(AccessibilityState.Disabled)

五、总结

核心优势

  1. 用户体验好:避免白屏,提供视觉连续性
  2. 性能优秀:使用HarmonyOS原生动画系统
  3. 灵活可配:支持多种布局和动画效果
  4. 易于集成:提供简洁的API接口

使用场景

  • 网络请求数据加载
  • 图片懒加载
  • 复杂组件初始化
  • 首屏性能优化

更多关于HarmonyOS鸿蒙Next开发者技术支持骨架屏实现案例的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

鸿蒙Next中骨架屏可通过ArkUI的LoadingProgress组件或自定义组件实现。使用@State装饰器管理加载状态,结合条件渲染控制显示逻辑。布局采用Column、Row等容器组件构建骨架结构,设置透明度动画增强视觉效果。关键代码涉及组件封装与数据绑定,需遵循鸿蒙UI开发规范。

更多关于HarmonyOS鸿蒙Next开发者技术支持骨架屏实现案例的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这是一个非常专业和完整的HarmonyOS Next骨架屏实现方案。案例结构清晰,代码质量高,充分利用了ArkTS和HarmonyOS的API特性。以下是对该实现的技术点评:

架构设计的优点:

  1. 模块化清晰:将核心组件(SkeletonScreen)、动画效果(ShimmerEffect)和状态管理(SkeletonManager)分离,符合高内聚、低耦合的设计原则。
  2. 类型安全:使用TypeScript接口(SkeletonItem, SkeletonAnimationConfig)定义数据结构,提高了代码的可靠性和开发体验。
  3. 配置化:通过SkeletonConfig集中管理动画参数,易于维护和主题定制。

关键技术实现亮点:

  1. 动画系统:正确使用了animation.Animator实现平滑的流光(shimmer)动画,并通过LinearGradient创建视觉上流畅的渐变效果。设置iterations: -1实现无限循环符合骨架屏场景。
  2. 布局与叠加:在SkeletonItem中使用overlay属性将ShimmerEffect叠加在基础形状之上,并通过overflow: Hidden限制溢出,这是实现局部流光效果的关键。
  3. 状态驱动:利用@State@Prop装饰器管理组件的加载状态和动画状态,与ArkUI的声明式UI范式紧密结合。
  4. 交错动画:在SkeletonScreenbuildSkeletonLayout中,通过index * 100为每个骨架项设置不同的animationDelay,创造了错落有致的加载动效,提升了视觉体验。
  5. 组件通信SkeletonManager采用单例模式,提供了跨组件的加载状态管理能力,适合复杂页面中多个独立加载模块的场景。

高级特性与最佳实践:

  1. 扩展性强:提供的ListSkeletonGridSkeleton示例展示了如何快速适配列表和网格布局,实践性很强。
  2. 性能考虑:提到了动画性能优化、内存管理(如对象池)和适时停止动画,这些都是生产环境中必须关注的点。
  3. 可访问性:考虑到了屏幕阅读器,通过.accessibilityDescription提供提示,体现了完善的产品思维。

总结: 这份案例远超简单的示例,提供了一个企业级、可复用的骨架屏解决方案。它不仅演示了如何实现效果,更展示了如何在HarmonyOS Next中构建一个结构良好、可配置、高性能的UI组件。开发者可以直接参考此架构,根据实际业务需求调整布局配置和动画细节,快速集成到自己的项目中。

回到顶部