鸿蒙NEXT场景化开发:探索高级视图

237 阅读18分钟

在第 1 和 2 章中分享了 ArkUI 基础组件的使用,以及如何使用基础组件和容器组件来设计精美的视图,本章将介绍Grid(网格)、Swiper(轮播)、WaterFlow(瀑布流) 等高级容器组件的使用场景。

数据懒加载是一种按需加载数据的策略,适用于数据量较大的场景,可以有效减少页面的初始渲染时间和内存占用。ArkUI 提供了LazyForEach 函数来实现数据的按需加载能力,当 List、Scroll、Grid、Swiper、WaterFlow 等容器组件使用LazyForEach 函数时,LazyForEach 仅会渲染当前屏幕可见的数据及其前后部分数据,而不会一次性加载整个数据集。这种方式可以有效降低内存占用,并提升应用运行的流畅度。

3.1 实现网格布局

Grid 组件是一种基于行和列的布局方式,能够将页面划分为多个整齐的单元格,非常适合用于展示具有规律性排列的内容,如相册中的图片展示、电商应用中的商品分类等。

创建一个名为MyGrid的新的 HarmonyOS 项目,并打开工程开发面板。

3.1.1 实现懒加载工具类

在 ets 模块目录下创建一个新的目录,命名为common,用于存放项目中使用到的通用模块。在common 目录下新增一个名为 DataSource.ets 的 ArkTS 文件,用于实现通用的数据源类,该类实现IDataSource接口,使其可用于LazyForEach等组件,代码如下:

// 第3章/DataSource.ets
export class DataSource<T> implements IDataSource {
  private dataArray: T[] = [];
  private listeners: DataChangeListener[] = [];

  constructor(data?: T[]) {
    if (data) this.dataArray = data;
  }

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

  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);
    });
  }

  notifyDatasetChange(operations: DataOperation[]): void {
    this.listeners.forEach(listener => {
      listener.onDatasetChange(operations);
    });
  }

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

  registerDataChangeListener(listener: DataChangeListener): void {
    if (!this.listeners.includes(listener)) {
      this.listeners.push(listener);
    }
  }

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

  public addFirstItem(item: T): void {
    this.dataArray.unshift(item);
    this.notifyDataAdd(0);
  }

  public addLastItem(item: T): void {
    this.dataArray.push(item);
    this.notifyDataAdd(this.dataArray.length - 1);
  }

  public addItem(item: T): void {
    this.dataArray.push(item)
    this.notifyDataAdd(this.dataArray.length - 1)
  }

  public deleteFirstItem(): void {
    if (this.dataArray.length > 0) {
      this.dataArray.shift();
      this.notifyDataDelete(0);
    }
  }

  public deleteLastItem(): void {
    if (this.dataArray.length > 0) {
      this.dataArray.pop();
      this.notifyDataDelete(this.dataArray.length);
    }
  }

  public deleteItem(index: number): void {
    if (index >= 0 && index < this.dataArray.length) {
      this.dataArray.splice(index, 1);
      this.notifyDataDelete(index);
    }
  }

  public reload(): void {
    this.dataArray = [];
    this.notifyDataReload();
  }
}

DataSource 是一个泛型类,专门用于管理瀑布流布局中的数据集合。通过 export 关键字加以声明,使得该类可以被外部文件轻松调用。在类的内部,声明了一个泛型数组 dataArray,用于存储和管理瀑布流中的数据项。同时,还声明了一个 DataChangeListener 类型的数组 listeners,用于存储数据变化的监听器,后续通过该数组实现对外部数据变化的实时通知。

在 DataSource 的构造函数中,通过可选参数 data 初始化数据数组 dataArray,如果未提供 data 参数,则 dataArray 默认为一个空数组,这种设计方式可以让开发者根据需要灵活地初始化数据源。

DataSource 提供了一系列方法来操作数据集合,其中 totalCount() 方法用于获取数据集合的总数,方便开发者随时了解当前数据源的大小。getData() 方法可以根据指定的索引获取数据项,从而实现对数据的精确访问。 addFirstItem() 方法用于在数据集合的开头添加一个数据项,并触发相应的数据变更通知,addLastItem() 方法则用于在数据集合的末尾添加一个数据项。

此外,addItem() 方法允许开发者插入数据项,deleteFirstItem()、deleteLastItem() 和 deleteItem() 方法分别用于删除数据集合的第一个、最后一个或指定索引位置的数据项,并在每次操作后触发数据变更通知。

