鸿蒙 渲染控制

391 阅读13分钟

目录

  • 渲染控制概述
  • if/else:条件渲染
  • ForEac:循环渲染
  • LazyForEach:数据懒加载
  • Repeat:循环渲染(推荐)
  • ConnentSlot:混合开发

渲染控制概述

ArkUI通过自定义组件的build()函数和@builder装饰器声明式UI描述语句构建相应的的UI。在声明式描述语句中开发者除了使用系统组件外,还可以使用渲染语句来辅助UI的构建,这些渲染控制语句包括控制组件是否显示的条件语句,基于数组数据快速生成组件的虚幻渲染语句,针对大数据量的数据懒加载语句,针对混合模式开发的组件渲染语句。

if/else:条件渲染

ArkTS提供了渲染控制的能力,条件渲染可根据应用的不同状态,使用if、else和else if渲染对应状态下的UI内容。

使用规则

  • 支持if、else和else if语句
  • if、else后跟随的条件语句可以使用状态变量
  • 允许在容器组件内使用,通过条件渲染语句构建不同的子组件。
  • 条件渲染语句在涉及到组件的父子关系时是"透明"的,当父组件和子组件之间存在一个或多个if语句时,必须遵守父组件关于子组件使用的规则
  • 每个分支内部的构建海曙必须遵循构建函数的规则,并创建一个或多个组件。无法创建组建的空构建函数会产生语法错误
  • 某些容器组件限制子组件的类型或数量,将条件渲染语句用于这些组件内时,这些限制同样应用于条件渲染语句内创建的组件。例如:Grid容器组件的子组件仅支持GridItem组件,在Grid内使用条件渲染语句时,条件渲染语句内仅允许使用GridItem组件。

更新机制

当if、else后跟随的状态判断中使用的状态变量值变化时,条件渲染语句会进行更新,更新步骤如下:

  1. 评估if和else if的状态判断条件,如果分支没有变化,无需执行以下步骤。如果分支有变化,则执行2、3步骤
  2. 删除此前构建的所有子组件
  3. 执行新分支的构造函数,将获取到的组件添加到if父容器中。如果缺少适用的else分支,则不构建然和内容。 条件可以包括Typescript表达式。对于构造函数中的表达式,此类表达式不得更改应用程序状态。

ForEach:循环渲染

ForEach接口基于数组类型数据来进行需=循环渲染,需要与容器组件配合使用,且接口返回的数组应当是允许包含在ForEach父容器组件中的子组件。例如,ListItem组件要求ForEach的父容器组件必须为List组件

接口 描述

ForEach(
  arr: Array,
  itemGenerator: (item: Object, index: number) => void,
  keyGenerator?: (item: Object, index: number) => string
)

以下是参数的详细说明:

参数名参数类型是否必填参数描述
arrArray数据源,为Array类型的数组。说明:
- 可以设置为空数组,此时不会创建子组件。

- 可以设置返回值为数组类型的函数,例如arr.slice(1,3),但设置的函数不应该改变包括数组本身在内的任何状态变量,例如不应使用Array.splice(),Array.sort()或Array.reverse()这些会改变原数组的函数
itemGenerator(item:Object,index:number) => void组件生成函数。
- 为数组中的每个元素创建对应的组件。

- item参数:arr数组中的数据项

- index参数(可选):arr数组中数据项索引

说明:

- 组件的类型必须是ForEach的父容器所允许的。
keyGenerator(item:Object,index:number) => string键值生成函数。
- 为数据源arr的每个数组项生成唯一且持久的键值。函数返回值为开发者自定义的键值生成规则

- item参数:arr数组中的数据项

- index参数(可选):arr数组中的数据项索引。

说明:

- 如果函数缺省,框架默认的键值生成函数为(item:T,index:number)=> {renturen index+'_'+JSON.stringify(item):}

-键值生成函数不应改变任何数组状态

键值生成规则

在ForEach循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于表示对应的组件。当这个键值发生变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。

ForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数。

ArkUI框架对于ForEach的键值生成有一套特定的判断规则,这主要与itemGenerator函数的第二个参数index以及keyGenerator函数的第二个参数index有关,具体的键值生成规则判断逻辑如下图:

LazyForEach:数据懒加载

LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件划出可视区域外时,框架会进行组件销毁回收以降低内存占用。

接口描述

LazyForEach(
    dataSource:IDataSource, //需要进行数据迭代的数据源
    itemGenerator:(item:Object,index:number) => void, 
    keyGenerator:(item:Object,index:number) => string
):void

参数:

