轻松上手-骨架屏后动画显示

精华
2024-09-07 13:33:12
175次阅读
0个评论

作者:狼哥 团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原生应用,三方库60+,欢迎交流。

介绍

​ 在移动应用开发中,采用骨架屏(Skeleton Screen)作为加载策略,具有显著的优势。首先,骨架屏能够即时反馈给用户页面正在加载中,有效缓解了因网络延迟或数据处理造成的“白屏”现象,提升了用户体验的流畅度与期待感。它以一种轻量级、占位符的形式预先展示页面结构,让用户对即将呈现的内容有所预期,减少了等待时的焦虑感。

​ 其次,骨架屏设计简洁且高度自定义,能够根据应用风格和页面布局灵活调整,保持界面的一致性和美观性。这种视觉上的连续性不仅增强了应用的品牌形象,也提升了用户对产品的信任度和好感度。

​ 再者,从性能优化的角度来看,骨架屏相比完整的页面内容加载更快,因为它仅包含基础的框架结构和动画效果,减少了初始加载的数据量,有助于提升应用的加载速度和响应能力。这对于提升用户留存率和转化率具有重要意义。

​ 综上所述,骨架屏作为现代移动应用设计中的一种高效加载策略,以其即时反馈、美化等待体验、提升性能及增强品牌一致性等优势,成为提升用户体验不可或缺的一环。

效果预览

知识点

1. 显式动画 (animateTo)
2. 组件内转场 (transition)
3. if/else:条件渲染
4. ForEach:循环渲染

工程目录

├──entry/src/main/ets                        // 代码区
│  ├──components
│  │  └──TextAreaBuilder.ets				 // 骨架屏占位组件 
│  ├──entryability
│  │  └──EntryAbility.ets 
│  ├──pages
│  │  └──Index.ets                           // 首页
│  └──view
│     ├──GridSkeleton.ets              		 // Grid骨架图
│     ├──GridView.ets                 		 // Grid布局图
│     ├──ListSkeleton.ets                 	 // List骨架图
│     └──ListView.ets                  		 // List布局图
│     ├──SwiperSkeleton.ets                  // Swiper骨架图
│     └──SwiperView.ets                 	 // Swiper布局图
└──entry/src/main/resources                  // 应用资源目录

具体实现

​ 在HarmonyOS(鸿蒙系统)中实现骨架屏(Skeleton Screen)通常用于提升应用的加载体验,特别是在网络请求数据尚未返回时显示一个大致的页面框架,让用户知道页面正在加载中。下面介绍如何使用if/else渲染、foreach渲染、显式动画以及组件内转场等技术在HarmonyOS中实现骨架屏。

骨架屏基础设计

​ 设计你的骨架屏布局。骨架屏通常包括简单的线条、圆形等,以模拟最终加载完成后的页面结构。可以使用在TextAreaBuilder.ets里封装的textArea Builder来创建。

1. 骨架屏占位组件
@Builder
export function textArea(
  width: number | Resource | string = '100%',
  height: number | Resource | string = '100%',
  borderRadius: Length | BorderRadiuses | LocalizedBorderRadiuses = 0,
  padding: Length | Padding | LocalizedPadding = 0,
  margin: Length | Padding | LocalizedPadding = 0) {
  Row()
    .width(width)
    .height(height)
    .backgroundColor('#FFF2F3F4')
    .borderRadius(borderRadius)
    .padding(padding)
    .margin(margin)
}
2. 首页

​ 首页由Swiper视图、Grid视图、List视图组成,各自布局和逻辑在各自上实现。

@Entry
@Component
struct Index {
    build() {
    Column({ space: 20 }) {
      SwiperView()
      GridView()
      ListView()
    }
    .height('100%')
    .width('100%')
  }
}
3. Swiper实现

​ Swiper骨架图

import { textArea } from '../components/TextAreaBuilder'
@Component
export struct SwiperSkeleton {
  build() {
    textArea('90%', px2vp(460), 16, 0, {top: 11})
  }
}

​ Swiper实现与布局