数据变更通知机制是 DataSource 的核心功能之一。notifyDataAdd()、notifyDataDelete()、notifyDataChange()、notifyDataMove() 和 notifyDatasetChange() 方法分别用于通知监听器数据的添加、删除、修改、移动以及批量操作。这些方法通过遍历 listeners 数组,调用每个监听器对应的回调方法,确保所有已注册的监听器都能及时响应数据的变化。

为了管理监听器,DataSource 提供了 registerDataChangeListener() 和 unregisterDataChangeListener() 方法。registerDataChangeListener() 方法用于注册数据变更监听器,将监听器添加到 listeners 数组中;而 unregisterDataChangeListener() 方法则用于取消注册监听器,如果监听器存在于 listeners 数组中,则将其移除。这种机制使得开发者可以灵活地控制哪些组件需要接收数据变更通知。

最后,DataSource 还提供了 reload() 方法,用于清空当前的数据集合并触发数据重新加载的通知。

DataSource 类不仅能够高效地存储和管理瀑布流布局中的数据,还能在数据发生变化时及时通知所有已注册的监听器。开发者可以将 DataSource.ets 作为一个通用的工具类,用于管理应用中的瀑布流数据源,从而实现动态数据的高效管理和瀑布流布局的实时更新。

3.1.2 实现网格布局

回到 Index.ets 文件中,使用 private 关键字声明一个私有的DataSource类型的数组来存储数字数据集,代码如下:

private dataSource: DataSource<number> = new DataSource<number>([])

由于DataSource 类归属于common 目录,因此在使用时需要使用import 关键字在 Index.ets 文件中引入DataSource.ets 文件,代码如下:

import { DataSource } from '../common/DataSource';

使用 private 关键字声明一个Scroller 类型的状态变量作为控制器来管理 Scroll 组件,该控制器用于与可滚动组件进行绑定,代码如下:

private scroller: Scroller = new Scroller()

与 List 组件和ListItem 子组件使用方式类似,在 build()函数中开发者可以使用Grid 组件和GridItem 子组件来构建网格视图,代码如下:

// 第3章/Index.ets
import { DataSource } from '../common/DataSource';

@Entry
@Component
struct Index {
  private dataSource: DataSource<number> = new DataSource<number>([])
  private scroller: Scroller = new Scroller()

  build() {
    Grid(this.scroller) {
      LazyForEach(this.dataSource, (item: number) => {
        GridItem() {
          Text('😀')
            .fontSize(32)
            .size({width:68,height:68})
            .textAlign(TextAlign.Center)
            .backgroundColor('#F5F5F5')
            .borderRadius(8)
        }
        .margin(10)
      })
    }
    .columnsTemplate('1fr 1fr 1fr 1fr')
  }
}

Grid 组件需要绑定scroller控制器,实现网格布局的滚动控制。使用LazyForEach 懒加载函数,以dataSource 作为数据源来循环渲染数据,并为每个数据项创建一个 GridItem 组件。在 GridItem 子组件的闭包中,使用 Text 组件显示一个表情符号,并使用修饰器来美化表情符号的样式。

此外,为了优化视觉间距,通过 columnsTemplate 修饰器配置 Grid 组件的列布局,columnsTemplate 中的4个1fr表示网格布局的每行包含 4 个等宽的列。

此时由于dataSource 中的数据为空,在预览器中并没有加载任何数据。

3.1.3 加载网格数据

下一步,使用 aboutToAppear 生命周期回调,在组件加载时向 dataSource 批量添加数据,代码如下:

// 第3章/Index.ets
import { DataSource } from '../common/DataSource'

@Entry
@Component
struct Index {
  private dataSource: DataSource<number> = new DataSource<number>([])
  private scroller: Scroller = new Scroller()

  aboutToAppear(): void {
    for (let i = 0; i < 999; i++) {
      this.dataSource.addItem(i)
    }
  }

  build() {...}
}

在 aboutToAppear() 方法中,使用 for 循环向 dataSource 添加 999 条数据,每条数据的值为对应索引。当数据源更新时,LazyForEach绑定的数据会自动触发 UI 更新,使网格组件动态渲染新增的数据。如图 3-1 所示。

图 3-1 网格懒加载效果预览

LazyForEach 可以确保组件在显示时立即加载数据,避免用户在界面初始化后才手动触发数据加载,提高了页面的交互体验。同时由于LazyForEach 实现了懒加载机制,它仅会渲染当前视图可见范围内的数据项,而不会一次性加载所有数据,从而有效减少内存占用和渲染压力,提升应用的流畅度。随着用户滚动,LazyForEach会动态加载新的数据项,确保界面始终保持高效响应。