参数名参数类型必填参数描述
dataSourceIDataSourceLazyForEach数据源,需要开发者实现相关接口
itemGenerator(item:Objec,index:number) => void子组件生成函数,为数组中的每一个数据项创建一个子组件。
keyGenerator(item:Object,index:number) =>string简直生成函数,用于给数据源中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能更好的识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。

IDataSource类型说明

interface IDataSource {
    totalCount():number; //获得数据总数
    getData(index:number):Object; //获取索引值对应的数据
    registerDataChangeListener(listener:DataChangeListener):void; //注册数据改变的监听器
    unregisterDataChangeListener(listener:DataChangeListener):void; //注销数据改变的监听器
}
接口声明参数类型说明
totoalCount():number-获取数据总数
getData(index:number):Objectnumber获取索引值index对应的数据。
index:获取数据对应的索引值
registerDataChangeListener(listener:DataChangeListener):voidDataChangeListener注册数据改变的监听器
listener:数据变化监听器
unregisterDataChangeListener(listener:DataChangeListener):voidDataChangeListener注销数据改变的监听器
listener:数据变化监听器

DataChangeListener类型说明

interface DataChangeListener {
    onDataReloaded():void; //重新加载数据完成调用
    onDataAdded(index:number):void; //添加数据完成后调用
    onDataMoved(from:number,to:number):void; //数据移动起始位置与数据移动目标位置交换完成后调用
    onDataDeleted(index:number):void; //删除数据完成后调用
    onDataChaged(index:number):void; //改变数据完成后调用
    onDataAdd(index:number):void; //添加数据完成后调用
    onDataMove(from:number,to:number):void; //数据移动起始位置与数据移动目标位置交换完成后调用
    onDataDelete(index:number):void; //删除数据完成后调用
    onDataChange(index:number):void; //改变数据完成后调用
    onDatasetChange(dataOperations:DataOperation[]):void; //批量数据处理后调用
}

DataOperation类型说明

type DataOperation = 
        DataAddOperation      |  //添加数据操作
        DataDeleteOperation   |  //删除数据操作
        DataChangeOperation   |  //改变数据操作
        DataMoveOperation     |  //移动数据操作
        DataExchangeOperation |  //交换数据操作
        DataReloadOperation      //重载所有数据操作

DataAddOperation

interface DataAddOperation {
    type:DataOperationType.ADD,    //数据添加类型
    index:number,                  //插入数据索引值
    count?:number,                 //插入数量,默认值为1
    ket?:string|Array<string>      //为插入的数据分配键值
}

DataDeleteOperation

interface DataDeleteOperation{
    type:DataOperationType.DELETE, //数据删除类型
    index:number,                  //起始删除位置索引值
    count?:number                  //删除数据数量,默认值为1
}

DataChangeOperation

interface DataChangeOperation{
    type:DataOperationType.CHANGE, //数据改变类型
    index:number,                  //改变的数据的索引值
    key?:string                    //为改变的数据分配新的键值,默认使用原键值
}

DataMoveOperation

interface MoveIndex {
    from:number;           //起始移动位置
    to:number;             //目的移动位置
}

interface DataMoveOperation {
    type:DataOperationType.MOVE, //数据移动类型
    index:MoveIndex,
    key?:string                  //为被移动的数据分配新的键值,默认使用原键值
}

DataExchangeOperation

interface ExchangeIndex {
    start:number;                     //第一个交换位置
    end:number;                       //第二个交换位置
}
interface ExchangeKey {
    start:string;                     //为第一个交换的位置分配新的键值,默认使用原键值
    end:string;                       //为第二个交换的位置分配新的键值,默认使用原键值
}
interface DataExchangeOperation {
    type:DataOperationType.EXCHANGE,  //数据交换类型
    index:ExchangeIndex,
    key?:ExchangeKey
}

DataReloadOperation

interface DataReloadOperation {      //当onDataChange含有DataOperationType.RELOAD操作时,其余操作全部失效,框架会自己调用keyGenerator进行键值比对
    type:DataOperationType.RELOAD    //数据全部重新加载
}

使用限制

  • LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有数据。
  • LazyForEach在每次迭代中,必须创建且只允许创建一个子组件
  • 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件
  • 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句
  • 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出问题
  • LazyForEach必须使用DataChangeListener对象来进行更新,第一个参数dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新
  • 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新

键值生成规则

在LazyForEach循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。

LazyForEach提供了一个名为keyGenerator的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义keyGenerator函数,则ArkUI框架会使用默认的键值生成函数,即(item: Object, index: number) => { return viewId + '-' + index.toString(); }, viewId在编译器转换过程中生成,同一个LazyForEach组件内其viewId是一致的。

