如何在HarmonyOS Next中自由切换缺省页

190 阅读3分钟

简介

StateLayout ,是一个针对HarmonyOS Next系统开发的缺省页框架,采用官方底层API,简单、实用、高效。

  • 根据状态显示对应状态页面
  • 可以全局配置状态页样式

下载安装

ohpm i @chawloo/state-layout

稳定版本

版本说明
V1.2.2(停止更新)V1版本,用的都是@Component、@State、@Link等
V2.1.0V2版本,用的都是@ComponentV2、@Local、@Param等

尝鲜版本(时间精力有限,故暂不考虑对V1进行尝鲜功能升级)

版本说明
V2.1.0(已Release)新增全局Builder功能,具体看更新说明

OpenHarmony ohpm 环境配置等更多内容,请参考如何安装 OpenHarmony ohpm 包

接口和属性列表

StateConfig属性列表

属性类型默认值描述
defaultStateStateEnumStateEnum.CONTENT默认缺省页
showLoadingWhenRetrybooleantrue重试自动显示加载中
loadingStrstring'加载中...'加载缺省页提示文本
progressColorResourceColor#FF3369E7加载缺省页进度条颜色
emptyStrstring'暂无数据'空白缺省页提示文本
emptyIconstring$r('app.media.ic_state_empty')空白缺省页图标
errorStrstring'加载失败'错误缺省页提示文本
errorIconResource$r('app.media.ic_state_error')错误缺省页图标
networkErrorStrstring'网络不稳定,请重试'网络错误缺省页提示文本
networkErrorIconResource$r('app.media.ic_state_network')网络错误缺省页图标
retryStrstring'重新加载'重试按钮文本

全局配置globalStateConfig配置接口

⚠️ V1.1.0 版本已废弃删除这些方法,新增globalStateConfig直接静态赋值

接口描述
loadingConfig(LoadingConfig)配置加载中的提示文案和Progress颜色
emptyConfig(EmptyConfig)配置空白页的提示文案和图标
errorConfig(ErrorConfig)配置错误中的提示文案、图标和重试文本
networkErrConfig(NetworkErrorConfig)配置网络错误中的提示文案、图标和重试文本

全局配置

可在任意地方配置,推荐EntryAbility入口Ability中配置

//在onCreate中配置即可
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  //全局配置缺省页的各种图标和文案
  GlobalStateConfig.progressColor = Color.Red// 设置加载中进度条的颜色
  GlobalStateConfig.emptyStr = '我是全局配置的空白提示文案'// 设置空白文案
  GlobalStateConfig.emptyIcon = $r('app.media.state_empty')// 设置空白Icon图标
  GlobalStateConfig.retryStr = '立即重试'// 设置默认重试按钮文案(网络错误和错误共享)
  GlobalStateConfig.defaultState = StateEnum.LOADING// 设置默认缺省页为加载中
  GlobalStateConfig.showLoadingWhenRetry:boolean = false// 关闭点击重试按钮自动切换加载中
}

V2.1.0 新增内容

在EntryAbility中配置全局Builder,去自定义缺省页的内容,仍然支持emptyConfig,也可以不用理会这个入参,因为全局Builder必须有入参

@Builder
function GlobalEmptyBuilder(emptyConfig?: EmptyConfig) {
  Text(`我是全局配置的EmptyBuilder:::${emptyConfig?.emptyStr}`)
}

export default class EntryAbility extends UIAbility{
  //····省略其他内容
  //在配置GlobalStateConfig的地方配置,我是放在onCreate
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    //···其他配置
    GlobalStateConfig.globalEmptyBuilder = wrapBuilder(GlobalEmptyBuilder)
  }
}

控制器 StateController

接口描述
loading(LoadingConfig)显示加载中(可单次配置)
empty(EmptyConfig)显示空白缺省页(可单次配置)
error(ErrorConfig)显示错误缺省页(可单次配置)
networkError(NetworkErrorConfig)显示网络错误缺省页(可单次配置)
content()显示内容页

单次配置接口

LoadingConfig

配置项类型描述
loadingStrstring加载中文案
progressColorResourceColor进度条颜色

EmptyConfig

配置项类型描述
emptyStrstring空白文案
emptyIconResource空白图标

ErrorConfig

配置项类型描述
errorStrstring空白文案
errorIconResource空白图标
retryStrstring重试文案
showLoadingWhenRetryboolean重试自动显示加载中
isCoverRetryboolean是否覆盖原有重试事件
retry()=>void重试事件

NetworkErrorConfig

配置项类型描述
networkErrorStrstring网络错误文案
networkErrorIconResource网络错误图标
retryStrstring重试文案
showLoadingWhenRetryboolean重试自动显示加载中
isCoverRetryboolean是否覆盖原有重试事件
retry()=>void重试事件

使用

简单用法

重要的写在前面,由于底层绘制时序问题,StateController控制器放在是在aboutToAppear中赋值的,那如果放在绘制前的生命周期里面切换,不带延迟,因为StateLayout没有build完成而去通过StateController去切换缺省状态,会切换无效的问题。 解决方案如下:

