三、渲染控制语法
ArkTS也提供了渲染控制的能力。条件渲染可根据应用的不同状态,渲染对应状态下的UI内容。循环渲染可从数据源中迭代获取数据,并在每次迭代过程中创建相应的组件。参考网址:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ets-rendering-control-0000001149698611
3.1.if/else条件渲染
使用 if/else
进行条件渲染需要注意以下情况:
- if 条件语句可以使用状态变量。
- 使用 if 可以使子组件的渲染依赖条件语句。
- 必须在容器组件内使用。
- 某些容器组件限制子组件的类型或数量。将if放置在这些组件内时,这些限制将应用于 if 和 else 语句内创建的组件。例如,
Grid
组件的子组件仅支持GridItem
组件,在Grid
组件内使用条件渲染时,则 if 条件语句内仅允许使用GridItem
组件。
案例如下:
@Entry
@Component
struct ComponentTest {
@State showImage: boolean = false; // 定义showImage状态,默认为false
build() {
// 纵向布局元素垂直方向间距。
Column({space:10}){ // 创建一个垂直布局的Column组件,设置子元素间距为10
// 如果showImage为true,则显示图片,否则显示"Loading ....."
if(this.showImage){
Image($r('app.media.Sns')) // 显示图片,使用资源管理器获取图片资源
.width(160) // 设置宽度为160
.height(60) // 设置高度为60
.backgroundColor(Color.Pink) // 设置背景颜色为粉色
}else {
Text("Loading .....")
.fontSize(23) // 设置字体大小为23
.fontColor('#FFFFF0') //设置字体颜色为象牙色
.width(160) // 设置宽度为160
.height(60) // 设置高度为60
.backgroundColor(Color.Pink) // 设置背景颜色为粉色
.textAlign(TextAlign.Center) // 设置文本居中对齐
}
Button(this.showImage?'Image Loaded':'Load Image') // 按钮内容根据showImage状态动态显示
.size({width:160,height:40}) // 设置按钮的宽和高
.backgroundColor(this.showImage?'#3F56EA':'#9C554B') // 根据showImage状态设置按钮背景色
.onClick(()=>{ // 点击事件处理
this.showImage = true; // 点击后设置showImage值为true,显示图片
})
}.width('100%') // 设置宽度为100%
.height('100%') // 设置高度为100%
.padding(10) // 设置内边距为10
}
}
预览效果如下:
3.2.ForEach循环渲染
ArkUI开发框架提供循环渲染(ForEach组件)来迭代数组,并为每个数组项创建相应的组件。ForEach
定义如下:
interface ForEach {(
arr: Array<any>,
itemGenerator: (item: any, index?: number) => void,
keyGenerator?: (item: any, index?: number) => string
): ForEach;
}
说明:
- arr:必须是数组,允许空数组,空数组场景下不会创建子组件。
- itemGenerator:子组件生成函数,为给定数组项生成一个或多个子组件。
- keyGenerator:匿名参数,用于给定数组项生成唯一且稳定的键值。
案例如下:
@Entry
@Component
struct ComponentTest02 {
//准备源数据
private textArray:string[] = ["一","二","三","四","五"] // 定义一个包含字符串的数组作为源数据
build() {
Column({space:10}){ // 创建一个垂直布局的Column组件,设置子元素间距为10
ForEach(this.textArray,(item:string,index?:number)=>{ // 遍历textArray数组
Text(`标题:${item}`) // 显示带有标题前缀的文本
.fontSize(20) // 设置字体大小为20
.backgroundColor('#00B377') // 设置背景颜色为绿色
.margin({top:10}) // 设置上边距为10
.fontColor('#FFFFF0') // 设置字体颜色为白色
})
}.width("100%") // 设置宽度为100%
.height("100%") // 设置高度为100%
.padding(10) // 设置内边距为10
}
}
预览后效果如下:
3.3.数据懒加载LazyForEach循环渲染
ArkUI开发框架通过数据懒加载(LazyForEach)从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。
1)LazyForEach
定义如下:
// LazyForEach定义
interface LazyForEach {(dataSource: IDataSource, itemGenerator: (item: any, index?: number) => void,keyGenerator?: (item: any, index?: number) => string): LazyForEach;
}
// IDataSource定义
export declare interface IDataSource {
totalCount(): number;
getData(index: number): any;
registerDataChangeListener(listener: DataChangeListener): void;
unregisterDataChangeListener(listener: DataChangeListener): void;
}
// DataChangeListener定义
export declare interface DataChangeListener {
onDataReloaded(): void;
onDataAdded(index: number): void;
onDataMoved(from: number, to: number): void;
onDataDeleted(index:number): void;
onDataChanged(index:number): void;
}
说明如下:
- itemGenerator:子组件生成函数,为给定数组项生成一个或多个子组件。
- keyGenerator:匿名参数,用于给定数组项生成唯一且稳定的键值。
- dataSource:实现
IDataSource
接口的对象,需要开发者实现相关接口。
2)IDataSource
定义如下:
export declare interface IDataSource {
totalCount(): number;
getData(index: number): any;
registerDataChangeListener(listener: DataChangeListener): void;
unregisterDataChangeListener(listener: DataChangeListener): void;
}
说明如下:
- totalCount:获取数据总数。
- getData:获取索引对应的数据。
- registerDataChangeListener:注册改变数据的监听器。
- unregisterDataChangeListener:注销改变数据的监听器。
3)DataChangeListener
定义如下:
export declare interface DataChangeListener {
onDataReloaded(): void;
onDataAdded(index: number): void;
onDataMoved(from: number, to: number): void;
onDataDeleted(index:number): void;
onDataChanged(index:number): void;
}
说明如下:
- onDataReloaded:item重新加载数据时的回调。
- onDataAdded:item新添加数据时的回调。
- onDataMoved:item数据移动时的回调。
- onDataDeleted:item数据删除时的回调。
- onDataChanged:item数据变化时的回调。
案例如下:
//##########################构造数据################################
//定义student
class Student{
public sid: number; // 学生ID
public name: string; // 学生姓名
public age: number; // 学生年龄
public address: string; // 学生地址
public avatar: string; // 学生头像
//构造方法
constructor(sid: number, name: string, age: number, address: string, avatar: string) {
this.sid = sid; // 初始化学生ID
this.name = name; // 初始化学生姓名
this.age = age; // 初始化学生年龄
this.address = address; // 初始化学生地址
this.avatar = avatar; // 初始化学生头像
}
}
// 定义一个抽象类 BaseDataSource,使用泛型 T,实现接口 IDataSource
abstract class BaseDataSource<T> implements IDataSource {
private dataSource: T[] = new Array(); // 数据源,使用泛型数组
// 构造方法,接收一个泛型数组作为参数,用于初始化数据源
constructor(dataList: T[]) {
this.dataSource = dataList; // 初始化数据源
}
// 返回数据源的长度
totalCount(): number {
return this.dataSource == null ? 0 : this.dataSource.length;
}
// 获取指定索引的数据,如果索引合法则返回数据,否则返回 null
getData(index: number): T | null {
return index >= 0 && index < this.totalCount() ? this.dataSource[index] : null;
}
// 注册数据变化监听器,这里的方法体为空,需要在子类中实现具体逻辑
registerDataChangeListener(listener: DataChangeListener) {}
// 取消注册数据监听器,这里的方法体为空,需要在子类中实现具体逻辑
unregisterDataChangeListener() {}
}
// 继承 BaseDataSource 类,指定泛型为 Student 类型
class StudentDataSource extends BaseDataSource<Student> {
constructor(students: Student[]) {
super(students); // 调用父类构造函数进行初始化
}
}
//产生数据
function mock():Student[]{
let students = [];
for (let i = 1; i < 21; i++) {
//模拟学生数据
students[i] = new Student(i,"student:"+i, i+10, "address:"+i, "app.media.Sns");
}
return students;
}
@Entry
@Component
struct ComponentTest03 {
// mock数据
private student: Student[] = mock(); // 模拟学生数据
// 创建dataSource
private dataSource: StudentDataSource = new StudentDataSource(this.student); // 创建学生数据源
build() {
Column({ space: 10 }) { // 创建一个垂直布局的列,设置间距为10
List() { // 创建一个列表
LazyForEach(this.dataSource, (item: Student) => { // 使用自定义dataSource进行懒加载
ListItem() { // 创建列表项
Row() { // 创建一个水平布局的行
Image($r("app.media.Sns")) // 显示学生头像
.height('100%') // 设置高度为100%
.width(80) // 设置宽度为80
Column() { // 创建一个垂直布局的列
Text(this.getName(item)) // 调用getName方法验证懒加载
.fontSize(20) // 设置字体大小为20
Text('address: ' + item.address) // 显示学生地址
.fontSize(17) // 设置字体大小为17
}
.margin({ left: 5 }) // 设置左边距为5
.alignItems(HorizontalAlign.Start) // 设置子元素水平方向对齐方式为起始位置
.layoutWeight(1) // 设置布局权重为1
}
.width('100%') // 设置宽度为100%
.height('100%') // 设置高度为100%
}
.width('100%') // 设置宽度为100%
.height(60) // 设置高度为60
})
}
.divider({ // 设置列表的分隔线样式
strokeWidth: 3, // 分隔线宽度为3
color: Color.Gray // 分隔线颜色为灰色
})
.width('90%') // 设置宽度为90%
.height(160) // 设置高度为160
.backgroundColor(Color.Pink) // 设置背景颜色为粉色
}
.width('100%') // 设置宽度为100%
.height('100%') // 设置高度为100%
.padding(10) // 设置内边距为10
}
getName(item: Student): string {
console.log("index: " + item.sid); // 打印学生索引日志
return 'index:' + item.sid + ", " + item.name; // 返回学生索引和姓名
}
}
执行后效果如下:
输出的日志如下:
说明:
- LazyForEach必须在容器组件内使用,目前仅有List、Grid以及Swiper组件支持数据懒加载(即只加载可视部分以及其前后少量数据用于缓冲,避免过多的占用资源),其他组件仍然是一次性加载所有的数据。
- LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
- 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
- 允许LazyForEach包含在if/else条件渲染语句中,但不允许LazyForEach中出现if/else条件渲染语句。
- 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,仅当itemGenerator中创建的子组件内使用了状态变量时,才会触发组件刷新。
- itemGenerator函数的调用顺序不一定和数据源中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常工作:
LazyForEach(dataSource,
item => Text(`${item.i}. item.data.label`)),
item => item.data.id.toString())