HarmonyOS 鸿蒙Next 轻松上手-骨架屏后动画显示

发布于 1周前 作者 nodeper 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 轻松上手-骨架屏后动画显示
<markdown _ngcontent-uov-c237="" class="markdownPreContainer">

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

介绍

        在移动应用开发中,采用骨架屏(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                  // 应用资源目录
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

具体实现

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

        骨架屏基础设计

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

1. 骨架屏占位组件
[@Builder](/user/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)
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
2. 首页

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

[@Entry](/user/Entry)
[@Component](/user/Component)
struct Index {
   build() {
   Column({ space: 20 }) {
     SwiperView()
     GridView()
     ListView()
   }
   .height('100%')
   .width('100%')
 }
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
3. Swiper实现

        Swiper骨架图

import { textArea } from '../components/TextAreaBuilder'
[@Component](/user/Component)
export struct SwiperSkeleton {
 build() {
   textArea('90%', px2vp(460), 16, 0, {top: 11})
 }
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

        Swiper实现与布局

 [@State](/user/State) isLoadingSwiperData: boolean = true;
 aboutToAppear(): void {
   // 模拟异步回调
   setTimeout(() => {
     animateTo({ duration: 1000 }, () => {
       this.isLoadingSwiperData = false;
     })
   }, 4000)
 }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
 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())
 }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
4. Grid实现

        Grid骨架图

import { textArea } from '../components/TextAreaBuilder'
[@Component](/user/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)
 }
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

        Grid实现与布局

 [@State](/user/State) isLoadingGridData: boolean = true;
 aboutToAppear(): void {
   // 模拟异步回调
   setTimeout(() => {
     animateTo({ duration: 3000 }, () => {
       this.isLoadingGridData = false;
     })
   }, 6000)
 }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
 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(</span></span><span class="hljs-number"><span class="hljs-function"><span class="hljs-params"><span class="hljs-number">160</span></span></span></span><span class="hljs-function"><span class="hljs-params">)

}, (item: ArticleClass) => item.id.toString()) } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

5. List实现

        List骨架图

import { textArea } from '../components/TextAreaBuilder'
[@Component](/user/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)
 }
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

        List实现与布局

 [@State](/user/State) isLoadingListData: boolean = true;
 aboutToAppear(): void {
   // 模拟异步回调
   setTimeout(() => {
     animateTo({ duration: 3000 }, () => {
       this.isLoadingListData = false;
     })
   }, 8000)
 }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
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())
 }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

总结

        通过此案例,可以学习到显式动画知识点,全局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及以上。

</markdown>

关于HarmonyOS 鸿蒙Next 轻松上手-骨架屏后动画显示的问题,您也可以访问:https://www.itying.com/category-93-b0.html 联系官网客服。
9 回复

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

好奇问问楼主,自己根据文章示例代码调试了一下,运行模拟器时就显示白屏。
我把textArea的背景颜色调了一下,并添加margin和borderRadius,但是也没有达到文章的效果。
cke_1752.png

我是用真机直接调试的,晚点我用本地的模拟器看看

本地模拟器运行,也是可以显示出来的,请问你是怎么调用显示的?

HarmonyOS的开发者模式提供了很多实用的工具,方便我们进行调试和优化。

奇怪了,就新建几个ets文件,GridSkeleton,GridView,ListSkeleton,ListView,SwiperSkeleton,SwiperView,然后index主页引用组件。会不会组件里面的enablementList, tutoriaList,bannerList需要填充数据的?因为他们我只声明了空白的数据。

本地模拟器效果

Screenshot_2024-08-18T175948.png

严重支持 狼哥666
正好需要,学习了
回到顶部