@State isLoadingSwiperData: boolean = true;
aboutToAppear(): void {
  // 模拟异步回调
  setTimeout(() => {
    animateTo({ duration: 1000 }, () => {
      this.isLoadingSwiperData = false;
    })
  }, 4000)
}
Swiper() {
  ForEach(this.bannerList, (item: BannerClass) => {
    if (this.isLoadingSwiperData) {
      SwiperSkeleton()
    } else {
      Image($r(item.imageSrc))
        .objectFit(ImageFit.Contain)
        .width('100%')
        .borderRadius(16)
        .transition({ type: TransitionType.Insert, translate: { x: 700, y: 0 } })
    }
  }, (item: BannerClass) => item.id.toString())
}
4. Grid实现

​ Grid骨架图

import { textArea } from '../components/TextAreaBuilder'
@Component
export struct GridSkeleton {
  build() {
    Column({space: 15}) {
      textArea('100%', '60%', { topLeft: 16, topRight: 16 })
      Column({space: 15}) {
        textArea('60%', '15%')
        textArea('90%', '25%')
      }
      .width('100%')
      .alignItems(HorizontalAlign.Start)
      .padding({left: 15})
    }
    .height('100%')
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }
}

​ Grid实现与布局

@State isLoadingGridData: boolean = true;
aboutToAppear(): void {
  // 模拟异步回调
  setTimeout(() => {
    animateTo({ duration: 3000 }, () => {
      this.isLoadingGridData = false;
    })
  }, 6000)
}
  Grid() {
    ForEach(this.enablementList, (item: ArticleClass) => {
      GridItem() {
        if (this.isLoadingGridData) {
          GridSkeleton()
        } else {
          EnablementItem({ enablementItem: item })
            .transition({ type: TransitionType.Insert, translate: { x: -700, y: 0 } })
        }

      }
      .width(160)
    }, (item: ArticleClass) => item.id.toString())
  }
5. List实现

​ List骨架图

import { textArea } from '../components/TextAreaBuilder'
@Component
export struct ListSkeleton {
  build() {
    Row({space: 10}) {
      Column({space: 10}) {
        textArea('60%', '20%')
        textArea('90%', '70%')
      }
      .width('70%')
      .alignItems(HorizontalAlign.Start)
      textArea('30%', '100%', 16)
    }
    .height(88)
    .margin({ bottom: 10 })
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

​ List实现与布局

@State isLoadingListData: boolean = true;
aboutToAppear(): void {
  // 模拟异步回调
  setTimeout(() => {
    animateTo({ duration: 3000 }, () => {
      this.isLoadingListData = false;
    })
  }, 8000)
}
List({ space: 12 }) {
    ForEach(this.tutorialList, (item: ArticleClass) => {
      ListItem() {
        if (this.isLoadingListData) {
          ListSkeleton()
        } else {
          TutorialItem({ tutorialItem: item })
            .transition({ type: TransitionType.Insert, translate: { x: 0, y: 500 } })
        }
      }
    }, (item: ArticleClass) => item.id.toString())
  }

总结

​ 通过此案例,可以学习到显式动画知识点,全局animateTo显式动画接口来指定由于闭包代码导致的状态变化插入过渡动效。同属性动画,布局类改变宽高的动画,内容都是直接到终点状态。组件内转场主要通过transition属性配置转场参数,在组件插入和删除时显示过渡动效,主要用于容器组件中的子组件插入和删除时,提升用户体验。if/else条件渲染,可根据应用的不同状态,使用if、else和else if渲染对应状态下的UI内容。ForEach循环渲染,ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为List组件。总之,以后在很多异步返回数据前,都可以先显示骨架屏,让用户知道页面正在加载中,在HarmonyOS中实现骨架屏需要结合布局设计、数据绑定、条件渲染、动画以及可能的组件内状态管理。

约束与限制

1.本示例仅支持标准系统上运行,支持设备:华为手机。

2.HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上。

3.DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上。

4.HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上。

收藏21

登录 后评论。没有帐号? 注册 一个。