3.2 实现轮播布局

Swiper 组件则是一种常见的轮播组件,它能够将多个页面或视图按照一定的顺序进行轮播展示,通常用于展示焦点图、广告轮播等。

创建一个名为MySwiper的新的 HarmonyOS 项目,并打开工程开发面板。

3.2.1 导入轮播图素材

将下载好的图片素材拖放media文件夹中,路径为entry/src/main/resources/base/media。如图 3-2 所示。

图 3-2 图片素材准备

3.2.2 实现BannerModel接口

定义一个名为 BannerModel 的接口,用于描述轮播图对象的结构和参数,每个轮播项目都有一个唯一的标识符、图片名称 2 个参数,代码如下:

interface BannerModel {
  id: number
  imageName: string
}

通过@State 装饰器声明一个BannerModel 类型的数组bannerItems 用于存储初始的轮播图内容,并创建 5 个轮播图项目,代码如下:

//第3章/Index.ets
@State bannerItems: BannerModel[] = [
  { id: 1, imageName: 'library001'},
  { id: 2, imageName: 'library002' },
  { id: 3, imageName: 'library003' },
  { id: 4, imageName: 'library004' },
  { id: 5, imageName: 'library005' }
]

声明一个swiperController类型的状态变量作为控制器来管理 Swiper组件,该控制器用于与轮播图组件进行绑定,代码如下:

private swiperController: SwiperController = new SwiperController()

3.2.3 创建轮播图子组件

创建一个子组件BannerItemView,用于渲染单个轮播图片视图,并通过 imageName参数动态设置图片内容,代码如下:

//第3章/Index.ets
@Component
struct BannerItemView {
  @Prop imageName: string

  build() {
    Image($r(`app.media.${this.imageName}`))
      .objectFit(ImageFit.Auto)
      .size({width:'95%',height:200})
      .border({ width:1, color:'#D5D5D5', radius:16})
      .margin(10)
  }
}

BannerItemView 组件使用 @Prop 装饰器定义了 imageName 参数,使其可以从父组件接收图片名称,并动态渲染图片内容。Image 组件用于显示轮播图项,通过 objectFit 修饰器确保图片实现自适应缩放,通过 size 修饰器设置图片的宽高比例,使布局更加整齐。border 修饰器用于添加边框,并通过width 参数设置边框的宽度,通过 color 参数设置边框的颜色,radius 参数设置图片的圆角。同时,通过margin 修饰器设置轮播图的外边距,使界面更加整洁美观。

3.2.4 实现轮播图布局

回到 Index 视图中,将 build()函数中的主体内容替换为 Swiper组件,通过ForEach()函数从bannerItems数组中获取数据,并将获取的数据传给BannerItemView 组件,从而渲染一个轮播图视图,代码如下:

//第3章/Index.ets
interface BannerModel {...}

@Entry
@Component
struct Index {
  @State bannerItems: BannerModel[] = [...]

  private swiperController: SwiperController = new SwiperController()

  build() {
    Swiper(this.swiperController) {
      ForEach(this.bannerItems,(item:BannerModel)=>{
        BannerItemView({imageName:item.imageName})
      })
    }
    .loop(true)
    .autoPlay(true)
    .interval(3000)
    .indicator(
      Indicator.dot()
        .color(Color.Gray)
        .selectedColor(Color.White)
    )
  }
}

@Component
struct BannerItemView {...}

Swiper 组件作为一个容器组件,和Grid 组件不同的是,Swiper 组件不需要类似SwiperItem子组件构建其项目内容,可以直接使用自定义组件作为其子组件。

ArkUI 为Swiper 组件提供了专属修饰器用于控制轮播图的交互,其中loop 修饰器用于设置轮播图的循环播放功能,当开启时,轮播图滚动到最后一张会自动回到第一张,以实现无限轮播效果。autoPlay 和interval 修饰器控制轮播图的自动播放以及自动播放的间隔时长,其中interval 修饰器的参数单位为ms。indicator 修饰器通过为轮播图添加分页指示器(小圆点),用于显示当前轮播图的索引位置,开发者也可以自定义小圆点的样式,使用户能直观看到当前轮播图的位置。

打开预览器,开发者可以看到轮播图显示效果,如图 3-3 所示。


图3-3 轮播图预览效果

3.3 实现瀑布流布局

