002 Aprenda a linguagem ArkTs do desenvolvimento de aplicativos Hongmeng

Índice

1. Introdução à linguagem ArkTs

1. Visão geral da linguagem ArkTs

2. Recursos de linguagem do ArkTs

2. Descrição básica da IU

1. Conceitos básicos

2. Especificação da descrição da IU

Construir configuração sem parâmetros

Construir configuração com parâmetros

configuração de atributos

configuração do evento

configuração do subcomponente

3. Gestão do estado

1. Conceitos básicos

2. Gerenciamento de estado no nível da página

@Estado

@Suporte

Gerenciamento de dados @Observed e ObjectLink

@Fornecer e @Consumir

@Assistir

3. Gerenciamento de estado de variáveis ​​de nível de aplicativo

AppStorage

LocalStorage

Armazenamento persistente

Ambiente

4. Crie elementos de IU dinamicamente

@Construtor

@BuilderParam8+

Apresente a motivação

Componente de inicialização de parâmetro

@Estilos

@Ampliar

@CustomDialog

5. Controle de renderização

renderização condicional

Renderização em loop

carregamento preguiçoso de dados

Descrição do tipo IDataSource

Descrição do tipo DataChangeListener


1. Introdução à linguagem ArkTs

1. Visão geral da linguagem ArkTs

ArkTs é atualmente a linguagem principal para o desenvolvimento do Hongmeng. Atualmente, o desenvolvimento da nova versão (3.1.0 (API 9)) do modelo principal do Hongmeng Stage não suporta Java e JavaScript , por isso é necessário desenvolver aplicativos Hongmeng e aprender ArkTs . ArkTs é uma extensão de Ts (TypeScript). A introdução a seguir é apresentar as características de ArkTs. Para a parte Ts, você pode consultar: Comece com TypeScript em 5 minutos TypeScript Chinese Network TypeScript——um superconjunto de JavaScript

2. Recursos de linguagem do ArkTs

A linguagem ArkTs atual estende os recursos declarativos da UI com base nos Ts originais, o objetivo é simplificar a construção e atualização da UI, possui principalmente os seguintes recursos:

  • Descrição básica da interface do usuário: ArkTs define vários decoradores, componentes personalizados, descrições da interface do usuário e métodos de evento e métodos de propriedade de componentes integrados da estrutura da interface do usuário para construir a arquitetura principal da interface do usuário.
  • Gerenciamento de estado: ArkTs fornece um mecanismo de gerenciamento de estado multidimensional.No framework de desenvolvimento de UI, os dados vinculados à interface podem ser usados ​​não apenas no mesmo componente, mas também em diferentes componentes. Por exemplo: componentes pai-filho, componentes avós e netos também podem ser transferidos globalmente no aplicativo e até dados podem ser transferidos entre dispositivos. Do ponto de vista do fluxo de dados, a distinção é entre a transferência de dados legíveis de um único item e a transferência bidirecional de dados alterados. Os desenvolvedores podem usar de forma flexível os recursos existentes para vincular dados e interface do usuário.
  • Construção dinâmica da interface do usuário: ArkTs fornece recursos dinâmicos de construção da interface do usuário, que podem não apenas personalizar a estrutura interna dos componentes, mas também reutilizar estilos de componentes e estender componentes nativos
  • Controle de renderização: ArkTs fornece principalmente controle de renderização condicional e controle de renderização de loop. A renderização condicional é principalmente para renderizar diferentes conteúdos de interface do usuário de acordo com as condições. A renderização de loop pode obter dados iterativamente da fonte de dados e criar componentes de interface do usuário no processo iterativo
  • Restrições e extensões de uso: o ArkTS possui restrições e restrições no processo de uso e também expande a ligação bidirecional e outros recursos.

Abaixo, usamos um exemplo específico para ilustrar a composição básica do ArkTS. No exemplo de código mostrado na figura abaixo, a interface UI contém um pedaço de texto e um botão. Quando o desenvolvedor clicar no botão, o conteúdo do texto mudará de 'Hello World' para 'Hello ArkUI'.

Os componentes básicos do paradigma de desenvolvimento declarativo ArkTS contidos neste exemplo são descritos a seguir:

  • Decorador: Usado para decorar classes, estruturas, métodos e variáveis ​​para dar a eles significados especiais, como @Entry, @Component e @State nos exemplos acima são todos decoradores. Especificamente, @Component indica que este é um componente personalizado; @Entry indica que este é um componente de entrada; @State indica que esta é uma variável de estado no componente e uma alteração nessa variável acionará uma atualização da interface do usuário.
  • Componentes personalizados: unidades de interface do usuário reutilizáveis ​​que podem ser combinadas com outros componentes, como a estrutura Hello mencionada acima, decorada por @Component.
  • Descrição da interface do usuário: uma abordagem declarativa para descrever a estrutura da interface do usuário, como blocos de código no método build().
  • Componentes integrados: Os componentes básicos integrados padrão, componentes de contêiner, componentes de mídia, componentes de desenho, componentes de tela e outros componentes no ArkTS podem ser chamados diretamente pelos desenvolvedores, como Coluna, Texto, Divisor, Botão, etc. exemplo.
  • Método de propriedade: usado para configurar as propriedades do componente, como fontSize(), width(), height(), color(), etc. Várias propriedades podem ser definidas por meio de chamadas em cadeia.
  • Método de evento: usado para adicionar lógica de resposta de componente a eventos, como onClick() seguindo Button, e várias lógicas de resposta de evento também podem ser definidas por meio de chamadas em cadeia.
  • Modificação de estado: É usado para modificar os dados vinculados à interface do usuário. Após o modificador @state ser modificado, a interface da interface do usuário correspondente também mudará quando o estado mudar.

2. Descrição básica da IU

ArkTs decora a estrutura de dados da palavra-chave struct life por meio dos decoradores @Component e @Entry para construir um componente personalizado. Uma função de compilação é fornecida no componente personalizado e o desenvolvedor precisa executar uma descrição básica da interface do usuário em uma chamada encadeada nessa função.

