鸿蒙 ArkTS 自适应企业表单:用 `onAreaChange` 实现类似 Flutter `LayoutBuilder` 的响应式布局


鸿蒙 ArkTS 自适应企业表单:用 onAreaChange 实现类似 Flutter LayoutBuilder 的响应式布局
一、项目背景与概述
在移动端与桌面端界限日益模糊的今天,企业级应用面临一个核心挑战:同一套代码如何在手机、平板、折叠屏乃至 PC 上都能提供优质的用户体验? 传统做法是为不同屏幕尺寸分别维护一套布局文件,但这样带来的维护成本是指数级增长的。
鸿蒙(HarmonyOS)作为面向全场景的分布式操作系统,天然就需要应对这种多设备、多形态的挑战。其原生开发语言 ArkTS(Ark TypeScript)提供了丰富的布局能力,但很多从 Flutter、React Native 或原生 Android/iOS 转过来的开发者,会习惯性地寻找类似 LayoutBuilder、MediaQuery 或 breakpoints 这样的响应式工具。
本文将以一个 企业员工信息登记表单 为实战案例,详细讲解如何在鸿蒙 ArkTS 中利用 onAreaChange 回调实现类似 Flutter LayoutBuilder 的容器级响应式布局——当容器宽度大于 600vp 时自动切换为双列 Row 布局(大屏模式),小于 600vp 时切换为单列 Column 布局(小屏模式)。
这篇文章不仅会展示完整代码,更会深入剖析每个技术决策背后的思考过程,帮助读者建立起在鸿蒙生态中构建自适应界面的系统方法论。
二、鸿蒙 ArkTS 布局体系概述
2.1 什么是 ArkTS?
ArkTS 是鸿蒙原生应用开发的主力语言,基于 TypeScript 语法进行了扩展和约束。相比于标准 TypeScript,ArkTS 具有以下特点:
- 强制类型安全:所有变量必须有明确的类型声明,不允许隐式
any - 装饰器驱动的响应式系统:通过
@State、@Prop、@Link、@Provide、@Consume等装饰器管理组件的状态和通信 - 声明式 UI 构建:与 Flutter、SwiftUI、Jetpack Compose 类似的声明式写法,UI 是状态的函数
- 组件化架构:通过
@Component定义可复用的 UI 组件,通过@Builder定义可复用的构建块 - 严格的初始化规则:所有非
@State属性必须提供编译时常量的默认值,@BuilderParam必须关联一个默认的@Builder方法
2.2 布局容器
ArkTS 提供了三种核心布局容器:
| 容器 | 功能 | 对应 CSS/Flexbox |
|---|---|---|
Column |
垂直方向排列子组件 | flex-direction: column |
Row |
水平方向排列子组件 | flex-direction: row |
Stack |
层叠排列子组件(支持绝对定位) | position: relative + 层叠 |
这三大容器配合 FlexAlign(主轴对齐)、HorizontalAlign(交叉轴对齐)、layoutWeight(权重分配)等属性,足以应对绝大多数布局场景。
2.3 响应式布局的途径
在 ArkTS 中,实现响应式布局主要有以下几种方式:
onAreaChange回调:监听容器的尺寸变化,手动控制布局分支切换(本文的核心方法)- 断点系统(breakpoints):通过
GridRow/GridCol组件配合breakpoints实现栅格布局 MediaQuery:获取设备级别的媒体信息(如屏幕宽高、深色模式等)- 百分比 /
vp单位:使用相对单位实现弹性缩放
其中,onAreaChange 是粒度最细、控制最灵活的方式——因为它监听的是 容器 的尺寸,而非设备屏幕的尺寸。这意味着即使在一个可拖拽分屏的应用中,当用户调整面板大小时,布局也能实时响应。
三、核心代码实现深度解析
3.1 顶层布局结构
整个页面的骨架是一个 Stack 容器,内部包含两层:
Stack
├── Column (主内容层)
│ ├── TitleBar (标题栏)
│ └── Column (表单区域 + onAreaChange 监听)
│ ├── MultiColumnForm / SingleColumnForm
│ └── SubmitButton
└── SuccessDialog (弹窗覆盖层,根据条件显示)
选择 Stack 作为顶层容器的原因是:弹窗组件需要覆盖在表单之上,而 Stack 天然支持层叠布局。
3.2 onAreaChange:ArkTS 版的 LayoutBuilder
这是整个项目的核心亮点。在 Flutter 中,我们这样写:
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return _buildWideLayout();
} else {
return _buildNarrowLayout();
}
},
)
而在 ArkTS 中,我们通过 onAreaChange 回调配合 @State 实现同样的效果:
@State containerWidth: number = 0;
// 在 build() 中:
Column() {
if (this.isMultiColumn) {
this.MultiColumnForm()
} else {
this.SingleColumnForm()
}
}
.width('100%')
.onAreaChange((oldValue: Area, newValue: Area): void => {
this.containerWidth = newValue.width as number;
})
对比分析:
| 特性 | Flutter LayoutBuilder |
ArkTS onAreaChange |
|---|---|---|
| 触发时机 | 每次布局重建时 | 容器尺寸实际变化时 |
| 返回信息 | BoxConstraints(宽高约束) |
Area(完整的位置和尺寸信息) |
| 声明方式 | 回调函数返回子树 | 回调更新状态,状态驱动 UI |
| 性能特点 | 随布局树重建触发 | 仅尺寸变化时触发,更精确 |
| 使用场景 | 任何需要根据容器尺寸切换布局的地方 | 同上 |
值得注意的是,onAreaChange 是非惰性的——它只在容器的尺寸 实际发生变化 时才触发,而非在每次渲染帧都触发。这意味着它的性能开销非常低,适合高频交互场景。
3.3 计算属性驱动布局分支
通过一个只读计算属性 isMultiColumn 来驱动布局选择:
get isMultiColumn(): boolean {
return this.containerWidth > 600;
}
在模板中直接使用 if (this.isMultiColumn) 来切换两个 @Builder 方法。这里的阈值 600vp(virtual pixel,虚拟像素)是经过实践检验的一个合理的分界点——手机竖屏通常在 360~420vp,平板竖屏在 600~800vp,桌面窗口通常在 1000vp 以上。
这种写法比直接在模板中写 if (this.containerWidth > 600) 更优雅,因为:
- 逻辑集中:要修改阈值只需改一处
- 语义清晰:
isMultiColumn的名称直接表达了意图 - 可测试性:可以单独测试这个计算逻辑
3.4 双列表单(大屏模式)
当 isMultiColumn 为 true 时,渲染 MultiColumnForm() 方法。核心思路是用 Row 将两个表单项并排排列:
@Builder
MultiColumnForm(): void {
Column() {
// 第一行:姓名 + 性别
Row() {
FormFieldItem({ label: '姓名', required: true }) {
TextInput({ placeholder: '请输入姓名', text: this.name })
.onChange((val: string): void => { this.name = val; })
.height(40)
.backgroundColor('#F0F2F5')
.borderRadius(8)
.padding({ left: 12, right: 12 })
}
Blank().width(16) // 列间距
FormFieldItem({ label: '性别' }) {
Row() {
Radio({ value: 'male', group: 'gender' })
.checked(this.gender === 'male')
.onChange((): void => { this.gender = 'male'; })
Text('男').fontSize(14).margin({ left: 4 })
Blank().width(24)
Radio({ value: 'female', group: 'gender' })
.checked(this.gender === 'female')
.onChange((): void => { this.gender = 'female'; })
Text('女').fontSize(14).margin({ left: 4 })
}
}
}
.width('100%')
// ... 后续行
}
}
每一行都是一个 Row,内部包含两个 FormFieldItem,中间用 Blank().width(16) 作为间距。FormFieldItem 组件内部已经设置了 width('100%'),因此在 Row 中它会自动平分可用空间。
这种布局的优势在于:
- 信息密度高:同样一屏可以展示更多内容
- 视觉对齐:标签和输入框在一行内对齐,便于快速扫描
- 节省滚动:减少用户的滚动操作
3.5 单列表单(小屏模式)
当 isMultiColumn 为 false 时,渲染 SingleColumnForm() 方法。每个表单项独占一行:
@Builder
SingleColumnForm(): void {
Column() {
FormFieldItem({ label: '姓名', required: true }) {
TextInput({ placeholder: '请输入姓名', text: this.name })
.onChange((val: string): void => { this.name = val; })
.height(40)
.backgroundColor('#F0F2F5')
.borderRadius(8)
.padding({ left: 12, right: 12 })
}
Blank().height(16)
FormFieldItem({ label: '性别' }) {
// ...
}
// ... 后续表单项
}
}
单列布局虽然信息密度低,但有以下不可替代的优点:
- 聚焦性强:用户一次只能看到一个字段,填写更专注
- 滑动顺畅:适合单手操作,手指覆盖区域合理
- 触控友好:每个输入框都有足够大的点击区域
3.6 大屏与小屏的布局差异总结
| 布局特性 | 大屏模式(>600vp) | 小屏模式(≤600vp) |
|---|---|---|
| 核心容器 | Row + Row 嵌套 |
Column 纵向排列 |
| 每行字段数 | 2 个 | 1 个 |
| 字段间距 | 行内 Blank().width(16),行间 Blank().height(20) |
Blank().height(16) |
| 左右内边距 | 32vp | 16vp |
| 信息密度 | 高 | 中 |
| 适用场景 | 平板、桌面、横屏手机 | 竖屏手机、小尺寸窗口 |
四、组件化设计:FormFieldItem
4.1 组件职责
FormFieldItem 是一个通用表单项包装组件,职责清晰:
- 显示标签(含可选的必填标记
*) - 渲染子内容(输入框、选择器、开关等)
- 统一间距和样式
4.2 @BuilderParam 的妙用
ArkTS 中的 @BuilderParam 是组件化构建的强大工具。它类似于 Vue 的 slot(插槽)或 Flutter 的 child 参数:
@Component
struct FormFieldItem {
label: string = '';
required: boolean = false;
@BuilderParam content: () => void = this.defaultContent;
@Builder
defaultContent(): void {
Text(' ').fontSize(14)
}
build() {
Column() {
// 标签行
Row() {
Text(this.label).fontSize(14).fontColor('#333').fontWeight(FontWeight.Medium)
if (this.required) {
Text(' *').fontSize(14).fontColor('#E53E3E')
}
Blank().layoutWeight(1)
}
.width('100%')
Blank().height(8)
// 子内容插槽
this.content()
}
.width('100%')
.alignItems(HorizontalAlign.Start)
}
}
使用时通过尾随闭包语法传入子内容:
FormFieldItem({ label: '姓名', required: true }) {
TextInput({ placeholder: '请输入姓名', text: this.name })
.onChange((val: string) => { this.name = val; })
.height(40)
.backgroundColor('#F0F2F5')
.borderRadius(8)
.padding({ left: 12, right: 12 })
}
这里的 { ... } 就是 @BuilderParam content 的实际参数。这种语法设计使得组件的使用非常自然——标签属性通过 {} 传入,子内容通过尾随闭包传入。
4.3 @BuilderParam 的初始化规则
在之前的调试过程中,我们遇到了一个 ArkTS 的编译规则:
如果组件属性支持本地初始化,则必须设置一个合法的、不依赖运行时的默认值。
对于 @BuilderParam content: () => void,它需要有一个默认的 @Builder 方法:
@BuilderParam content: () => void = this.defaultContent;
@Builder
defaultContent(): void {
Text(' ').fontSize(14)
}
这里的 = this.defaultContent 引用了一个 @Builder 方法。这是 ArkTS 中少数允许引用 this 的默认值场景——因为 @Builder 方法是编译期处理的函数引用,不属于运行时状态。
如果我们不提供默认值,IDE 会报错:“@BuilderParam content 没有设置默认值”。如果提供的默认值不是 @Builder 方法引用(而是一个 lambda () => {}),也会报错。
五、弹窗实现:从 @CustomDialog 到纯组件
5.1 初版方案:@CustomDialog + CustomDialogController
最初的弹窗方案使用了 @CustomDialog 装饰器:
@CustomDialog
struct SuccessDialog {
controller: CustomDialogController;
// ...
}
但这种方式带来了几个问题:
controller属性没有默认值:ArkTS 要求所有非@State属性必须有默认值,但CustomDialogController的构造函数需要builder参数,形成了循环依赖CustomDialogController不能是@State:编译器明确禁止将CustomDialogController标记为@State- API 弃用风险:
AlertDialog.show()已经从 API 26 开始标记为弃用
5.2 最终方案:纯组件 + Stack 覆盖层
最终采用了更受控的纯组件方案:
@Component
struct SuccessDialog {
name: string = '';
email: string = '';
phone: string = '';
department: string = '';
isFullTime: boolean = true;
@Link showDialog: boolean;
build() {
Column() {
// 半透明遮罩
Column() {
// 弹窗卡片
Column() {
Text('提交成功').fontSize(20).fontWeight(FontWeight.Bold)
// ... 信息展示行
Button('确定').onClick(() => { this.showDialog = false })
}
.width('85%')
.padding(24)
.backgroundColor(Color.White)
.borderRadius(16)
.shadow({ radius: 20, color: 'rgba(0,0,0,0.15)', offsetY: 8 })
}
.width('100%').height('100%')
.backgroundColor('rgba(0,0,0,0.4)')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
.width('100%').height('100%')
}
}
在父组件中,通过 Stack 和条件渲染控制弹窗的显示:
Stack() {
// 主表单
Column() { /* ... */ }
// 弹窗覆盖层
if (this.showSuccessDialog) {
SuccessDialog({
name: this.name,
email: this.email,
phone: this.phone,
department: this.department,
isFullTime: this.isFullTime,
showDialog: this.showSuccessDialog
})
}
}
点击"确定"时通过 @Link 反向修改父组件的 showSuccessDialog 状态来关闭弹窗。
5.3 方案对比
| 特性 | @CustomDialog 方案 |
纯组件方案 |
|---|---|---|
| 代码复杂度 | 低(内置动画) | 中(手动管理) |
| 可控性 | 中(受框架约束) | 高(完全控制) |
| 编译通过率 | 低(易触发 ArkTS 规则) | 高(遵循标准组件规则) |
| 动画效果 | 内置淡入淡出 | 需要手动实现 |
| 样式自由度 | 较低 | 完全自由 |
六、状态管理与数据绑定
6.1 表单状态的定义
所有表单字段都使用 @State 装饰器定义,确保 UI 随数据变化自动更新:
@State name: string = '';
@State email: string = '';
@State phone: string = '';
@State department: string = '';
@State jobPosition: string = '';
@State joinDate: string = '';
@State remarks: string = '';
@State gender: string = 'male';
@State isFullTime: boolean = true;
@State showSuccessDialog: boolean = false;
6.2 双向数据绑定
在 ArkTS 中,双向绑定需要手动实现。以 TextInput 为例:
TextInput({ placeholder: '请输入姓名', text: this.name })
.onChange((val: string): void => {
this.name = val;
})
text 参数显示当前值,onChange 回调更新状态。这种方式虽然比 Flutter 的 TextEditingController 略显繁琐,但胜在直接——没有中间层,状态流向一目了然。
6.3 特殊控件的状态同步
Radio 单选框:通过 checked 属性和 group 属性管理选中状态:
Radio({ value: 'male', group: 'gender' })
.checked(this.gender === 'male')
.onChange((): void => { this.gender = 'male'; })
Radio({ value: 'female', group: 'gender' })
.checked(this.gender === 'female')
.onChange((): void => { this.gender = 'female'; })
这里有一个坑:大屏和小屏模式不能共用同一个 group 名称,否则会出现选择不同步的问题。因此大屏用 group: 'gender',小屏用 group: 'gender2'。
Toggle 开关:
Toggle({ type: ToggleType.Switch, isOn: this.isFullTime })
.onChange((val: boolean): void => { this.isFullTime = val; })
七、视觉设计与样式系统
7.1 色彩系统
本项目采用了一套简洁的视觉设计,整体风格偏向专业、清爽:
| 用途 | 色值 | 应用场景 |
|---|---|---|
| 背景色 | #F5F7FA |
页面背景 |
| 卡片背景 | #FFFFFF |
标题栏、弹窗卡片 |
| 输入框背景 | #F0F2F5 |
TextInput、TextArea |
| 主色 | #4A6CF7 |
提交按钮、主题元素 |
| 标题色 | #1A1A2E |
页面标题 |
| 标签色 | #333333 |
表单项标签 |
| 辅助色 | #8E8EA0 |
副标题、说明文字 |
| 错误色 | #E53E3E |
必填标记 * |
7.2 按钮样式
提交按钮采用了带有投影的现代风格:
Button() {
Text('提交登记')
.fontSize(16)
.fontColor(Color.White)
.fontWeight(FontWeight.Medium)
}
.width('100%')
.height(48)
.backgroundColor('#4A6CF7')
.borderRadius(10)
.shadow({
radius: 8,
color: 'rgba(74,108,247,0.3)',
offsetY: 4
})
shadow 属性为按钮添加了柔和的下投影,增强了立体感和点击欲望。
7.3 间距系统
间距在 UI 设计中至关重要。本项目采用了 8 的倍数作为间距单位:
- 标签与输入框间距:8vp
- 小屏表单项间距:16vp
- 大屏表单项间距:20vp
- 表单与边缘间距:24vp
- 页面顶部间距:32vp
八、ArkTS 开发的血泪教训与最佳实践
在开发这个项目的过程中,我们遇到了多个 ArkTS 特有的编译问题和设计约束。下面逐一记录,希望能帮助后来的开发者少走弯路。
8.1 属性名不能与系统方法冲突
问题:@State position: string = ''; 导致编译错误。
原因:position 是 CommonAttribute 接口中定义的方法(用于设置组件位置),在子类 Index 中声明同名字段会与方法重名。
解决方案:重命名为 jobPosition。
教训:在定义 @State 变量时,避开所有已知的组件属性方法名,如 width、height、position、margin、padding、align、shadow 等。
8.2 非 @State 属性必须有编译时常量默认值
问题:controller: CustomDialogController; 没有默认值,触发编译错误。
原因:在 ArkTS 的 @Component 中,所有非装饰器属性(没有 @State、@Prop、@Link 等)必须有一个默认值,且默认值必须是编译时常量(不能是 new 表达式或函数调用)。
解决方案:避免在组件属性中存储需要运行时初始化的对象,改用其他方式管理。
8.3 @BuilderParam 的默认值必须是 @Builder 方法引用
问题:@BuilderParam content: () => void; 没有默认值 → 编译错误。
解决方案:提供一个默认的 @Builder 方法:
@BuilderParam content: () => void = this.defaultContent;
@Builder
defaultContent(): void {
Text(' ').fontSize(14)
}
8.4 private 属性不能通过构造函数初始化
问题:
private label: string = '';
FormFieldItem({ label: '姓名' }) // Warning: private 属性不能通过构造函数初始化
解决方案:去掉 private,ArkTS 中不写访问修饰符等同于 public(但不能显式写 public):
label: string = '';
8.5 Radio 的 value 参数必须是字符串
问题:
Radio({ value: 0, group: 'gender' }) // Error: number 不能赋值给 string
解决方案:使用字符串值:
Radio({ value: 'male', group: 'gender' })
8.6 @Builder 方法中不能对自定义组件链式调用属性方法
问题:在 @Builder 方法中调用 FormFieldItem({...}) { ... }.layoutWeight(1) 导致 Cannot find name 'layoutWeight' 错误。
原因:在 @Builder 上下文中,自定义组件实例化后不能像在 build() 方法中那样链式调用属性设置方法。这是一个已知的 ArkTS 编译器限制。
解决方案:
- 对于
width('100%'):直接在FormFieldItem的build()方法内部设置 - 对于
layoutWeight:将FormFieldItem包裹在Column容器中,在容器上设置layoutWeight(但为了简洁,我们最终选择让大屏模式不做权重分配)
8.7 避免 @Builder 中调用 @Builder 传参导致的冗余
这是一个设计层面的建议:大屏模式(MultiColumnForm)和小屏模式(SingleColumnForm)虽然逻辑相似但布局结构不同。如果尝试通过参数化的 @Builder 方法来统一两者,代码反而会因为条件分支过多而难以维护。因此我们选择保留两个独立的 @Builder 方法,虽然代码量稍大,但清晰度更高。
九、性能考量
9.1 onAreaChange 的性能
onAreaChange 只在容器的尺寸实际变化时回调,不会在每次帧渲染时触发。这意味着:
- 旋转屏幕:触发一次
- 拖拽分屏:每次拖拽暂停时触发
- 键盘弹出/收起:触发一次
- 窗口最大化/恢复:触发一次
因此性能开销可以忽略不计。
9.2 条件渲染的性能
使用 if (this.isMultiColumn) 进行布局分支切换时,ArkTS 会销毁当前分支的组件树并创建新分支的组件树。这意味着切换时会有短暂的 UI 重建过程。
为了优化,可以考虑:
- 使用
opacity和visibility:对于只是隐藏/显示的场景,用这些属性代替条件渲染 - 懒加载:对于非首屏内容,可以使用
LazyForEach @Builder缓存:ArkTS 编译器会对@Builder方法进行优化缓存
对于本项目的场景(用户不频繁切换窗口大小),条件渲染的开销完全可以接受。
十、测试验证
10.1 构建验证
通过 hvigorw assembleApp --build-mode debug 命令可以验证代码的编译正确性。每次修改后都应该运行此命令,确保:
- 0 ERROR:编译错误必须全部修复
- 0 ArkTS WARNING:IDE 的警告也应尽量清零,因为有些警告在 DevEco Studio 的预览器中可能会被当作错误处理
10.2 预览验证
在 DevEco Studio 中,可以打开 Index.ets 文件,点击右侧的 Previewer 标签进行实时预览。通过拖拽预览器的边框改变窗口宽度,观察布局是否在 600vp 处正确切换。
10.3 真机验证
建议在以下设备上验证:
- 手机(竖屏 360~420vp):验证单列布局
- 平板(竖屏 600~800vp):验证双列布局
- 平板(横屏 800~1280vp):验证双列布局的宽屏表现
- 折叠屏(展开态 ~800vp):验证布局切换的流畅性
十一、扩展与展望
11.1 三列布局
本项目的阈值是 600vp,这只是一个起点。对于更大尺寸的屏幕(如 PC 端 1440vp+),可以增加第三个阈值,实现三列布局:
get layoutMode(): LayoutMode {
if (this.containerWidth > 1200) return LayoutMode.TRIPLE;
if (this.containerWidth > 600) return LayoutMode.DOUBLE;
return LayoutMode.SINGLE;
}
11.2 表格视图
对于更复杂的企业表单(如订单列表、审批流),可以在大屏模式下切换为表格视图,在 Row 中嵌入 List / Grid 组件,显示更多数据列。
11.3 与 GridRow/GridCol 结合
ArkTS 的 GridRow / GridCol 组件提供了更完善的栅格系统,可以结合本文的 onAreaChange 方法,实现更精细的断点控制:
GridRow({
columns: this.isMultiColumn ? { sm: 12, md: 6 } : { sm: 12 },
gutter: { x: 16, y: 16 }
}) {
GridCol({ span: { sm: 12, md: 6 } }) { /* 表单字段 */ }
GridCol({ span: { sm: 12, md: 6 } }) { /* 表单字段 */ }
}
11.4 表单验证
当前项目还未添加表单验证功能。可以扩展 FormFieldItem 组件,增加 validate 属性和错误提示能力:
FormFieldItem({ label: '邮箱', required: true, validate: EmailValidator }) {
// ...
}
十二、总结
12.1 技术要点回顾
- 容器级响应式布局:使用
onAreaChange监听容器宽度变化,结合@State和计算属性isMultiColumn驱动布局分支切换 - 组件化设计:通过
@Component+@BuilderParam实现可复用的表单项包装组件 - 纯组件弹窗:用
Stack+ 条件渲染 +@Link替代@CustomDialog,避免框架约束 - ArkTS 开发规范:遵循 ArkTS 的初始化规则、访问修饰符约束和类型要求
12.2 架构评价
| 维度 | 评价 |
|---|---|
| 可维护性 | 布局清晰,两个模式独立 @Builder,修改互不影响 |
| 可扩展性 | 可轻松添加第三个阈值实现三列布局 |
| 性能 | onAreaChange 惰性触发,条件渲染仅在切换时重建 |
| 代码可读性 | 命名语义化,组件职责单一,@Builder 方法结构清晰 |
| 可测试性 | 状态纯函数,分离了数据与 UI |
12.3 心得体会
在鸿蒙生态中做响应式布局,Flutter 开发者可能会本能地寻找 LayoutBuilder 的替代品。onAreaChange 虽然 API 签名和使用方式与 LayoutBuilder 不同——它不返回子树而是通过状态驱动——但最终达成的效果是一致的:代码根据容器的实时尺寸智能地选择最合适的布局方案。
更重要的是,onAreaChange 的工作方式更符合 ArkTS 的声明式哲学:UI 是状态的函数,而不是事件的直接结果。这种从"命令式回调"到"声明式状态驱动"的思维转换,是掌握 ArkTS 的关键一步。
本文档对应的完整源代码位于项目根目录 entry/src/main/ets/pages/Index.ets 中。
更多推荐



所有评论(0)