WaterFlow 组件是一种瀑布流布局组件,它能够将内容以不规则的列进行排列,通常用于展示图片、文章等内容。这种布局方式可以让内容在不同屏幕尺寸下自动适应,呈现出一种自然流畅的视觉效果。

创建一个名为MyWaterFlow的新的 HarmonyOS 项目,并打开工程开发面板。

在 ets 模块目录下创建一个新的目录,命名为common,用于存放项目中使用到的通用模块。在common 目录下新增一个名为 DataSource.ets 的 ArkTS 文件,用于实现通用的数据源类,该类实现IDataSource接口,使其可用于LazyForEach等组件。此部分代码与 3.1 DataSource.ets 文件代码一致,此处不做过多赘述。

3.3.1 实现WallpaperModel接口

回到 Index.ets 文件中,定义一个名为WallpaperModel 的接口,用于描述壁纸对象的结构和参数,每个壁纸项都有一个唯一的标识符、壁纸标题、壁纸路径 3 个参数,代码如下:

//第3章/Index.ets
interface WallpaperModel {
  id: number
  title: string
  imageUrl: string
}

使用 private 关键字声明一个私有的DataSource类型的数组来存储壁纸数据集,代码如下:

private dataSource: DataSource<WallpaperModel> = new DataSource<WallpaperModel>([])

由于DataSource 类归属于common 目录,因此在使用时需要使用import 关键字在 Index.ets 文件中引入DataSource.ets 文件,代码如下:

import { DataSource } from '../common/DataSource';

3.3.2 创建瀑布流子组件

创建一个子组件WallpaperItemView,用于渲染单个壁纸视图,并通过WallpaperModel 类型的 item参数动态设置壁纸的标题和图片来源,代码如下:

//第3章/Index.ets
@Component
struct WallpaperItemView {
  @Prop item: WallpaperModel

  build() {
    Column() {
      Image(this.item.imageUrl)
        .objectFit(ImageFit.Cover)
        .width('100%')
        .height(this.getDynamicHeight())
        .borderRadius({topLeft:8,topRight:8})
      Text(this.item.title)
        .fontSize(14)
        .fontColor('#333333')
        .margin({ top: 5, bottom: 5 })
    }
    .width('100%')
    .backgroundColor('#F5F5F5')
    .borderRadius(8)
  }

  private getDynamicHeight(): number {
    return 200 + (this.item.id % 3) * 50;
  }
}

WallpaperItemView 组件使用 @Prop 装饰器定义了 item 参数,使其能够接收 WallpaperModel 数据并动态渲染壁纸内容。

Image 组件用于展示壁纸,可直接传入网址来显示网络图片。使用 objectFit 修饰器修饰 Image 组件,确保图片按比例填充,同时使用 borderRadius 为顶部圆角添加 8 像素的弧度,使界面更加美观。创建一个 getDynamicHeight() 方法,根据 item.id 来计算图片高度,实现瀑布流效果,使不同壁纸的尺寸显示更加自然。

Text 组件用于显示壁纸标题,并通过 fontSize、fontColor 和margin修饰器修饰。最后在布局方面,Column 组件用于垂直排列图片与标题,并通过 backgroundColor、margin 和 width 修饰器调整布局,使得布局统一且层次分明。

3.3.3 实现瀑布流布局

与 Grid 组件和GridItem 子组件使用方式类似,在 build()函数中开发者可以使用WaterFlow 组件和FlowItem 子组件来构建瀑布流视图,代码如下:

//第3章/Index.ets
@Entry
@Component
struct Index {
  private dataSource: DataSource<WallpaperModel> = new DataSource<WallpaperModel>([])

  build() {
    WaterFlow() {
      LazyForEach(this.dataSource, (item: WallpaperModel) => {
        FlowItem() {
          WallpaperItemView({item:item})
            .margin(5)
        }
        .width('100%')
      })
    }
    .columnsTemplate("1fr 1fr")
  }
}

WaterFlow 组件不需要绑定scroller控制器就可以实现瀑布流布局的滚动控制。使用LazyForEach 懒加载函数,以dataSource 作为数据源来循环渲染数据,并为每个数据项创建一个 FlowItem 组件。在 FlowItem 子组件的闭包中,使用 WallpaperItemView 组件显示单张壁纸视图。

此外,为了多行瀑布流布局,通过 columnsTemplate 修饰器配置 WaterFlow 组件的列布局,columnsTemplate 中的 2 个1fr表示瀑布流布局的每行包含 2 个等宽的列。