1. Conceitos básicos

  • Struct: Componentes personalizados podem ser implementados com base em struct e não podem ser usados ​​para herança.
  • Decorador: Um decorador confere ao objeto atualmente decorado certas capacidades. Ele pode não apenas decorar uma estrutura ou classe, mas também decorar os atributos de uma classe. Vários decoradores recomendam a escrita em várias linhas para ser mais padronizada .
  • função de compilação: os componentes personalizados devem definir uma função de compilação e os construtores personalizados são proibidos . A função build satisfaz a definição da interface do construtor Builder e é usada para definir a descrição declarativa da interface do usuário do componente.
  • @Component: Decore struct, a estrutura possui recursos baseados em componentes após a decoração e precisa implementar o método de construção para criar a interface do usuário.

  • @Entry: Decore a estrutura, o componente é decorado como a entrada da página e será renderizado e exibido quando a página for carregada.

  • Chamada em cadeia: Configure o método de propriedade, método de evento, etc. do componente de interface do usuário na forma de "." chamada em cadeia.

2. Especificação da descrição da IU

Construir configuração sem parâmetros

Caso a definição da interface do componente não contenha parâmetros de construção obrigatórios, não há necessidade de configurar nada no "()" atrás do componente. Por exemplo, os componentes Column e Divider não contêm parâmetros de construtor:

      coluna() {
        Text('Olá Mundo')
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
        Divider().width(200).height(10).color($r('app.color.button_next_background'))
        Text('Olá ArkTs')
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
      }

Construir configuração com parâmetros

Se a definição de interface do componente contiver parâmetros de construção, os parâmetros correspondentes podem ser configurados no "()" atrás do componente e os parâmetros podem ser atribuídos com constantes.

Por exemplo:

  • O parâmetro obrigatório src do componente Image:

Image($r('app.media.icon'))
  • O conteúdo do parâmetro do componente Texto, este parâmetro é opcional, ou seja, pode ser configurado ou não:
Texto('teste')

Variáveis ​​ou expressões também podem ser usadas para atribuição de parâmetros, e o tipo de resultado retornado pela expressão deve atender aos requisitos de tipo de parâmetro. Para a definição de variáveis, consulte gerenciamento de estado de variável em nível de página e gerenciamento de estado de variável em nível de aplicativo. Por exemplo, definir variáveis ​​ou expressões para construir parâmetros para componentes de imagem e texto:

Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`contagem: ${this.count}`)

configuração de atributos

Use o método de propriedade para configurar as propriedades do componente. O método de propriedade é seguido pelo componente e conectado com o operador ".".

  • Configure a propriedade de tamanho da fonte do componente Texto:

Texto('teste')
  .fontSize(12)
  • Use o operador "." para encadear chamadas e configurar várias propriedades do componente ao mesmo tempo, conforme a seguir:
Text("你好").fontSize(20).fontColor($r('app.color.button_next_background')).textAlign(TextAlign.End).width(200)
  • Além de passar parâmetros constantes diretamente, você também pode passar variáveis ​​ou expressões, como segue:
Text('olá')
  .fontSize(this.fontSize)
Image('teste.jpg')
  .width(this.count % 2 === 0 ? 100 : 200)    
  .height(this.offsetTest + 100)

  • Para componentes internos do sistema, a estrutura também predefine alguns tipos de enumeração para suas propriedades para os desenvolvedores chamarem. Os tipos de enumeração podem ser passados ​​como parâmetros e devem atender aos requisitos de tipo de parâmetro. Por exemplo, as propriedades de cor e fonte de um componente de texto podem ser configuradas da seguinte maneira
Text('olá')
  .fontSize(20)
  .fontColor(Color.Red)
  .fontWeight(FontWeight.Bold)

configuração do evento

Os eventos suportados pelo componente podem ser configurados através do método event O método event segue o componente e está conectado com o operador ".".

  • Use expressões lambda para configurar métodos de evento para componentes:

