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 使用
  • @StorageProp/@StorageLink的参数必须为string类型,否则编译期会报错。
AppStorage.setOrCreate('PropA', 47);

// 错误写法,编译报错
@StorageProp() storageProp: number = 1;
@StorageLink() storageLink: number = 2;


// 正确写法
@StorageProp('PropA') storageProp: number = 1;
@StorageLink('PropA') storageLink: number = 2;
  • AppStorage是单例,它的所有API都是静态的,使用方法类似于LocalStorage中对应的非静态方法。
AppStorage.setOrCreate('PropA', 47);


let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('PropA',17);
let propA: number | undefined = AppStorage.get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA'); // link2.get() == 47
let prop: SubscribedAbstractProperty<number> = AppStorage.prop('PropA'); // prop.get() == 47


link1.set(48); // 双向同步: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // 单向同步: prop.get() == 1; 但 link1.get() == link2.get() == 48
link1.set(49); // 双向同步: link1.get() == link2.get() == prop.get() == 49


storage.get<number>('PropA') // == 17
storage.set('PropA', 101);
storage.get<number>('PropA') // == 101


AppStorage.get<number>('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容