3.3.4 添加导航标题

使用Navigation 组件和title 修饰器为该页面添加页面标题,代码如下:

//第3章/Index.ets
@Entry
@Component
struct Index {
  private dataSource: DataSource<WallpaperModel> = new DataSource<WallpaperModel>([])

  build() {
    Navigation(){
      WaterFlow() {...}
      .columnsTemplate("1fr 1fr")
    }
    .title(this.navigationTitle())
  }

  @Builder navigationTitle() {
    Column({ space:5 }) {
      Text('Nature')
        .fontColor('#333333')
        .fontSize(28)
        .fontWeight(FontWeight.Bold)
      Text('999 wallpaper available')
        .fontColor('#999999')
        .fontSize(14)
    }
    .alignItems(HorizontalAlign.Start)
    .margin({ left:10 })
  }
}

Navigation 组件用于 ArkUI 视图之间的导航,title 修饰器用于为 Index 视图设置标题,开发者可以使用自定义组件的方式来创建复杂的导航标题。

3.3.5 加载瀑布流数据

下一步,使用 aboutToAppear 生命周期回调,在组件加载时向 dataSource 批量添加数据,代码如下:

//第3章/Index.ets
aboutToAppear(): void {
  for (let i = 0; i < 999; i++) {
    this.dataSource.addItem({
      id: i,
      title: `壁纸 ${i + 1}`,
      imageUrl: `https://picsum.photos/300/500?random=${i}`
    })
  }
}

在 aboutToAppear() 方法中,使用 for 循环向 dataSource 添加 999 条数据,每条数据的值为符合WallpaperModel

类型的参数值,其中id 作为唯一标识,title 通过字符串模板壁纸 i+1生成,使壁纸按编号展示,而imageUrl使用https://picsum.photos/300/500?random={i + 1} 生成,使壁纸按编号展示,而 imageUrl 使用 https://picsum.photos/300/500?random={i} 生成不同的随机图片,确保每次刷新时加载不同的壁纸内容。当数据源更新时,LazyForEach绑定的数据会自动触发 UI 更新,使瀑布流组件动态渲染新增的数据。

3.3.6 配置网络请求权限

由于使用到网络请求这一系统能力,开发者还需要在项目的基础配置文件中配置系统权限,文件路径为entry/src/main/module.json5,该文件是鸿蒙系统应用开发和部署的基础配置文件之一,主要用于管理应用的配置信息,包含应用的描述、入口能力(Ability)、支持的设备类型、权限请求等。代码如下:

"requestPermissions": [
  {"name": "ohos.permission.INTERNET"}
]

网络请求的权限配置方式如图 3-4 所示。

图3-4 网络请求的权限配置

打开模拟器,将项目安装到模拟器中,在模拟器中预览瀑布流布局效果,如图 3-5 所示。

图 3-5 瀑布流效果预览

3.4 实现树形布局

TreeView 组件是一种树形结构展示组件,它能够以层级方式组织和呈现数据,通常用于文件管理、分类展示等场景。该组件支持节点的展开与折叠,使用户能够高效地浏览和操作多层次的数据结构。

创建一个名为MyTree的新的 HarmonyOS 项目,并打开工程开发面板。

3.4.1 实现树形布局

TreeView 组件在使用前需要导入@kit.ArkUI 模块中的TreeView、TreeController 工具接口,该接口用于实现调用ArkUI 的TreeView组件的能力,实现对TreeView 的自定义和扩展。代码如下:

import { TreeController, TreeView } from '@kit.ArkUI'

声明一个treeController类型的状态变量作为控制器来管理 TreeView组件,该控制器用于与树组件进行绑定,代码如下:

private treeController: TreeController = new TreeController()

在 build()函数中开发者可以使用TreeView 组件来构建树形视图,代码如下:

//第3章/Index.ets
import { TreeController, TreeView } from '@kit.ArkUI'

@Entry
@Component
struct Index {
  private treeController: TreeController = new TreeController()
  
  build() {
    Column() {
      TreeView({ treeController: this.treeController })
    }
  }
}

TreeView 组件需要绑定treeController控制器,实现树形布局的逻辑控制。值得注意的是, TreeView 组件需要放置在一个容器组件中,这是由于TreeView 组件自身不具备独立的布局能力,需要依赖外部容器组件来控制其位置、排列方式和尺寸。

3.4.2 实现NodeModel接口

定义一个名为 NodeModel的接口,用于描述树形视图对象的结构和参数,每个树形结构都有一个唯一的标识符 id、一级菜单名称 name、二级菜单名称 subNodes参数,代码如下:

