HarmonyOS7 骨架屏比转圈更有耐心:先把页面结构露出来

为一篇讲解 HarmonyOS7 骨架屏的中文技术文章绘制一张 sketch-notes 风格信息图

前言

商品列表一进来居中放一个转圈,用户只能等。他不知道这里会出现什么,也不知道页面是不是卡住了。骨架屏好一点,它至少先告诉用户:这里会有图片、标题、价格和操作按钮。

为一篇 HarmonyOS7 ArkTS 教程绘制一张 sketch-notes 风格框架图,主题是

HarmonyOS7 做电商、课程、消息列表时,我更愿意用骨架屏承接首屏加载。骨架屏不是灰色装饰,它是在加载阶段提前交代页面结构。

为什么这个问题经常被写乱

骨架屏比转圈更有耐心 这类内容很容易被写成“代码能跑就算讲完了”,但对初学者来说,这恰恰是最不够的地方。真正让人卡住的,往往不是某个组件名记不住,而是不知道这段代码为什么要这样拆、状态为什么要这样放、以后需求变化时应该从哪里改。

为一篇讲解 HarmonyOS7 骨架屏实现的中文文章绘制一张 sketch-notes 风格流程图

所以这篇文章不只想给你一个能跑的例子,更想把背后的判断过程讲清楚。你只要把这个判断过程吃透,后面自己改页面、补需求、查问题时,心里会稳很多。

骨架要贴近真实卡片

区域骨架形状真实内容
商品图方形灰块商品封面
标题两条短线商品名与卖点
价格短线价格
操作小圆角块加购按钮

骨架数量和首屏真实卡片接近就够了,不要无限铺满页面。骨架尺寸也要贴近真实内容,否则加载完成后页面会跳动。

先把页面目标想清楚

在真正写代码之前,先别急着盯着 API。更有用的做法是先想清楚:这个页面到底想解决什么问题,用户最在意的反馈是什么,哪些状态必须一直保持一致。

当你先把这条主线想明白,再回头看组件和状态设计,很多选择都会顺理成章。对小白来说,这一步尤其重要,因为它能帮你从“照着抄”慢慢过渡到“看得懂、改得动”。

完整 ArkTS 示例

interface GoodsItem23 {
  id: number
  name: string
  price: string
  tag: string
}

@Entry
@Component
struct SkeletonGoodsPage {
  @State loading: boolean = true
  @State loadFailed: boolean = false
  @State goods: GoodsItem23[] = []

  aboutToAppear(): void {
    this.loadGoods()
  }

  async loadGoods(): Promise<void> {
    this.loading = true
    this.loadFailed = false
    try {
      await new Promise<void>((resolve) => {
        setTimeout(() => resolve(), 700)
      })
      this.goods = [
        { id: 1, name: '轻量通勤双肩包', price: '¥199', tag: '今日好价' },
        { id: 2, name: '桌面无线充电底座', price: '¥129', tag: '热卖' },
        { id: 3, name: '降噪蓝牙耳机', price: '¥399', tag: '新品' }
      ]
    } catch (err) {
      this.loadFailed = true
    } finally {
      this.loading = false
    }
  }

  @Builder
  SkeletonCard() {
    Row({ space: 12 }) {
      Blank().width(86).height(86).backgroundColor('#E5E8EF').borderRadius(10)
      Column({ space: 10 }) {
        Blank().width('80%').height(16).backgroundColor('#E5E8EF').borderRadius(4)
        Blank().width('56%').height(16).backgroundColor('#EEF0F5').borderRadius(4)
        Blank().width(72).height(22).backgroundColor('#E5E8EF').borderRadius(11)
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
    }
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }

  @Builder
  GoodsCard(item: GoodsItem23) {
    Row({ space: 12 }) {
      Stack() {
        Text(item.tag).fontSize(12).fontColor(Color.White)
      }
      .width(86)
      .height(86)
      .backgroundColor('#2F6FED')
      .borderRadius(10)

      Column({ space: 8 }) {
        Text(item.name).fontSize(17).fontWeight(FontWeight.Medium)
        Text('次日达 · 支持七天无理由').fontSize(12).fontColor('#777777')
        Row() {
          Text(item.price).fontSize(18).fontColor('#D92D20').fontWeight(FontWeight.Bold)
          Blank()
          Button('加购').height(30)
        }
        .width('100%')
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)
    }
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }

  build() {
    Column({ space: 14 }) {
      Text('商品推荐').fontSize(24).fontWeight(FontWeight.Bold).width('100%')

      if (this.loading) {
        ForEach([0, 1, 2], (index: number) => {
          this.SkeletonCard()
        }, (index: number) => index.toString())
      } else if (this.loadFailed) {
        Column({ space: 10 }) {
          Text('商品加载失败').fontSize(18).fontWeight(FontWeight.Bold)
          Button('重新加载').onClick(() => this.loadGoods())
        }
        .padding(24)
        .width('100%')
        .backgroundColor(Color.White)
        .borderRadius(12)
      } else {
        ForEach(this.goods, (item: GoodsItem23) => {
          this.GoodsCard(item)
        }, (item: GoodsItem23) => item.id.toString())
      }
    }
    .padding(16)
    .width('100%')
    .height('100%')
    .backgroundColor('#F6F7F9')
  }
}

把关键代码一段段拆开

SkeletonCard()GoodsCard() 的主要尺寸接近。图片区域、内边距、圆角都一致,加载完成后页面不会突然抖一下。

loadingloadFailed 分开。只用一个布尔值很快会不够用,失败态和加载态必须能独立表达,否则接口失败后骨架可能一直停在那里。

ForEach([0, 1, 2]) 用固定骨架数量。骨架不是数据,不需要伪造商品对象,首屏放三个占位就能说明结构。

让骨架更像真实业务

骨架不要覆盖所有可能内容,只覆盖稳定区域。比如商品图片、标题、价格、按钮通常都会有;临时活动标签、库存提醒、会员价不一定每条都有,就不必硬塞。

接口很快返回时,可以让骨架少展示或不展示,避免灰块一闪而过。接口失败时必须切到错误态,不能让用户一直看骨架。

新手最容易踩的坑

这一类示例最容易让人产生错觉:界面出来了,就以为已经掌握了。其实真正容易出问题的地方,通常都在效果之外,比如状态有没有收拢、失败后怎么兜底、以后要扩展时会不会牵一发动全身。

所以你练这篇内容时,别只看“现在能不能跑”,还要继续看“以后好不好改”。能把这个习惯养起来,你写出来的页面会比单纯照着示例拼出来的页面稳很多。

放进真实项目还要补什么

示例代码的重点是把核心思路讲明白,所以很多工程化细节会故意省掉。真正落到项目里时,你通常还要继续补接口联动、异常处理、边界保护、资源抽离,以及和其他页面状态之间的配合。

比较稳的做法是分三步走:先把结构和职责立住,再把真实业务接进去,最后再优化视觉和交互体验。这样改出来的页面不只是“能演示”,而是真的更接近可以长期维护的业务代码。

写在最后

骨架屏的价值是降低等待焦虑,不是把页面做得更花。越贴近真实卡片,越能让用户预期下一步会出现什么。HarmonyOS7 用条件渲染写这种状态很直接,关键是别把加载、失败和真实内容混在一起。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值