HarmonyOS 项目实练(二)

跟着官网学习了些HarmonyOS基础,整体感受下来,还是比较容易上手的。 今天开始有时间想把之前的一个WIFI & BLE项目,置换成Harmony试试看。仅做些记录。

HarmonyOS 项目实练(一)
HarmonyOS 项目实练(三)

截屏2024-11-28 16.34.16.png

1. 导航

1.1 Nav

  • Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(NavDestination的子组件),首页和非首页通过路由进行切换。
Navigation(pathInfos: NavPathStack)
  • NavPathStack Navigation路由栈
pushPath(info: NavPathInfo, animated?: boolean): void
pushPathByName(name: string, param: unknown, animated?: boolean): void
  • pop
pop(animated?: boolean): NavPathInfo | undefined
  • clear 清除栈中所有页面。
clear(animated?: boolean): void

1.2 Example

  @StorageLink('pageIndexInfos') pageIndexInfos: NavPathStack = new NavPathStack();
 build() {
    Navigation(this.pageIndexInfos) {
      Column() {
        Text(this.message)
          .id('HelloWorld')
          .fontSize(StyleConstants.FONTSIZE_CONTENT)
          .fontWeight(FontWeight.Bold)
        Button('Click')
          .id('click')
          .fontSize(StyleConstants.FONTSIZE_BUTTON)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.pageIndexInfos.pushPathByName(RouterUrlConstants.SelectModule, true)
          })
      }
      .height(StyleConstants.FULL_HEIGHT)
      .justifyContent(FlexAlign.SpaceAround)
    }
    .mode(NavigationMode.Stack)
    .navDestination(this.PagesMap)
    .height(StyleConstants.FULL_HEIGHT)
    .hideTitleBar(true)
    .hideToolBar(true)
  }
  @Builder
  PagesMap(name: string, param?: object) {
    if (name === RouterUrlConstants.SelectModule) {
      NavDestination() {
        SelectModule()
      }
      .hideTitleBar(true)
    }
  }

2. 自定义View

  • @Component :自定义组件
import { StyleConstants } from '@ohos/constantsCommon'

@Component
export struct HeadComponent {
  @StorageLink('pageIndexInfos') pageIndexInfos: NavPathStack = new NavPathStack();

  build() {
    Row() {
      Image($r('app.media.ic_public_back'))
        .width($r('app.float.header_image_width'))
        .height($r('app.float.header_image_height'))
        .margin({
          left: $r('app.float.header_margin_left'),
          right: $r('app.float.header_margin_right')
        })
      Text($r('app.string.comment_title'))
        .fontSize($r('app.float.header_font_size'))
        .fontColor($r('app.color.subtitle_color'))
        .lineHeight($r('app.float.header_line_height'))
        .fontWeight(FontWeight.Medium)
    }
    .width(StyleConstants.FULL_WIDTH)
    .height($r('app.float.header_height'))
    .onClick(() => {
      this.pageIndexInfos.pop()
    })
  }
}
  • @Builder : 页面内自定义View
  @Builder
  pickerView() {
    Column() {
      PickerListView({ currentSelected: this.currentSelected, isShow: this.isShow, itemList: this.indexItemList })
        .height(StyleConstants.FULL_HEIGHT)
        .width(StyleConstants.FULL_WIDTH)
        .padding({
          top: 20,
          left: 20
        })
    }
    .height(StyleConstants.FULL_HEIGHT)
    .width(StyleConstants.FULL_WIDTH)
  }

3. 半模态转场

bindSheet(isShow: Optional<boolean>, builder: CustomBuilder, options?: SheetOptions)
参数介绍
      Row() {
        Button() {
          Text(this.currentSelected)
            .fontColor($r('app.color.content_color'))
            .fontSize($r('app.float.title_content_size'))
            .padding({
              left: 0
            })
        }
        .onClick(() => {
          this.isShow = true;
        })
        .bindSheet($$this.isShow, this.pickerView(), {
          height: 300,
          backgroundColor: Color.White,
          onDisappear: () => {
            this.isShow = false
          }
        })
        .width(StyleConstants.FULL_WIDTH)
        .height(StyleConstants.FULL_HEIGHT)
        .backgroundColor(Color.White)
        .borderRadius(15)
        .borderWidth(1)
        .borderColor($r('app.color.background_color'))
      }

4. 状态管理MVVM

4.1 MVVM 划分

  • Model:负责存储和管理应用的数据以及业务逻辑,不直接与用户界面交互。通常从后端接口获取数据,是应用程序的数据基础,确保数据的一致性和完整性。
  • View:负责用户界面展示数据并与用户交互,不包含任何业务逻辑。它通过绑定ViewModel层提供的数据来动态更新UI。
  • ViewModel:负责管理UI状态和交互逻辑。作为连接Model和View的桥梁,ViewModel监控Model数据的变化,通知View更新UI,同时处理用户交互事件并转换为数据操作。


    mvvm 工程结构

4.2 响应式状态管理

  • 不可跨层访问

View层不可以直接调用Model层的数据,只能通过ViewModel提供的方法进行调用。
Model层数据,不可以直接操作UI,Model层只能通知ViewModel层数据有更新,由ViewModel层更新对应的数据。

状态管理的关键词

4.2.1 管理组件拥有的状态

  • @State装饰器:组件内状态
    @State装饰器作为最常用的装饰器,用来定义状态变量,一般作为父组件的数据源,当开发者点击时,通过触发状态变量的更新从而刷新UI,去掉@State则不再支持刷新UI。

    @State

  • @Prop装饰器:父子单向同步
    @Prop是父子间单向传递,子组件会深拷贝父组件数据,可从父组件更新,也可自己更新数据,但不会同步父组件数据。

    @Prop

  • @Link装饰器:父子双向同步
    @Link是父子间双向传递,父组件改变,会通知所有的@Link,同时@Link的更新也会通知父组件对应变量进行刷新。

    @Link

  • @Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
    应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。

    @Observed

    @ObjectLink

  • @Provide装饰器和@Consume装饰器:与后代组件双向同步
    @Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景,实现跨层级传递。

    @Provide

    @Consume