interface NodeModel {
  id: number
  name: string
  subNodes: string[]
}

通过@State 装饰器声明一个NodeModel 类型的数组bannerItems 用于存储初始的轮播图内容,并创建 5 个轮播图项目,代码如下:

//第3章/Index.ets
private nodes: NodeModel[] = [
  { id: 1, name: "文本与输入", subNodes: ["Text", "TextArea", "TextInput", "RichEditor", "Search", "Span", "ImageSpan", "ContainerSpan", "SymbolSpan", "SymbolGlyph", "Hyperlink", "RichText", "SelectionMenu", "属性字符串", "文本组件公共接口"] },
  { id: 2, name: "图片与视频", subNodes: ["Image", "ImageAnimator", "Video", "图像类型定义"] },
  { id: 3, name: "按钮与选择", subNodes: ["Button", "Toggle", "Checkbox", "CheckboxGroup", "CalendarPicker", "DatePicker", "TextPicker", "TimePicker", "Radio", "Rating", "Select", "Slider", "DownloadFileButton", "ProgressButton", "SegmentButton", "Filter"] },
  { id: 4, name: "导航与切换", subNodes: ["Indicator", "Navigation", "NavDestination", "MultiNavigation", "Stepper", "StepperItem", "Tabs", "TabContent"] },
  { id: 5, name: "滚动与滑动", subNodes: ["List", "ListItem", "ListItemGroup", "Grid", "GridItem", "Scroll", "Swiper", "WaterFlow", "FlowItem", "ScrollBar", "Refresh", "ComposeListItem", "GridObjectSortComponent", "SwipeRefresher", "滚动组件通用接口"] }
]

3.4.3 实现新增子节点方法

创建一个自定义函数 addNodeStructure,用于构建树组件的节点和子节点,代码如下:

addNodeStructure(node: NodeModel): void {
  this.treeController.addNode({
    parentNodeId: -1,
    currentNodeId: node.id,
    isFolder: true,
    primaryTitle: node.name
  })

  node.subNodes.forEach((subNode, index) => {
    this.treeController.addNode({
      parentNodeId: node.id,
      currentNodeId: node.id * 10 + index,
      isFolder: false,
      primaryTitle: subNode
    })
  })
}

addNodeStructure 函数中传入一个 NodeModel 类型的参数 node,该函数的作用是将传入的节点及其子节点添加到树形结构中。

调用 treeController 控制器的addNode()方法,将当前节点添加到树形结构中,设置其父节点的 id 为 -1(表示该节点是根节点),当前节点的 id 为 node.id,并将 isFolder 设置为 true,表示该节点是一个文件夹节点,同时将节点的名称 node.name 设置为 primaryTitle。

使用 forEach 遍历当前节点的子节点数组 node.subNodes,对于每个子节点,再次调用 this.treeController.addNode 方法,设置子节点的父节点 id 为当前节点的 id,子节点的 id 为 node.id * 10 + index,确保其唯一。将 isFolder 设置为 false,同时将子节点的名称 subNode 设置为 primaryTitle。

此时并初始化树组件的节点,因此在预览器中并没有加载任何数据。

3.4.4 加载树节点数据

下一步,使用 aboutToAppear 生命周期回调,在组件加载时动态构建树节点数据,代码如下:

aboutToAppear(): void {
  this.treeController.removeNode()
  this.nodes.forEach(node => this.addNodeStructure(node))
  this.treeController.buildDone()
}

在 aboutToAppear()方法中,调用removeNode()方法移除树形结构中已有的节点。使用 forEach`循环遍历 nodes 数组数组,对数组中的每个节点调用addNodeStructure()方法,将节点及其子节点逐一添加到树形结构中。最后调用 buildDone()方法完成树形结构的构建,确保所有节点正确加载并展示。如图 3-6 所示。

图 3-6 树形结构效果预览

3.5 本章小结

本章主要介绍了 ArkUI 中网格、轮播、瀑布流和树形组件的使用方法。通过实现懒加载、布局设计和数据加载等功能,开发者可以高效地构建出功能丰富的界面。这些组件不仅支持基本的数据展示,还具备动态交互和复杂布局的能力,能够满足多样化的开发需求。

通过本章的学习,开发者可以掌握这些组件的核心用法,包括接口实现、子组件创建和数据管理等。这些技能将帮助开发者在实际项目中快速实现复杂的界面设计,提升开发效率和用户体验。