Repeat:循环渲染(推荐)

Repeat组件基于数组类型数据进行循环渲染,需要与容器组件配合使用,且接口返回的数组应当是允许包含在Repeat父容器组件中的子组件。

Repeat循环渲染和ForEach相比有两个区别,一是优化了部分更新场景下的渲染性能,二是组件生成函数的索引index由框架侧来维护。

接口描述

Repeat 组件构造

declare const Repeat:<T>(arr:Array<T>) => RepeatAttribute<T>

参数说明:

参数名参数类型是否必填参数描述
arrArray<T>数据源,为Array<T>类型的数组,由开发者决定参数类型。

Repeat组件属性

declare class RepeatAttribute<T> {
    each(itemGenerator:(repeatItem:RepeatItem<T>) => void) : RepeatAttribute<T>;
    key(keyGenerator:(item:T,index:number) => string) : RepeatAttribute<T>;
}

参数说明:

属性名参数类型是否必填参数描述
eachitemGenerator:(repeatItem:RepeatItem) => void组件生成函数。
说明:

- each属性必须有,否则运行时会报错。

- itemGenerator的参数为RepeatItem,该参数将item和index结合到了一起
keykeyGenerator:(item:T,index:number) => string键值生成函数。
-为数组中的每个元素创建对应的键值。

-iitem:arr数组中的元素项。


-index:arr数组中的数据项索引

RepeatItem 类型

interface RepeatItem<T> {
    item:T,
    index?:number
}

参数说明:

参数名参数类型是否必填参数描述
itemTarr中每一个数据项。T为开发者传入的数据类型
numbernumber当前数据项对应的索引

键值生成规则

在Repeat循环渲染过程中,系统会为每个数组元素生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,会基于此去做对应的更新。若当前数组有数据项对应的键值与其他项重复,Repeat会为每一项数据补上index作为新的键值。

Repeat提供了key属性,参数是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有调用key属性,则ArkUI框架会使用默认的键值生成函数。

ArkUI框架对于Repeat的键值生成有一套特定的判断规则,具体的键值生成规则判断逻辑如下图所示。

ContentSlot: 混合开发

用于渲染并管理Native层使用C-API创建的组件。
支持混合模式开发,当容器是ArkTS组件,子组件在Native侧创建时,推荐使用ContentSlot占位组件。

接口

ContentSlot组件接口

//当前开发者需要使用ArkUI提供的NodeContent作为管理器
ContentSlot(content:Content)
参数名类型必须参数描述
contentContentContent作为ContentSlot的管理器,通过Native侧提供的接口,可以注册并触发ContentSlot的上下树事件回调以及管理ContentSlot的子组件
abstract class Content {
}

ContentSlotInterface

(content:Content):ContentSlotAttribute;
当内容添加到占位符组件时调用

系统能力: SystemCapability.ArkUI.ArkUI.Full
参数:

参数名类型必须参数描述
contentContentContent作为ContentSlot的管理器,通过Native侧提供的接口,可以注册并触发ContentSlot的上下树事件回调以及管理ContentSlot的子组件

ContentSlotAttribute

定义ContentSlot属性,以防止不正确的递归使用ContentSlot
系统能力: SystemCapability.ArkUI.ArkUI.Full

Native侧接口

接口名描述
OH_ArkUI_NodeContent_RegisterCallback(ArkUI_NodeContentHandle content, ArkUI_NodeContentCallback callback)向管理器Content上注册事件。
OH_ArkUI_NodeContentEvent_GetEventType(ArkUI_NodeContentEvent* event)获取Content上触发的事件类型。
OH_ArkUI_NodeContent_AddNode(ArkUI_NodeContentHandle content, ArkUI_NodeHandle node)在Content上添加子组件。
OH_ArkUI_NodeContent_InsertNode(ArkUI_NodeContentHandle content, ArkUI_NodeHandle node, int32_t position)在Content上插入子组件。
OH_ArkUI_NodeContent_RemoveNode(ArkUI_NodeContentHandle content, ArkUI_NodeHandle node)在Content上移除子组件。
OH_ArkUI_GetNodeContentFromNapiValue(napi_env env, napi_value value, ArkUI_NodeContentHandle* content)在Native侧获取ArkTS侧Content指针。
typedef enum {NOTE_CONTENT_EVENT_ON_ATTACH_TO_WINDOW = 0,NOTE_CONTENT_EVENT_ON_DETACH_FROM_WINDOW = 1,} ArkUI_NodeContentEventTypeContent上会触发的上树和下树两种事件类型。