4.2.1 管理应用拥有的状态

LocalStorage:页面级UI状态存储

AppStorage:应用全局的UI状态存储

PersistentStorage:持久化存储UI状态

Environment:设备环境查询

  • LocalStorage:页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。

  • AppStorage:特殊的单例LocalStorage对象,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。

  • PersistentStorage:持久化存储UI状态,通常和AppStorage配合使用,选择AppStorage存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同。

  • Environment:应用程序运行的设备的环境参数,环境参数会同步到AppStorage中,可以和AppStorage搭配使用。

LocalStorage:页面级UI状态存储

LocalStorage是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage支持UIAbility实例内多个页面间状态共享。

LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内的“数据库”。

  • 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过GetShared接口,实现跨页面、UIAbility实例内共享。

  • 组件树的根节点,即被@Entry装饰的@Component,可以被分配一个LocalStorage实例,此组件的所有子组件实例将自动获得对该LocalStorage实例的访问权限。

  • 被@Component装饰的组件最多可以访问一个LocalStorage实例和AppStorage,未被@Entry装饰的组件不可被独立分配LocalStorage实例,只能接受父组件通过@Entry传递来的LocalStorage实例。一个LocalStorage实例在组件树上可以被分配给多个组件。

  • LocalStorage中的所有属性都是可变的

LocalStorage根据与@Component装饰的组件的同步类型不同,提供了两个装饰器:

  • @LocalStorageProp:@LocalStorageProp装饰的变量与LocalStorage中给定属性建立单向同步关系。

  • @LocalStorageLink:@LocalStorageLink装饰的变量与LocalStorage中给定属性建立双向同步关系。

let localStorageA: LocalStorage = new LocalStorage();
localStorageA.setOrCreate('PropA', 'PropA');

let localStorageB: LocalStorage = new LocalStorage();
localStorageB.setOrCreate('PropB', 'PropB');

let localStorageC: LocalStorage = new LocalStorage();
localStorageC.setOrCreate('PropC', 'PropC');

@Entry
@Component
struct MyNavigationTestStack {
  @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();

  @Builder
  PageMap(name: string) {
    if (name === 'pageOne') {
      // 传递不同的LocalStorage实例
      pageOneStack({}, localStorageA)
    } else if (name === 'pageTwo') {
      pageTwoStack({}, localStorageB)
    } else if (name === 'pageThree') {
      pageThreeStack({}, localStorageC)
    }
  }

  build() {
    Column({ space: 5 }) {
      Navigation(this.pageInfo) {
        Column() {
          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
            .width('80%')
            .height(40)
            .margin(20)
            .onClick(() => {
              this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈
            })
        }
      }.title('NavIndex')
      .navDestination(this.PageMap)
      .mode(NavigationMode.Stack)
      .borderWidth(1)
    }
  }
}

@Component
struct pageOneStack {
  @Consume('pageInfo') pageInfo: NavPathStack;
  @LocalStorageLink('PropA') PropA: string = 'Hello World';

  build() {
    NavDestination() {
      Column() {
        NavigationContentMsgStack()
        // 显示绑定的LocalStorage中PropA的值'PropA'
        Text(`${this.PropA}`)
        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.pageInfo.pushPathByName('pageTwo', null);
          })
      }.width('100%').height('100%')
    }.title('pageOne')
    .onBackPressed(() => {
      this.pageInfo.pop();
      return true;
    })
  }
}

@Component
struct pageTwoStack {
  @Consume('pageInfo') pageInfo: NavPathStack;
  @LocalStorageLink('PropB') PropB: string = 'Hello World';

  build() {
    NavDestination() {
      Column() {
        NavigationContentMsgStack()
        // 如果绑定的LocalStorage中没有PropB,显示本地初始化的值 'Hello World'
        Text(`${this.PropB}`)
        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.pageInfo.pushPathByName('pageThree', null);
          })

      }.width('100%').height('100%')
    }.title('pageTwo')
    .onBackPressed(() => {
      this.pageInfo.pop();
      return true;
    })
  }
}

@Component
struct pageThreeStack {
  @Consume('pageInfo') pageInfo: NavPathStack;
  @LocalStorageLink('PropC') PropC: string = 'pageThreeStack';

  build() {
    NavDestination() {
      Column() {
        NavigationContentMsgStack()

        // 如果绑定的LocalStorage中没有PropC,显示本地初始化的值 'pageThreeStack'
        Text(`${this.PropC}`)
        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.pageInfo.pushPathByName('pageOne', null);
          })

      }.width('100%').height('100%')
    }.title('pageThree')
    .onBackPressed(() => {
      this.pageInfo.pop();
      return true;
    })
  }
}

@Component
struct NavigationContentMsgStack {
  @LocalStorageLink('PropA') PropA: string = 'Hello';

  build() {
    Column() {
      Text(`${this.PropA}`)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
    }
  }
}

应用程序决定LocalStorage对象的生命周期。

  • AppStorage:应用全局的UI状态存储
    AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。

和AppStorage不同的是,LocalStorage是页面级的,通常应用于页面内的数据共享。而AppStorage是应用级的全局状态共享,还相当于整个应用的“中枢”,持久化数据PersistentStorage环境变量Environment都是通过AppStorage中转,才可以和UI交互。
AppStorage是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容