build() {
  StateLayout({
    controller: this.controller,
    contentBuilder: () => {
      this.redBuilder()
    }
  })
    .onAppear(() => {//放在组件的onAppear中,一般出现问题是本地数据加载过快,通过网络加载的没有这个情况
      if (ArrayUtil.isNotEmpty(this.list)) {
        this.controller.content()
      } else {
        this.controller.empty()
      }
    })
}
@Entry
@ComponentV2
struct Index {
  controller: StateController = new StateController() //初始化StateController
  @Local message: string = 'Hello World'
  

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

  //模拟网络加载
  loading() {
    setTimeOut(() => {
      //简单设置状态
      this.controller.content()
    }, 2000)
  }

  build() {
    Column() {
      //加入缺省页组件
      StateLayout({
        controller: this.controller,
        retry: () => {
          this.loading()
        },
      }) {
        Text(`加载成功后的内容:::${this.message}`)
          .fontSize(30)
          .fontColor(Color.Black)
          .onClick(() => {
            router.pushUrl({
              url: "pages/StateLayoutPage"
            })
          })
      }
    }
    .height('100%')
    .width('100%')
  }
}

通过控制器调用切换缺省状态

@Entry
@ComponentV2
struct Index {
  @Local message: string = 'Hello World';
  controller: StateController = new StateController()

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

  //模拟网络加载
  loading() {
    const timerId = setInterval(() => {
      this.controller.content()
      clearInterval(timerId)
    }, 2000)
  }

  build() {
    Column() {
      SelectTitleBar({
        options: [
          { value: '加载中' },
          { value: '空白页(全局文案和图标)' },
          { value: '空白页(单例文案和图标)' },
          { value: '错误页' },
          { value: '错误页(单例重试按钮和事件)' },
          { value: '错误页(单例重试事件并覆盖)' },
          { value: '没有网络' },
          { value: '没有网络(自动显示加载)' },
          { value: '没有网络(不自动显示加载)' },
          { value: '未登录' },
          { value: '加载成功' },
        ],
        selected: 0,
        onSelected: (index) => {
          switch (index) {
            case 0:
              this.controller.loading()
              break
            case 1:
              this.controller.empty()
              break
            case 2:
              this.controller.empty({
                emptyStr: '我是单例空白文案',
                emptyIcon: $r('app.media.state_empty')
              })
              break
            case 3:
              this.controller.error()
              break
            case 4:
              this.controller.error({
                retryStr: '我是单例错误按钮',
                errorStr: `我是单例错误文案`,
                isCoverRetry: false, //不会覆盖
                retry: () => {
                  promptAction.showToast({ message: '我是单例重试事件' })
                }
              })
              break
            case 5:
              this.controller.error({
                retryStr: '给我重试',
                errorStr: '单例重试事件,后续点击重试,执行覆盖的重试',
                isCoverRetry: true, //覆盖原来的重试,普通的error和netError后,重试时间被覆盖
                retry: () => {
                  promptAction.showToast({ message: '我是覆盖的重试事件' })
                }
              })
              break
            case 6:
              this.controller.networkError({
                networkErrorStr: '网络错误(点击重试根据全局配置是否自动加载中)',
              })
              break
            case 7:
              this.controller.networkError({
                networkErrorStr: '网络错误(点击重试自动显示加载中)',
                showLoadingWhenRetry: true
              })
              break
            case 8:
              this.controller.networkError({
                networkErrorStr: '网络错误(点击重试不会自动显示加载中)',
                showLoadingWhenRetry: false
              })
              break
            case 9:
              this.controller.error({
                errorStr: '未登录状态提示,等同于Error',
                retryStr: '立即登录',
                showLoadingWhenRetry: false,
                retry: () => {
                  promptAction.showToast({
                    message: '跳转登录的点击事件'
                  })
                }
              })
              break
            case 10:
              this.controller.content()
              break
          }
        },
        hidesBackButton: true
      })
      StateLayout({
        controller: this.controller,
        retry: () => {
          promptAction.showToast({ message: '我是最初重试事件' })
          this.loading()
        },
      }) {
        Text(`加载成功后的内容:::${this.message}`)
          .fontSize(30)
          .fontColor(Color.Black)
          .onClick(() => {
            router.pushUrl({
              url: "pages/StateLayoutPage"
            })
          })
      }
    }
    .height('100%')
      .width('100%')
  }
}

Feature

目前感觉网络错误的状态和错误状态雷同,无非就是换了文案和图标,打算给他去掉

约束与限制

在下述版本验证通过: DevEco Studio NEXT Release (5.0.3.900), SDK: API12(5.0.0) 设备:Mate 60 Pro(Release)

FAQ

  • 目前只是简单的设置,细节问题和更多功能正在研究和发掘开发,欢迎提交PR,共同进步

贡献代码

使用过程中发现任何问题都可以提Issue 给我们,当然,我们也非常欢迎你给我们提PR

开源协议

本项目基于 Apache-2.0 ,请自由地享受和参与开源。