Botão() {
          Texto('Adicionar')
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .onClick(() => {
          this.messageNum++
        })
  • Use uma expressão de função anônima para configurar o método de evento do componente, exigindo o uso de bind (o evento click é inválido sem o uso de bind) para garantir que isso no corpo da função se refira ao componente contido:
Botão() {
          Text('Add'+this.messageNum)
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .onClick(função (){
          this.messageNum++
        }.bind(este))
  • Configure os métodos de evento do componente usando as funções de membro do componente:
Botão() {
          Text('Adicionar' + this.messageNum)
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .onClick(this.myClick.bind(this))

configuração do subcomponente

Para componentes que oferecem suporte à configuração de subcomponentes, como componentes de contêiner, adicione a descrição da interface do usuário dos subcomponentes ao componente em "{ ... }". Componentes como coluna, linha, pilha, grade e lista são todos componentes de contêiner.

  • Aqui está um exemplo simples de coluna:

coluna() {
        Botão() {
          Text('Adicionar' + this.messageNum)
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .onClick(this.myClick.bind(this))
        Text("你好").fontSize(20).fontColor($r('app.color.button_next_background')).textAlign(TextAlign.End).width(200)
      }
  • Os componentes do contêiner também podem ser aninhados entre si para obter um efeito de aninhamento de vários níveis relativamente complexo:
    Linha() {
      coluna() {
        this.TextBuild()
        Filho({ contagem: this.num })
        Filho2({ contagem: $num })
        Botão() {
          Mande mensagem de volta')
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .type(ButtonType.Capsule)
        .width('60%')
        .height('10%')
        .backgroundColor($r('app.color.button_next_background'))
        .onClick(() => {
          router.back()
        })

        Button() {
          Text(`Num++ ${this.num}`)
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .type(ButtonType.Capsule)
        .width('60%')
        .height('10%')
        .backgroundColor($r('app.color.button_next_background'))
        .margin({ top: 20 })
        .onClick(() => {
          this.num++
        })
      }
      .width('100%')
    }
    .height('100%')

三.状态管理

1.基本概念

ArkTS提供了多维度的状态管理机制,在ArkUI开发框架中,和UI相关联的数据,不仅可以在组件内使用,还可以在不同组件层级间传递,比如父子组件之间、爷孙组件之间,也可以是应用全局范围内的传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活地利用这些能力来实现数据和UI的联动。

2.页面级状态管理

页面级状态管理对比介绍

装饰器 装饰内容 说明
@state 基本数据类型,类,数组 修改后的状态数据被执行时会执行自定义组件中与此状态变量相关的UI元素,更新UI界面
@Prop 基本数据类型,类,数组 修改后的状态数据用于父组件与子组件之间建立单项数据流的绑定关系,当父组件中状态数据发生变化时子组件与之相关的UI元素也会进行页面更新
@Link 基本数据类型,类,数组 父组件与子组件建立双向的数据流绑定关系,当任意一方数据发生改变另一方与之相关的UI元素都会进行页面更新,将最新的数据状态展示出来
@Observed @Observed应用于类,表示该类中的数据变更被UI页面管理
@ObjectLink

被@Observed所装饰类的对象

@ObjectLink装饰的状态数据被修改时,在父组件或者其他兄弟组件内与它关联的状态数据所在的组件都会重新渲染。
@Provide 基本数据类型,类,数组 @Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面重新渲染。
@Consume 基本数据类型,类,数组 @Consume装饰的变量在感知到@Provide装饰的变量更新后,会触发当前自定义组件的重新渲染。

@State

@State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法中的部分UI描述(使用该状态变量的UI组件相关描述)进行UI刷新。

@State状态数据具有以下特征:

  • 支持多种类型数据:支持class、number、boolean、string强类型数据的值类型和引用类型,以及这些强类型构成的数组,即Array<class>、Array<string>、Array<boolean>、Array<number>。不支持any。
  • 支持多实例:组件不同实例的内部状态数据独立。
  • 内部私有:标记为@State的属性是私有变量,只能在组件内访问。
  • 需要本地初始化:必须为所有@State变量分配初始值,变量未初始化可能导致未定义的框架异常行为。
  • 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定@State状态变量的初始值。

现在我们来做一个示例,主要逻辑是在自定义组件MyComponent设置两个状态值为state的值count与title,如下:

// xxx.ets
@Entry
@Component
struct EntryComponent {
  build() {
    Column() {
      MyComponent({ count: 1, increaseBy: 2 }) // 第1个MyComponent实例
      MyComponent({ title: 'Hello World 2', count: 7 }) // 第2个MyComponent实例
    }
  }
}

@Component
struct MyComponent {
  @State count: number = 0
  @State title: string = 'Hello World'
  private increaseBy: number = 1

  build() {
    Column() {
      Text(this.title)
      Button('Click to change title')
        .margin(20)
        .onClick(() => {
          // 修改内部状态变量title
          this.title = (this.title != 'Hello World') ? 'Hello World' : 'Hello ArkUI'
        })

      Button(`Click to increase count=${this.count}`)
        .margin(20)
        .onClick(() => {
          // 修改内部状态变量count
          this.count += this.increaseBy
        })
    }
  }
}

上面的代码放在编辑器中尝试是可以看出点击Click to change title事件MyComponent是进行界面更新的并且两个MyComponent是相互不干扰的,同理点击Click to increase count=也是如此

@Prop

@Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但变量的更改不会通知给父组件,父组件变量的更改会同步到@prop装饰的变量,即@Prop属于单向数据绑定。

@Prop状态数据具有以下特征:

  • 支持多种类型数据:支持class、number、boolean、string强类型数据的值类型和引用类型,以及这些强类型构成的数组,即Array<class>、Array<string>、Array<boolean>、Array<number>。不支持any。
  • 私有:仅支持组件内访问;
  • 单个数据源:父组件中用于初始化子组件@Prop变量的必须是父组件定义的状态变量;
  • 单向通信:子组件对@Prop变量的更改将不会同步修改父组件中的@State变量,父组件中@State的变量修改将会同步给子组件中的@Prop变量;
  • 创建自定义组件时将按值传递方式给@Prop变量进行初始化:在创建组件的新实例时,必须初始化所有@Prop变量,不支持在组件内部进行初始化。

此处示例与下一个@Link装饰符一起演示,可以看出明显区别

@Link装饰的变量可以和父组件的@State变量建立双向数据绑定:

  • 支持多种类型:@Link支持的数据类型与@State相同,即class、number、string、boolean或这些类型的数组;
  • 私有:仅支持组件内访问;
  • 单个数据源:父组件中用于初始化子组件@Link变量的必须是父组件定义的状态变量;
  • 双向通信:子组件对@Link变量的更改将同步修改父组件中的@State变量;
  • 创建自定义组件时需要将变量的引用传递给@Link变量,在创建组件的新实例时,必须使用命名参数初始化所有@Link变量。@Link变量可以使用@State变量或@Link变量的引用进行初始化,@State变量可以通过'$'操作符创建引用。
  • @Link变量不能在组件内部进行初始化。

下面的示例是演示Child组件中的count变量是@Prop修饰的,Child2组件中的count变量是@Link修饰的,当点击Child组件的按钮时会发现只有Child组件count数据更新并更新了界面,当点击Child2组件的按钮时会发现所有的count数据都更新并且界面更新了,当点击父组件的按钮也会发现所有的组件中count数据发生了更新并且界面发生更新了

import router from '@ohos.router';

@Entry
@Component
struct Second {
  @State message: string = 'Hello World Second'
  @State num: number = 1

  build() {
    Row() {
      Column() {
        this.TextBuild()
        Child({ count: this.num })
        Child2({ count: $num })
        Button() {
          Text('Back')
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .type(ButtonType.Capsule)
        .width('60%')
        .height('10%')
        .backgroundColor($r('app.color.button_next_background'))
        .onClick(() => {
          router.back()
        })

        Button() {
          Text(`Num++ ${this.num}`)
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .type(ButtonType.Capsule)
        .width('60%')
        .height('10%')
        .backgroundColor($r('app.color.button_next_background'))
        .margin({ top: 20 })
        .onClick(() => {
          this.num++
        })
      }
      .width('100%')
    }
    .height('100%')
  }

  @Builder TextBuild(){
    Text(this.message)
      .align(Alignment.Center)
      .fontSize(40)
      .fontWeight(FontWeight.Bold)
  }
}

@Component
struct Child {
  //父组件可以改变子组件,子组件不能改变父组件
  @Prop count: number

  build() {
    Column() {
      Text(`You have ${this.count} Nuggets left`).fontSize(18)
      Button('count - costOfOneAttempt')
        .margin(15)
        .onClick(() => {
          this.count--
        })
    }
  }
}

@Component
struct Child2 {
  //父组件与子组件双向绑定
  @Link count: number

  build() {
    Column() {
      Text(`You have ${this.count} Nuggets left`).fontSize(18)
      Button('count - costOfOneAttempt')
        .margin(15)
        .onClick(() => {
          this.count--
        })
    }
  }
}

@Observed和ObjectLink数据管理

当开发者需要在子组件中针对父组件的一个变量(parent_a)设置双向同步时,开发者可以在父组件中使用@State装饰变量(parent_a),并在子组件中使用@Link装饰对应的变量(child_a)。这样不仅可以实现父组件与单个子组件之间的数据同步,也可以实现父组件与多个子组件之间的数据同步。如下图所示,可以看到,父子组件针对ClassA类型的变量设置了双向同步,那么当子组件1中变量对应的属性c的值变化时,会通知父组件同步变化,而当父组件中属性c的值变化时,会通知所有子组件同步变化。而当需要传递数组其中一个实例时,使用@Link就不能满足要求。如果这些部分信息是一个类对象,就可以使用@ObjectLink配合@Observed来实现

设置要求:

  • @Observed用于类,@ObjectLink用于变量。

  • @ObjectLink装饰的变量类型必须为类(class type)。

    • 类要被@Observed装饰器所装饰。
    • 不支持简单类型参数,可以使用@Prop进行单向同步。
  • @ObjectLink装饰的变量是不可变的。

    • 属性的改动是被允许的,当改动发生时,如果同一个对象被多个@ObjectLink变量所引用,那么所有拥有这些变量的自定义组件都会被通知进行重新渲染。
  • @ObjectLink装饰的变量不可设置默认值。

    • 必须让父组件中有一个由@State、@Link、@StorageLink、@Provide或@Consume装饰的变量所参与的TS表达式进行初始化。
  • @ObjectLink装饰的变量是私有变量,只能在组件内访问。

示例与下面的修饰符一起演示

@Provide和@Consume

@Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。@Consume在感知到@Provide数据的更新后,会触发当前自定义组件的重新渲染。

@Provide

  • 装饰器参数是一个string类型的常量,用于给装饰的变量起别名。如果规定别名,则提供对应别名的数据更新。如果没有,则使用变量名作为别名。推荐使用@Provide('alias')这种形式。
  • 同步机制:@Provide的变量类似@State,可以修改对应变量进行页面重新渲染。也可以修改@Consume装饰的变量,反向修改@State变量。
  • 必须设置初始值。
  • 触发页面渲染的修改:基础类型(boolean,string,number)变量的改变;@Observed class类型变量及其属性的修改;添加,删除,更新数组中的元素。

@Consume

  • 不可设置默认初始值。

示例如下:

import router from '@ohos.router';

@Entry
@Component
struct Third {
  //因为是数组所以需要用到@ObjectLink和@Observed
  @State arrA: ClassA[] = [new ClassA(2), new ClassA(0)]
  //此修饰符与@Consume组合可以让其与孙子节点数据双向绑定
  @Provide("reviewVote") reviewVotes: number = 0;

  build() {
    Row() {
      Column() {
        ForEach(this.arrA, (item) => {
          TextChild({ a: item })
        }, (item) => item.id.toString())
        ForEach(this.arrA, (item) => {
          Child({ a: item })
        }, (item) => item.id.toString())
        Button() {
          Text('Back')
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .type(ButtonType.Capsule)
        .width('60%')
        .height('10%')
        .backgroundColor($r('app.color.button_next_background'))
        .onClick(() => {
          router.back()
        })

        Button() {
          Text('Add')
            .fontSize(45)
            .fontColor($r('app.color.start_window_background'))
        }
        .type(ButtonType.Capsule)
        .width('60%')
        .height('5%')
        .backgroundColor($r('app.color.button_next_background'))
        .margin({ top: 20 })
        .onClick(() => {
          this.arrA[0].c++
        })
        Button() {
          Text('AddChildChild'+this.reviewVotes)
            .fontSize(25)
            .fontColor($r('app.color.start_window_background'))
        }
        .type(ButtonType.Capsule)
        .width('60%')
        .height('10%')
        .backgroundColor($r('app.color.button_next_background'))
        .margin({ top: 20 })
        .onClick(() => {
          this.reviewVotes++
        })
      }
      .width('100%')
    }
    .height('100%')
  }
}

@Component
struct TextChild {
  @ObjectLink a: ClassA

  build() {
    Column() {
      Text(this.a.c + "TextChild")
        .align(Alignment.Center)
        .fontSize(40)
        .fontWeight(FontWeight.Bold)
      TextChildChild()
    }
  }
}

@Component
struct TextChildChild {
  //此修饰符与爷爷组件的@Provide组合可以与爷爷组件双向绑定
  @Consume("reviewVote") reviewVotes: number

  build() {
    Column() {
      Button() {
        Text('RemoveChildChild'+this.reviewVotes)
          .fontSize(20)
          .fontColor($r('app.color.start_window_background'))
      }
      .type(ButtonType.Capsule)
      .width('60%')
      .height('5%')
      .backgroundColor($r('app.color.button_next_background'))
      .margin({ top: 20 })
      .onClick(() => {
        this.reviewVotes--
      })
      Text(this.reviewVotes + "TextChildChild")
        .align(Alignment.Center)
        .fontSize(40)
        .fontWeight(FontWeight.Bold)
    }
  }
}

@Component
struct Child {
  @ObjectLink a: ClassA

  build() {
    Column() {
      Text(this.a.c + "Child")
        .align(Alignment.Center)
        .fontSize(40)
        .fontWeight(FontWeight.Bold)
      Button('count - costOfOneAttempt')
        .margin(15)
        .onClick(() => {
          this.a.c--
        })
    }
  }
}

var nextID: number = 0

@Observed
class ClassA {
  public name: string
  public c: number
  public id: number

  constructor(c: number, name: string = 'OK') {
    this.name = name
    this.c = c
    this.id = nextID++
  }
}

@Watch

@Watch用于监听状态变量的变化,语法结构为:

  1. @State @Watch("onChanged") count : number = 0

如上所示,给状态变量增加一个@Watch装饰器,通过@Watch注册一个回调方法onChanged, 当状态变量count被改变时, 触发onChanged回调。

装饰器@State、@Prop、@Link、@ObjectLink、@Provide、@Consume、@StorageProp以及@StorageLink所装饰的变量均可以通过@Watch监听其变化。

import router from '@ohos.router';

// xxx.ets
@Entry
@Component
struct CompA {
  @State @Watch('onBasketUpdated') shopBasket: Array<number> = [7, 12, 47, 3]
  @State totalPurchase: number = 0
  @State addPurchase: number = 0

  aboutToAppear() {
    this.updateTotal()
  }

  updateTotal(): void {
    let sum = 0;
    this.shopBasket.forEach((i) => {
      sum += i
    })
    // 计算新的购物篮总价值,如果超过100,则适用折扣
    this.totalPurchase = (sum < 100) ? sum : 0.9 * sum
  }

  // shopBasket更改时触发该方法
  onBasketUpdated(propName: string): void {
    this.updateTotal()
  }

  build() {
    Column() {
      Button('add to basket ' + this.addPurchase)
        .margin(15)
        .onClick(() => {
          this.addPurchase = Math.round(100 * Math.random())
          this.shopBasket.push(this.addPurchase)
        })
      Text(`${this.totalPurchase}`)
        .fontSize(30)
      Button() {
        Text('Back')
          .fontSize(45)
          .fontColor($r('app.color.start_window_background'))
      }
      .type(ButtonType.Capsule)
      .width('60%')
      .height('10%')
      .backgroundColor($r('app.color.button_next_background'))
      .onClick(() => {
        router.back()
      })
    }
  }
}

3.应用级变量的状态管理

AppStorage

AppStorage是应用程序中的单例对象,由UI框架在应用程序启动时创建,在应用程序退出时销毁,为应用程序范围内的可变状态属性提供中央存储。

AppStorage包含整个应用程序中需要访问的所有状态属性,只要应用程序保持运行,AppStorage就会保存所有属性及属性值,属性值可以通过唯一的键值进行访问。

组件可以通过装饰器将应用程序状态数据与AppStorage进行同步,应用业务逻辑的实现也可以通过接口访问AppStorage。

AppStorage的选择状态属性可以与不同的数据源或数据接收器同步,这些数据源和接收器可以是设备上的本地或远程,并具有不同的功能,如数据持久性。这样的数据源和接收器可以独立于UI在业务逻辑中实现。

默认情况下,AppStorage中的属性是可变的,AppStorage还可使用不可变(只读)属性。

@StorageLink装饰器

组件通过使用@StorageLink(key)装饰的状态变量,与AppStorage建立双向数据绑定,key为AppStorage中的属性键值。当创建包含@StorageLink的状态变量的组件时,该状态变量的值将使用AppStorage中的值进行初始化。在UI组件中对@StorageLink的状态变量所做的更改将同步到AppStorage,并从AppStorage同步到任何其他绑定实例中,如PersistentStorage或其他绑定的UI组件。

@StorageProp装饰器

组件通过使用@StorageProp(key)装饰的状态变量,与AppStorage建立单向数据绑定,key标识AppStorage中的属性键值。当创建包含@StorageProp的状态变量的组件时,该状态变量的值将使用AppStorage中的值进行初始化。AppStorage中属性值的更改会导致绑定该状态变量的UI组件进行状态更新。

示例

每次用户单击Count按钮时,this.varA变量值都会增加1,此变量与AppStorageA中的varA同步。每次用户单击language按钮时,修改AppStorageA中的languageCode,此修改会同步给this.languageCode变量。点击跳转到AppStorageB,会发现varA的值和languageCode的值是传递过去了。

import router from '@ohos.router';

// xxx.ets
@Entry
@Component
struct AppStorageA {
  @StorageLink('varA') varA: number = 2
  @StorageProp('languageCode') languageCode: string = 'en'
  @State private label: string = 'count'

  build() {
    Column() {
      Row({ space: 20 }) {
        Button(`${this.label}: ${this.varA}`)
          .onClick(() => {
            AppStorage.Set<number>('varA', AppStorage.Get<number>('varA') + 1)
          })
        Button(`language: ${this.languageCode}`)
          .onClick(() => {
            if (AppStorage.Get<string>('languageCode') === 'zh') {
              AppStorage.Set<string>('languageCode', 'en')
            } else {
              AppStorage.Set<string>('languageCode', 'zh')
            }
            this.label = (this.languageCode === 'zh') ? '数量' : 'Count'
          })
      }
      .margin({ bottom: 50 })

      Row() {
        Button(`更改@StorageLink修饰的变量:${this.varA}`).height(40).fontSize(14)
          .onClick(() => {
            this.varA++
          })
      }.margin({ bottom: 50 })

      Row() {
        Button(`更改@StorageProp修饰的变量:${this.languageCode}`).height(40).fontSize(14)
          .onClick(() => {
            if (this.languageCode === 'zh') {
              this.languageCode = 'en'
            } else {
              this.languageCode = 'zh'
            }
          })
      }.margin({bottom:100})
      Row() {
        Button(`跳转到AppStorageB`).height(40).fontSize(14)
          .onClick(() => {
            router.pushUrl({url:'pages/AppStorageB'})
          })
      }
    }
  }
}

@Entry
@Component
struct AppStorageB {
  @StorageLink('varA') varA: number = 2
  @StorageProp('languageCode') languageCode: string = 'en'
  @State private label: string = 'count'

  build() {
    Column() {
      Text(`language: ${this.languageCode}`).margin({top:100})
      Text(`varA: ${this.varA}`).margin({top:100})
    }.height('100%').width('100%')
  }
}

LocalStorage

LocalStorage是应用程序中的存储单元,生命周期跟随其关联的Ability。在Stage模型下,LocalStorage解决AppStorage共享范围过大的问题,提供Ability之间全局数据的隔离。同时,LocalStorage为应用程序范围内的可变状态属性和非可变状态属性提供存储,可变状态属性和非可变状态属性是构建应用程序UI的一部分,如一个Ability的UI。解决App与Ability之间数据互相干扰问题,多实例场景下同一个Ability类的不同Ability实例之间的数据互相干扰问题。在分布式迁移的场景下,Ability是系统调度的最小单元,配合LocalStorage更方便实现组件的数据迁移。

应用层:一个应用程序可以创建多个LocalStorage实例,应用程序的每一个Ability对应一个LocalStorage实例。

Ability:一个应用程序可以拥有多个Ability,一个Ability中的所有子组件最多可以分配一个LocalStorage实例。并且,Ability中的所有子组件都将继承对此LocalStorage实例存储对象的访问权。

一个组件最多可以访问一个LocalStorage实例,一个LocalStorage对象可以分配给多个组件。

@LocalStorageLink装饰器

组件通过使用@LocalStorageLink(key)装饰的状态变量,key值为LocalStorage中的属性键值,与LocalStorage建立双向数据绑定。当创建包含@LocalStorageLink的状态变量的组件时,该状态变量的值将会使用LocalStorage中的值进行初始化。如果LocalStorage中未定义初始值,将使用@LocalStorageLink定义的初始值。在UI组件中对@LocalStorageLink的状态变量所做的更改将同步到LocalStorage中,并从LocalStorage同步到Ability下的组件中。

@LocalStorageProp装饰器

组件通过使用LocalStorageProp(key)装饰的状态变量,key值为LocalStorage中的属性键值,与LocalStorage建立单向数据绑定。当创建包含@LocalStorageProp的状态变量的组件时,该状态变量的值将使用LocalStorage中的值进行初始化。LocalStorage中的属性值的更改会导致当前Ability下的所有UI组件进行状态更新。

具体用法参考:

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';

export default class EntryAbility extends UIAbility {
    storage: LocalStorage

    onCreate(want, launchParam) {
        this.storage = new LocalStorage()
        this.storage.setOrCreate('storageSimpleProp', 8)
        hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
    }

    onDestroy() {
        hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
    }

    onWindowStageCreate(windowStage: window.WindowStage) {
        // Main window is created, set main page for this ability
        hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
        windowStage.loadContent('pages/LocalStorageComponent', this.storage)
    }
}

import router from '@ohos.router';

// xxx.ets
// Index.ets
let storage = LocalStorage.GetShared()

@Entry(storage)
@Component
struct LocalStorageComponent {
  @LocalStorageLink('storageSimpleProp') simpleVarName: number = 0

  build() {
    Column() {
      Button(`LocalStorageLink: ${this.simpleVarName.toString()}`)
        .margin(20)
        .onClick(() => {
          this.simpleVarName += 1
        })
      Text(JSON.stringify(this.simpleVarName))
        .fontSize(50)
      LocalStorageComponentProp()
    }.width('100%')
  }
}

@Component
struct LocalStorageComponentProp {
  @LocalStorageProp('storageSimpleProp') simpleVarName: number = 0

  build() {
    Column() {
      Button(`LocalStorageProp: ${this.simpleVarName.toString()}`)
        .margin(20)
        .onClick(() => {
          this.simpleVarName += 1
        })
      Text(JSON.stringify(this.simpleVarName))
        .fontSize(50)
    }.width('100%')
  }
}

PersistentStorage

PersistentStorage提供了一些静态方法用来管理应用持久化数据,可以将特定标记的持久化数据链接到AppStorage中,并由AppStorage接口访问对应持久化数据,或者通过@StorageLink装饰器来访问对应key的变量。

// xxx.ets
PersistentStorage.PersistProp('highScore', 10)

@Entry
@Component
struct PersistentComponent {
  @StorageLink('highScore') highScore: number = 0
  @State currentScore: number = 0

  build() {
    Column() {
      if (this.currentScore === Number(this.highScore)) {
        Text(`new highScore : ${this.highScore}`).fontSize(18)
      }
      Button(`goal!, currentScore : ${this.currentScore}`)
        .margin(20)
        .onClick(() => {
          this.currentScore++
          if (this.currentScore > Number(this.highScore)) {
            this.highScore = this.currentScore
          }
        })
    }.width('100%')
  }
}

Environment

Environment是框架在应用程序启动时创建的单例对象,它为AppStorage提供了一系列应用程序需要的环境状态数据,这些数据描述了应用程序运行的设备环境,包括系统语言、深浅色模式等等。Environment及其属性是不可变的,所有数据类型均为简单类型。如下示例展示了从Environment获取系统是否开启无障碍屏幕朗读:

Environment.EnvProp('accessibilityEnabled', 'default')
var enable = AppStorage.Get('accessibilityEnabled')

accessibilityEnabled是Environment提供的系统默认变量识别符。首先需要将对应系统属性绑定到AppStorage上,再通过AppStorage中的方法或者装饰器访问对应的系统属性数据。

四.动态构建UI元素

基本UI描述介绍的是如何创建一个内部UI结构固定的自定义组件,为了满足开发者自定义组件内部UI结构的需求,ArkTS同时提供了动态构建UI元素的能力。

@Builder

可通过@Builder装饰器进行描述,该装饰器可以修饰一个函数,此函数可以在build函数之外声明,并在build函数中或其他@Builder修饰的函数中使用,从而实现在一个自定义组件内快速生成多个布局内容。使用方式如下面示例所示。

@Entry
@Component
struct Second {
  @State message: string = 'Hello World Second'
  @State num: number = 1

  build() {
    Row() {
      Column() {
        this.TextBuild()
      }
      .width('100%')
    }
    .height('100%')
  }

  @Builder TextBuild(){
    Text(this.message)
      .align(Alignment.Center)
      .fontSize(40)
      .fontWeight(FontWeight.Bold)
  }
}

@BuilderParam8+

@BuilderParam装饰器用于修饰自定义组件内函数类型的属性(例如:@BuilderParam noParam: () => void),并且在初始化自定义组件时被@BuilderParam修饰的属性必须赋值。

引入动机

当开发者创建自定义组件,并想对该组件添加特定功能时(例如在自定义组件中添加一个点击跳转操作)。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,引入了@BuilderParam装饰器,此装饰器修饰的属性值可为@Builder装饰的函数,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。

参数初始化组件

通过参数初始化组件时,将@Builder装饰的函数赋值给@BuilderParam修饰的属性,并在自定义组件内调用该属性值。若@BuilderParam修饰的属性在进行赋值时不带参数(如:noParam: this.specificNoParam),则此属性的类型需定义为无返回值的函数(如:@BuilderParam noParam: () => void);若带参数(如:withParam: this.SpecificWithParam('WithParamA')),则此属性的类型需定义成any(如:@BuilderParam withParam: any)。

// xxx.ets
@Component
struct CustomContainer {
  header: string = ''
  @BuilderParam noParam: () => void
  @BuilderParam withParam: any
  footer: string = ''

  build() {
    Column() {
      Text(this.header)
        .fontSize(30)
      this.noParam()
      this.withParam()
      Text(this.footer)
        .fontSize(30)
    }
  }
}

@Entry
@Component
struct CustomContainerUser {
  @Builder specificNoParam() {
    Column() {
      Text('noParam').fontSize(30)
    }
  }

  @Builder SpecificWithParam(label: string) {
    Column() {
      Text(label).fontSize(30)
    }
  }

  build() {
    Column() {
      CustomContainer({
        header: 'HeaderA',
        noParam: this.specificNoParam,
        withParam: this.SpecificWithParam('WithParamA'),
        footer: 'FooterA'
      })
      Divider().color(Color.Black)
        .strokeWidth(3)
        .margin(10)
      CustomContainer({
        header: 'HeaderB',
        noParam: this.specificNoParam,
        withParam: this.SpecificWithParam('WithParamB'),
        footer: 'FooterB'
      })
    }
  }
}

@Styles

ArkTS为了避免开发者对重复样式的设置,通过@Styles装饰器可以将多个样式设置提炼成一个方法,直接在组件声明时调用,通过@Styles装饰器可以快速定义并复用自定义样式。当前@Styles仅支持通用属性。

@Styles可以定义在组件内或组件外,在组件外定义时需在方法名前面添加function关键字,组件内定义时则不需要添加function关键字。

// xxx.ets
@Styles function globalFancy () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  @Styles componentFancy() {
    .width(100)
    .height(200)
    .backgroundColor(Color.Yellow)
  }

  build() {
    Column({ space: 10 }) {
      Text('FancyA')
        .globalFancy()
        .fontSize(30)
      Text('FancyB')
        .globalFancy()
        .fontSize(20)
      Text('FancyC')
        .componentFancy()
        .fontSize(30)
      Text('FancyD')
        .componentFancy()
        .fontSize(20)
    }
  }
}

@Styles还可以在StateStyles属性内部使用,在组件处于不同的状态时赋予相应的属性。

在StateStyles内可以直接调用组件外定义的@Styles方法,但需要通过this关键字调用组件内定义的@Styles方法。

// xxx.ets
@Styles function globalFancy () {
  .width(120)
  .height(120)
  .backgroundColor(Color.Green)
}

@Entry
@Component
struct FancyUse {
  @Styles componentFancy() {
    .width(80)
    .height(80)
    .backgroundColor(Color.Red)
  }

  build() {
    Row({ space: 10 }) {
      Button('Fancy')
        .stateStyles({
          normal: {
            .width(100)
            .height(100)
            .backgroundColor(Color.Blue)
          },
          disabled: this.componentFancy,
          pressed: globalFancy
        })
    }
  }
}

@Extend

@Extend装饰器将新的属性方法添加到Text、Column、Button等内置组件上,通过@Extend装饰器可以快速地扩展原生组件。@Extend不能定义在自定义组件struct内。

// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
  .fontColor(Color.Red)
  .fontSize(fontSize)
  .fontStyle(FontStyle.Italic)
  .fontWeight(600)
}

@Entry
@Component
struct FancyUse {
  build() {
    Row({ space: 10 }) {
      Text("Fancy")
        .fancy(16)
      Text("Fancy")
        .fancy(24)
      Text("Fancy")
        .fancy(32)
    }
  }
}

@CustomDialog

@CustomDialog装饰器用于装饰自定义弹窗组件,使得弹窗可以动态设置内容及样式。

import router from '@ohos.router';

// xxx.ets
@CustomDialog
struct DialogExample {
  controller: CustomDialogController
  action: () => void

  build() {
    Row() {
      Button('Close CustomDialog')
        .onClick(() => {
          this.controller.close()
          this.action()
        })
    }.padding(20)
  }
}

@Entry
@Component
struct CustomDialogUser {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: DialogExample({ action: this.onAccept }),
    cancel: this.existApp,
    autoCancel: true
  });

  onAccept() {
    console.info('onAccept');
  }

  existApp() {
    console.info('Cancel dialog!');
  }

  build() {
    Column() {
      Button('Click to open Dialog')
        .onClick(() => {
          this.dialogController.open()
        })
    }
  }
}

五.渲染控制

ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。

条件渲染

使用if/else进行条件渲染。

f/else语句的每个分支都包含一个构建UI的描述,此类描述中必须创建一个或多个子组件。在初始渲染时,if语句会执行其中的一个分支并将生成的子组件添加到if/else的父组件中。

每当if或else if条件语句中使用的状态变量发生变化时,条件渲染都会更新并重新进行条件评估,如果条件分支发生了变化,这意味着需要构建另一个条件分支,此时框架将:

  1. 删除所有以前渲染的(早期分支的)组件。
  2. 执行分支的UI描述,将生成的子组件添加到其父组件中。

在以下示例中,如果count从0增加到1,那么条件渲染会进行更新并计算各个条件分支值:

  • 条件(count > 0)计算结果为false,未发生变化,不执行额外操作;
  • 条件(this.count % 2 === 0)从true更改为false,该分支内的Text组件将从Column组件中移除并销毁;
  • else分支为true,执行该条件下的UI描述,创建一个Text组件,并将它放在Column组件内。
@Entry
@Component
struct MyComponent {
  @State count: number = 300
  build() {
    Column() {
      if (this.count < 0) {
        Text('count is negative').fontSize(14)
      } else if (this.count % 2 === 0) {
        Text('count is even').fontSize(14)
      } else {
        Text('count is odd').fontSize(14)
      }
    }
  }
}

循环渲染

通过循环渲染(ForEach)从数组中获取数据,并为每个数据项创建相应的组件,可减少代码复杂度。

ForEach(
  arr: any[], 
  itemGenerator: (item: any, index?: number) => void,
  keyGenerator?: (item: any, index?: number) => string 
)

参数:

参数名

参数类型

必填

参数描述

arr

any[]

必须是数组,允许设置为空数组,空数组场景下将不会创建子组件。同时允许设置返回值为数组类型的函数,例如arr.slice(1, 3),设置的函数不得改变包括数组本身在内的任何状态变量,如Array.splice、Array.sort或Array.reverse这些改变原数组的函数。

itemGenerator

(item: any, index?: number) => void

生成子组件的lambda函数,为数组中的每一个数据项创建一个或多个子组件,单个子组件或子组件列表必须包括在大括号“{...}”中。

keyGenerator

(item: any, index?: number) => string

匿名函数,用于给数组中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,不提供时框架默认使用index+JSON.stringify(item)的方式生成。

为了使开发框架能够更好地识别数组更改,提高性能,建议开发者提供自定义的键值生成器。如将数组反向时,如果没有提供键值生成器,则ForEach中的所有节点都将重建。

示例:

@Entry
@Component
struct MyComponent {
  @State arr: number[] = [10, 20, 30]

  build() {
    Column({ space: 5 }) {
      Button('Reverse Array')
        .onClick(() => {
          this.arr.reverse()
        })

      ForEach(this.arr, (item: number) => {
        Text(`item value: ${item}`).fontSize(18)
        Divider().strokeWidth(2).color(Color.Black)
      }, (item: number) => item.toString())
    }
  }
}

数据懒加载

通过数据懒加载(LazyForEach)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。

LazyForEach(
  dataSource: IDataSource,             
  itemGenerator: (item: any) => void,  
  keyGenerator?: (item: any) => string 
): void

interface IDataSource {
  totalCount(): number;                                           
  getData(index: number): any;                                   
  registerDataChangeListener(listener: DataChangeListener): void;   
  unregisterDataChangeListener(listener: DataChangeListener): void;
}

interface DataChangeListener {
  onDataReloaded(): void;                      
  onDataAdd(index: number): void;            
  onDataMove(from: number, to: number): void; 
  onDataDelete(index: number): void;         
  onDataChange(index: number): void;          
}

参数:

参数名

参数类型

必填

参数描述

dataSource

IDataSource

实现IDataSource接口的对象,需要开发者实现相关接口。

itemGenerator

(item: any, index?: number) => void

生成子组件的lambda函数,为数组中的每一个数据项创建一个或多个子组件,单个子组件或子组件列表必须包括在大括号“{...}”中。

keyGenerator

(item: any, index?: number) => string

匿名函数,用于给数组中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,不提供时框架默认使用index+JSON.stringify(item)的方式生成。

为了使开发框架能够更好地识别数组更改,提高性能,建议提供自定义的键值生成器。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。

IDataSource类型说明

名称

描述

totalCount(): number

获取数据总数。

getData(index: number): any

获取索引值index对应的数据。

registerDataChangeListener(listener:DataChangeListener): void

注册数据改变的监听器。

unregisterDataChangeListener(listener:DataChangeListener): void

注销数据改变的监听器。

DataChangeListener类型说明

名称

描述

onDataReloaded(): void

重新加载所有数据。

onDataAdded(index: number): voiddeprecated

通知组件index的位置有数据添加。从API Version 8开始废弃,建议使用onDataAdd。

onDataMoved(from: number, to: number): voiddeprecated

通知组件数据从from的位置移到to的位置。从API Version 8开始废弃,建议使用onDataMove。

onDataDeleted(index: number): voiddeprecated

通知组件index的位置有数据删除。从API Version 8开始废弃,建议使用onDataDelete。

onDataChanged(index: number): voiddeprecated

通知组件index的位置有数据变化。 从API Version 8开始废弃,建议使用onDataChange。

onDataAdd(index: number): void8+

通知组件index的位置有数据添加。

onDataMove(from: number, to: number): void8+

通知组件数据从from的位置移到to的位置。

onDataDelete(index: number): void8+

通知组件index的位置有数据删除。

onDataChange(index: number): void8+

通知组件index的位置有数据变化。

示例:

// xxx.ets
class BasicDataSource implements IDataSource {
  private listeners: DataChangeListener[] = []

  public totalCount(): number {
    return 0
  }

  public getData(index: number): any {
    return undefined
  }

  registerDataChangeListener(listener: DataChangeListener): void {
    if (this.listeners.indexOf(listener) < 0) {
      console.info('add listener')
      this.listeners.push(listener)
    }
  }

  unregisterDataChangeListener(listener: DataChangeListener): void {
    const pos = this.listeners.indexOf(listener);
    if (pos >= 0) {
      console.info('remove listener')
      this.listeners.splice(pos, 1)
    }
  }

  notifyDataReload(): void {
    this.listeners.forEach(listener => {
      listener.onDataReloaded()
    })
  }

  notifyDataAdd(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataAdd(index)
    })
  }

  notifyDataChange(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataChange(index)
    })
  }

  notifyDataDelete(index: number): void {
    this.listeners.forEach(listener => {
      listener.onDataDelete(index)
    })
  }

  notifyDataMove(from: number, to: number): void {
    this.listeners.forEach(listener => {
      listener.onDataMove(from, to)
    })
  }
}

class MyDataSource extends BasicDataSource {
  // 初始化数据列表
  private dataArray: string[] = ['/path/image0.png', '/path/image1.png', '/path/image2.png', '/path/image3.png']

  public totalCount(): number {
    return this.dataArray.length
  }

  public getData(index: number): any {
    return this.dataArray[index]
  }

  public addData(index: number, data: string): void {
    this.dataArray.splice(index, 0, data)
    this.notifyDataAdd(index)
  }

  public pushData(data: string): void {
    this.dataArray.push(data)
    this.notifyDataAdd(this.dataArray.length - 1)
  }
}

@Entry
@Component
struct MyComponent {
  private data: MyDataSource = new MyDataSource()

  build() {
    List({ space: 3 }) {
      LazyForEach(this.data, (item: string) => {
        ListItem() {
          Row() {
            Image(item).width(50).height(50)
            Text(item).fontSize(20).margin({ esquerda: 10 })
          }.margem({ esquerda: 10, direita: 10 })
        }
        .onClick(() => {
          // Sempre que um item da lista é clicado, os dados aumentam em um item
          this.data.pushData('/caminho/imagem' + this.data.totalCount() + '.png')
        })
      }, item => item)
    }
  }
}

Acho que você gosta

Origin blog.csdn.net/gongjdde/article/details/130242541
Recomendado
Clasificación