HarmonyOS NEXT 学生管理应用开发实战 —— Column + layoutWeight 弹性布局构建数据管理界面


1. 引言:从数据管理看布局设计
在移动应用开发中,数据管理类应用是最常见的类型之一——通讯录、订单列表、课程表、员工花名册、学生成绩册……它们的核心需求高度相似:展示数据列表、提供筛选搜索、查看条目详情、展示统计概览。这类应用的设计难点从来不在于某个炫酷的交互特效,而在于如何用最简洁的布局结构承载最多的信息量。
想象一下你面前有一个班级的花名册:你需要一眼看到全班有多少人、男女比例是多少、平均成绩如何;你需要能够按班级筛选、查看某个学生的详细信息;你还需要快速操作——添加新人、查看统计报表。所有这些需求都要在一个手机屏幕上完成,而且不能让人觉得拥挤或混乱。这就是布局设计要解决的核心问题。
这类应用的界面布局通常遵循一个经过多年验证的固定模式:
顶部:标题 + 统计概览 + 筛选条件
中部:可滚动的数据列表(核心内容区)
底部:操作按钮 + 辅助信息
在鸿蒙 ArkUI 框架中,这种"三段式"布局的最佳实现方案就是 Column + layoutWeight 弹性布局。固定区用 height() 锁定高度,弹性区用 layoutWeight() 按比例分配剩余空间。这样无论屏幕尺寸如何变化,顶部和底部始终可见,中间内容区自动适配填满所有可用空间。
本文将以一个完整的学生管理应用为载体,系统讲解如何利用 Column + layoutWeight 构建响应式的数据管理界面。这个应用包含了 24 名学生的完整数据,横跨三个班级,包含姓名、性别、年龄、专业、分数、电话等八个字段。它支持班级筛选、展开详情、实时统计和操作按钮,所有交互都基于 @State 响应式状态管理驱动。无论是布局结构还是交互模式,都可以作为通用数据管理应用开发的参考模板。
2. 项目全景:学生管理应用的架构设计
2.1 项目结构
entry/src/main/ets/pages/StudentApp.ets ← 唯一页面(511 行)
entry/src/main/resources/base/profile/
└── main_pages.json ← 页面路由
entry/src/main/ets/entryability/
└── EntryAbility.ets ← Ability 入口
2.2 SDK 版本
build-profile.json5 中确认了 SDK 版本:
{
"products": [{
"name": "default",
"targetSdkVersion": "6.1.1(24)",
"compatibleSdkVersion": "6.1.1(24)",
"runtimeOS": "HarmonyOS"
}]
}
2.3 页面布局架构
整个页面由最外层 Column 撑满全屏,内部包含 5 个区段:
Column() .width('100%').height('100%')
├── 固定顶部 .height(68) ← 标题 + 班级筛选标签
├── 弹性区 A .layoutWeight(1.0) ← 50% 学生列表(可滚动)
├── 弹性区 B .layoutWeight(0.4) ← 20% 统计概览
├── 弹性区 C .layoutWeight(0.6) ← 30% 操作按钮
└── 固定底部 内容撑高 ← 布局要点说明
弹性分配公式(以 800vp 屏幕为例):
弹性空间 = 800 - 68(顶) - 说明区(≈140) ≈ 592vp
学生列表 = 592 × 1.0/(1.0+0.4+0.6) = 592 × 50% ≈ 296vp
统计概览 = 592 × 0.4/2.0 = 592 × 20% ≈ 118vp
操作按钮 = 592 × 0.6/2.0 = 592 × 30% ≈ 178vp
2.4 交互功能矩阵
| 功能 | 技术实现 | 用户操作 |
|---|---|---|
| 班级筛选 | @State filterClass + getter filteredList | 点击顶部班级标签 |
| 展开详情 | @State selectedId + 条件渲染 if | 点击学生卡片 |
| 实时统计 | getter 计算 avgScore/maxScore/passRate | 自动更新 |
| 操作按钮 | Button + onClick + Toast | 点击按钮 |
| 分数着色 | scoreColor() 条件返回 | 自动应用 |
3. Column + layoutWeight 弹性布局的前世今生
3.1 layoutWeight 的本质
ArkUI 中的 layoutWeight 是一个子组件属性,而不是一个独立的组件。这与 Flutter 等框架存在根本区别——Flutter 使用 <Expanded> 包裹组件,而 ArkUI 直接在子组件上调用方法。这种设计让代码更加扁平,减少了不必要的嵌套层级。
它的作用是告诉父容器:“我愿意参与剩余空间的分配,这是我的权重。”
Column() {
Row() { /* 标题 */ }.height(68) // 固定高度 68vp
Column() { /* 列表 */ }.layoutWeight(1.0) // 吃掉 50% 的剩余空间
Column() { /* 统计 */ }.layoutWeight(0.4) // 吃掉 20%
Column() { /* 按钮 */ }.layoutWeight(0.6) // 吃掉 30%
Column() { /* 说明 */ } // 由内容撑高,不参与弹性分配
}
这里有一个细节值得注意:最后一个 Column 没有设置 layoutWeight,也没有设置 height。它的高度由其内部的内容撑开。这意味着它不参与弹性空间分配,但它的高度会计入"固定高度"的总和,从 Column 的可用高度中减去。这说明一个 Column 的子组件可以混合使用固定高度、弹性权重和内容撑高三种模式——ArkUI 会先计算固定高度,再扣除内容撑高的部分,最后把剩余空间按权重分配给弹性区。
3.2 weight 不是 flex
很多从 CSS 转过来的开发者会认为 layoutWeight 等同于 CSS Flexbox 中的 flex 属性。它们确实类似,但有一个关键区别:CSS 的 flex 属性是一个复合属性(包含了 flex-grow、flex-shrink 和 flex-basis),它同时控制了子组件的拉伸比例、收缩比例和初始尺寸。而 ArkUI 的 layoutWeight 只做一件事——按比例分配父容器的剩余空间。它不控制子组件的收缩行为,也不设置初始尺寸。
/* CSS flex:一个属性控制三种行为 */
.item {
flex: 1; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0 */
}
// ArkUI layoutWeight:只做一件事
Column() { }.layoutWeight(1.0) // 只控制剩余空间分配比例
这种简洁设计的背后是 ArkUI 的布局哲学:明确区分固定尺寸和弹性尺寸。固定尺寸用 height(),弹性尺寸用 layoutWeight(),没有模棱两可的中间地带。开发者不需要理解 flex-grow 和 flex-shrink 的区别,只需要知道"这个组件要固定高度还是弹性比例"。
3.3 为什么必须设置 height(‘100%’)
这是所有 ArkTS 初学者最容易踩的坑,也是在论坛和社区中被问得最多的问题。Column 如果不设置 height('100%'),它的高度将由子组件撑开——也就是说,Column 的高度刚好等于所有子组件的高度之和。在这种情况下,Column 没有"剩余空间"可以分配:它的可用空间已经被子组件占满了。
当 layoutWeight 试图计算"我应该占多少空间"时,它的计算公式是:
实际高度 = (父容器高度 - 固定高度之和) × (自身权重 / 总权重)
如果父容器没有固定高度(也没有 height(‘100%’)),那么父容器高度等于子组件高度之和,于是 父容器高度 - 固定高度之和 ≈ 0,乘以任何权重都等于零。这就是为什么 layoutWeight 设置了却不起作用。
因此,每次使用 layoutWeight 时,都要确认最外层 Column 设置了 .height('100%')。这是一个一劳永逸的设置——写一次,永远有效。
4. 三段式弹性布局在数据管理中的应用
4.1 代码结构
build() {
Column() {
// ── 固定顶部 ──
Column() { /* 标题 + 筛选标签 */ }
.height(68)
// ── 弹性区 A:学生列表 ──
Column() {
Scroll() {
Column({ space: 0 }) {
ForEach(...) { /* 学生卡片 */ }
}
}
.layoutWeight(1)
}
.layoutWeight(1.0)
// ── 弹性区 B:统计概览 ──
Column() { /* 平均分 | 最高分 | 及格率 */ }
.layoutWeight(0.4)
// ── 弹性区 C:操作按钮 ──
Column() { /* 添加 | 统计 | 刷新 */ }
.layoutWeight(0.6)
// ── 固定底部 ──
Column() { /* 布局要点 */ }
}
.width('100%')
.height('100%')
}
4.2 弹性分配的可视化效果
当屏幕高度变化时:
- 固定顶部(68vp):始终保持 68vp,不变化
- 学生列表(50%):屏幕越高,可见的学生条目越多;屏幕越矮,条目越少但可滚动
- 统计概览(20%):始终保持足够显示三个数字和一排文字
- 操作按钮(30%):始终保持足够容纳三个按钮
- 说明面板:由内容撑高,内容固定,高度固定
这种分层设计确保了关键信息始终可见,即使在较小的屏幕上也不会丢失功能。
5. 数据模型设计:Student 接口与样例数据
5.1 接口定义
interface Student {
id: number;
name: string;
gender: string; // 男 / 女
age: number;
className: string; // 班级
score: number; // 总分
major: string; // 专业
phone: string; // 电话号码
}
interface ClassInfo {
name: string;
studentCount: number;
}
Student 接口包含了学生管理最核心的 8 个字段。id 作为唯一标识用于列表渲染的 key 和展开详情的判断。
5.2 样例数据
24 名学生分布在 3 个班级,每个班级 8 人:
const STUDENTS: Student[] = [
{ id: 101, name: '张明', gender: '男', age: 18, className: '高一(1)班',
score: 92, major: '理科', phone: '138****1234' },
{ id: 102, name: '李华', gender: '女', age: 17, className: '高一(1)班',
score: 88, major: '理科', phone: '138****5678' },
// ... 共 24 条
];
const CLASSES: ClassInfo[] = [
{ name: '高一(1)班', studentCount: 8 },
{ name: '高一(2)班', studentCount: 8 },
{ name: '高一(3)班', studentCount: 8 },
];
数据的分布覆盖了不同分数段(61~98 分)、性别比例(男女各约一半)、专业分布(文理科各占一定比例),让筛选和统计功能有实际意义。
6. 响应式状态管理:@State 驱动筛选与展开
6.1 两个核心状态变量
@State private selectedId: number = -1; // 当前展开的学生 id
@State private filterClass: string = ''; // 筛选的班级名
selectedId 控制学生卡片的展开/收起。-1 表示没有展开任何卡片,其他值表示展开了对应 id 的学生。
filterClass 控制列表筛选。空字符串表示显示全部学生,非空字符串表示只显示该班级的学生。
6.2 getter 驱动的筛选逻辑
private get filteredList(): Student[] {
if (this.filterClass.length <= 0) {
return STUDENTS;
}
let result: Student[] = [];
for (let i: number = 0; i < STUDENTS.length; i++) {
if (STUDENTS[i].className === this.filterClass) {
result[result.length] = STUDENTS[i];
}
}
return result;
}
getter 每次被访问时重新计算,直接依赖于 filterClass 的值。当 filterClass 变化时,ArkUI 框架自动重新渲染所有依赖 filteredList 的 UI 部分。
6.3 数据流链路
点击班级标签
→ setFilter(cls) 修改 @State filterClass
→ filteredList 重新计算
→ ForEach 重新渲染列表
→ totalCount / avgScore 等统计重新计算
→ UI 更新
这是一个完整的单向数据流:用户操作 → 状态变更 → 数据重算 → UI 更新。不需要手动操作 DOM,不需要通知机制,ArkUI 的响应式系统自动完成所有中间步骤。
7. 列表渲染:Scroll + Column + Divider 实现学生卡片列表
7.1 核心结构
Scroll() {
Column({ space: 0 }) {
ForEach(this.filteredList, (stu: Student, idx: number) => {
if (idx > 0) {
Divider().height(1).color('#E0E0E0').margin({ top: 2, bottom: 2 })
}
// 学生卡片...
}, (stu: Student) => stu.id.toString())
}
}
.layoutWeight(1)
Scroll 包裹 Column 实现可滚动列表,Column 的 space 设为 0 表示间距由 Divider 的 margin 控制。
7.2 学生卡片概要行
Row() {
// 姓名
Text(stu.name).fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A237E')
// 性别图标
Text(stu.gender === '男' ? ' ♂' : ' ♀')
.fontColor(stu.gender === '男' ? '#1565C0' : '#E91E63')
// 班级标签
Text(stu.className).fontSize(11)
.backgroundColor('#F5F5F5').borderRadius(4)
Blank()
// 分数 + 等级
Text('' + stu.score).fontColor(this.scoreColor(stu.score))
Text(this.scoreLabel(stu.score))
// 展开箭头
Text(this.selectedId === stu.id ? '▲' : '▼')
}
.onClick(() => { this.toggleDetail(stu.id); })
每行包含姓名、性别图标、班级标签、分数、等级标签、展开箭头,外加点击展开详情。信息密度适中,一目了然。
7.3 Divider 的分隔作用
条目之间的 Divider 使用极浅灰色(#E0E0E0)和小间距(margin: 2),在视觉上提供轻微的分隔,既区分了条目边界,又不会喧宾夺主。
8. 条件展开:点击卡片查看详情
8.1 展开逻辑
private toggleDetail(id: number): void {
if (this.selectedId === id) {
this.selectedId = -1; // 点击已展开的卡片 → 收起
} else {
this.selectedId = id; // 点击其他卡片 → 展开
}
}
这是一个典型的手风琴(Accordion)交互模式——点击展开,再次点击或点击其他项时切换展开状态。
8.2 条件渲染详情区域
if (this.selectedId === stu.id) {
Column() {
Divider().height(1).color('#E8E8E8')
Row() {
Text('年龄:').fontSize(12).fontColor('#9E9E9E').width(48)
Text('' + stu.age + ' 岁')
Blank()
Text('专业:').fontSize(12).fontColor('#9E9E9E').width(48)
Text(stu.major)
}
Row() {
Text('电话:').fontSize(12).fontColor('#9E9E9E').width(48)
Text(stu.phone)
Blank()
Text('学号:').fontSize(12).fontColor('#9E9E9E').width(48)
Text('' + stu.id)
}
}
}
详情区域使用 if (this.selectedId === stu.id) 条件渲染。当条件不满足时,ArkUI 不会创建这些组件,完全避免了隐藏/显示切换的开销。
8.3 两列布局的细节
详情中的信息使用 Row + Text(标签) + Text(值) + Blank() + Text(标签) + Text(值) 的布局,将四个字段排列为两行两列:
年龄:18 岁 专业:理科
电话:138****1234 学号:101
每个标签使用固定宽度 48vp 右对齐,值使用自适应宽度,Blank() 占据中间剩余空间将两列推向两侧。
9. 实时统计:getter 计算平均分、最高分和及格率
9.1 统计 getter
/** 平均分 */
private get avgScore(): number {
if (this.totalCount <= 0) return 0;
let sum: number = 0;
for (let i: number = 0; i < this.filteredList.length; i++) {
sum = sum + this.filteredList[i].score;
}
return Math.round(sum / this.totalCount);
}
/** 最高分 */
private get maxScore(): number {
let m: number = 0;
for (let i: number = 0; i < this.filteredList.length; i++) {
if (this.filteredList[i].score > m) {
m = this.filteredList[i].score;
}
}
return m;
}
/** 及格率(>= 60 分) */
private get passRate(): number {
if (this.totalCount <= 0) return 0;
let pass: number = 0;
for (let i: number = 0; i < this.filteredList.length; i++) {
if (this.filteredList[i].score >= 60) pass = pass + 1;
}
return Math.round(pass * 100 / this.totalCount);
}
三个统计 getter 都基于 filteredList 计算,因此当筛选条件变化时,统计数据自动更新。这是响应式编程的一个显著优势:计算逻辑声明一次,数据变化时自动重新计算。
9.2 统计概览 UI
Row() {
Column() { Text(avgScore).fontColor('#1565C0') Text('平均分') }
Divider().height(24).width(1).color('#E0E0E0') // 竖线分隔
Column() { Text(maxScore).fontColor('#E65100') Text('最高分') }
Divider().height(24).width(1).color('#E0E0E0') // 竖线分隔
Column() { Text(passRate + '%').fontColor('#2E7D32') Text('及格率') }
}
每个统计项使用 Column 垂直排列数字和标签,数字用大字号(20fp)和不同颜色突出显示,标签用小字号灰色辅助说明。
10. Divider 的另类用法:竖线分隔符
10.1 原理
Divider 默认是水平分割线(横跨父容器宽度)。但通过调整 height 和 width 的属性值,可以将其旋转为竖线:
Divider()
.height(24) // 竖线的高度(垂直长度)
.width(1) // 竖线的宽度(水平粗细)
.color('#E0E0E0') // 颜色
关键技巧:height 控制长度,width 控制粗细。这与水平分割线正好相反。
10.2 在统计概览中的应用
在统计概览区(弹性区 B),三个统计数字之间用竖线分隔:
83 | 98 | 96%
平均分 | 最高分 | 及格率
竖线分隔符比文字分隔符(如 “|”)更加清晰美观,而且颜色、粗细、长度都可以精确控制。
10.3 注意事项
使用 Divider 做竖线时,父容器必须是 Row(水平排列)。Divider 在 Row 中占据宽度为 1vp、高度为 24vp 的空间,与两侧的 Column 并列。
11. 班级筛选:ForEach 动态标签栏
11.1 标签栏实现
Row() {
Text('全部')
.fontColor(this.filterClass === '' ? '#FFD54F' : '#B3E5FC')
.fontWeight(this.filterClass === '' ? FontWeight.Bold : FontWeight.Regular)
.backgroundColor(this.filterClass === '' ? '#ffffff22' : 'transparent')
.borderRadius(8)
.onClick(() => { this.resetFilter(); })
ForEach(CLASSES, (cls: ClassInfo) => {
Text(cls.name)
.fontColor(this.filterClass === cls.name ? '#FFD54F' : '#B3E5FC')
.fontWeight(this.filterClass === cls.name ? FontWeight.Bold : FontWeight.Regular)
.backgroundColor(this.filterClass === cls.name ? '#ffffff22' : 'transparent')
.borderRadius(8)
.margin({ left: 4 })
.onClick(() => { this.setFilter(cls.name); })
}, (cls: ClassInfo) => cls.name)
}
11.2 选中状态样式
当前选中的标签使用金色文字(#FFD54F)、粗体和半透明白色背景(#ffffff22);未选中的标签使用浅蓝色文字(#B3E5FC)、普通字重和透明背景。这种样式对比让当前筛选条件一目了然。
11.3 标签栏的响应性
当点击"高一(2)班"时:
filterClass变为'高一(2)班'- 标签栏重新渲染,"高一(2)班"获得选中样式,"全部"和"高一(1)班"恢复未选中样式
filteredList只返回高一(2)班的学生(8 人)- 平均分、最高分、及格率基于这 8 人重新计算
- 被选中的学生 id 重置为 -1(收起所有展开的详情)
整个过程不需要手动操作任何 DOM,ArkUI 的响应式系统自动完成了所有步骤。
12. 分数等级系统:条件着色与标签映射
12.1 等级颜色映射
private scoreColor(s: number): string {
if (s >= 90) return '#2E7D32'; // 优秀 → 绿色
if (s >= 80) return '#1565C0'; // 良好 → 蓝色
if (s >= 70) return '#E65100'; // 中等 → 橙色
if (s >= 60) return '#8D6E63'; // 及格 → 棕色
return '#C62828'; // 不及格 → 红色
}
12.2 等级标签映射
private scoreLabel(s: number): string {
if (s >= 90) return '优秀';
if (s >= 80) return '良好';
if (s >= 70) return '中等';
if (s >= 60) return '及格';
return '不及格';
}
12.3 在卡片中的应用
每个学生卡片的分数使用对应的等级颜色显示,右侧附加两字等级标签:
张明 ♂ 高一(1)班 92 优秀 ▼
杨磊 ♂ 高一(1)班 67 及格 ▼
颜色编码让分数高低一目了然——扫一眼颜色分布,就能大致判断班级的整体水平。绿色和蓝色越多,班级成绩越好;红色出现,说明有需要关注的学生。
13. 性能优化与最佳实践
13.1 @State 最小化原则
@State 是 ArkUI 响应式系统的核心,但滥用 @State 会导致性能问题——每次 @State 变量变化时,框架都会重新执行组件的 build() 方法,重新创建或更新 UI 树。如果 @State 变量变化过于频繁(比如每帧变化),就会造成严重的性能瓶颈。
因此,一个好的原则是:只将直接影响 UI 显示的数据标记为 @State,其他数据保持为普通变量、getter 计算属性或 const 常量。
本应用中只有两个 @State 变量:
@State private selectedId: number = -1; // 需要 → UI 控制展开/收起
@State private filterClass: string = ''; // 需要 → UI 控制筛选标签
不需要 @State 的数据包括:
STUDENTS常量数组——数据不会变化,声明为顶层constCLASSES常量数组——同上filteredList——这是一个 getter 计算属性,每次访问时根据filterClass重新计算,本身不存储状态totalCount、maleCount、femaleCount——同样为 getter 计算属性avgScore、maxScore、passRate——统计计算,基于 filteredList 实时计算
如果错误地把 avgScore 声明为 @State,那么每次筛选变化时,开发者需要手动 this.avgScore = newValue,不仅代码冗余,还容易遗漏。用 getter 计算属性,ArkUI 会自动追踪依赖关系,在依赖的 @State 变化时自动重新计算。
13.2 ForEach 的 key 函数
ForEach(this.filteredList, (stu, idx) => {
// 学生卡片 UI
}, (stu: Student) => stu.id.toString())
ForEach 的第三个参数是 key 生成函数。key 是 ArkUI 框架追踪列表项的唯一标识。当列表数据变化时(比如筛选条件改变导致列表项增减),框架通过 key 判断:
- key 存在的项 → 复用已有组件
- key 不存在的项 → 销毁组件
- key 新增的项 → 创建新组件
使用 stu.id.toString() 作为 key 是最佳实践——id 是每个学生的唯一标识,在整个数据集中不会重复,也不会随时间变化。如果使用 idx(索引)作为 key,当列表排序或筛选变化时,同一个 idx 可能对应不同的学生,导致组件状态错乱。
13.3 条件展开的性能考量
学生卡片的展开详情区域使用了 if 条件渲染,而不是 .visibility() 属性控制显隐:
if (this.selectedId === stu.id) {
// 详情区域
Column() {
Divider().height(1).color('#E8E8E8')
Row() { /* 年龄 + 专业 */ }
Row() { /* 电话 + 学号 */ }
}
}
if 条件渲染和 .visibility() 属性在功能上都可以实现"显示/隐藏",但二者的性能特征完全不同:
if条件渲染:条件不满足时,组件完全不存在于组件树中,不占用任何内存,也不参与布局计算。条件从 false 变为 true 时,组件从头开始创建。visibility(Hidden):组件始终存在于组件树中,只是不可见。它仍然占用布局空间,仍然参与布局计算。
对于"展开详情"这种交互——一次只展开一个卡片,99% 的时间里大多数卡片都是收起状态——使用 if 条件渲染可以显著减少组件数量,降低内存占用。
如果是一个"需要频繁显隐切换"的场景(比如动画效果),.visibility() 可能更合适。但在我们的学生管理应用中,if 条件渲染是最优选择。
13.4 避免在 build() 中做复杂计算
build() 方法应该只负责描述 UI 结构,不应该包含复杂的计算逻辑。本应用中的所有统计计算都封装在 getter 中:
// ✅ 在 getter 中计算
private get avgScore(): number {
// 求和 → 求平均 → 返回
}
// ✅ 在 getter 中计算
private get passRate(): number {
// 统计及格人数 → 计算百分比 → 返回
}
// build() 中只引用结果
build() {
Text('' + this.avgScore) // 直接使用计算好的值
}
这种分离的好处是:
- build() 方法更简洁,只包含 UI 描述代码
- 计算逻辑可以单独测试和复用
- 当需要修改计算逻辑时,不需要改动 UI 代码
13.5 合理使用 Blank() 占位
在 Row 布局中,Blank() 是一个"饥饿"的占位组件——它会吃掉所有剩余空间。在行布局的末尾使用 Blank() 可以轻松实现"左对齐"效果:
Row() {
Text('姓名')
Blank() // ← 吃掉中间所有剩余空间
Text('分数:92') // ← 被推到右端
}
这比设置 justifyContent(FlexAlign.SpaceBetween) 更加精确——Blank() 只占剩余空间,而 SpaceBetween 会将所有子组件均匀分布。
13.6 颜色常量的复用
分数等级颜色和标签通过两个函数 scoreColor() 和 scoreLabel() 统一管理。这种集中管理的方式避免了在 UI 代码中分散的条件判断:
// 集中管理
private scoreColor(s: number): string {
if (s >= 90) return '#2E7D32';
if (s >= 80) return '#1565C0';
// ...
}
// UI 中直接调用
Text('92').fontColor(this.scoreColor(92)) // → 绿色
Text('67').fontColor(this.scoreColor(67)) // → 棕色
当需要调整颜色方案时(比如把"优秀"从绿色改为金色),只需要修改 scoreColor() 一个地方,所有卡片同步更新。
14. 总结:从学生管理到通用数据应用
14.1 核心要点回顾
布局模式: 最外层 Column 使用 .width('100%').height('100%') 撑满全屏。内部子组件分为三种类型:用 height(px) 锁定的固定区(顶部标题栏 + 底部说明面板)、用 layoutWeight(flex) 分配剩余空间的弹性区(学生列表 + 统计概览 + 操作按钮)、以及由内容撑高的自适应区(同样也是底部说明面板)。这三种模式的混合使用,构成了 ArkUI 中构建数据管理界面的标准化方案。
Column() // 最外层:全屏
.width('100%').height('100%') // ★ 必须设置
Column() { /* 固定顶 */ }.height(68) // 固定区
Column() { /* 列表 */ }.layoutWeight(1.0) // 弹性区(50%)
Column() { /* 统计 */ }.layoutWeight(0.4) // 弹性区(20%)
Column() { /* 按钮 */ }.layoutWeight(0.6) // 弹性区(30%)
Column() { /* 说明 */ } // 固定区(内容撑高)
数据流模式: 用户操作 → 修改 @State 变量 → getter 计算属性自动重算 → UI 自动更新。这是一个闭环的单向数据流,不需要手动操作 DOM,不需要事件通知机制,ArkUI 的响应式系统自动完成所有中间步骤。
@State 变化 → getter 重算 → UI 自动更新
↑ |
用户操作 ←—— onClick ←——
功能实现速查表:
| 功能 | 技术 | ArkTS 关键代码 |
|---|---|---|
| 标题栏 | Column + Row | Column().height(68) |
| 筛选标签 | ForEach + 条件样式 | fontColor(cond?'#FFD54F':'#B3E5FC') |
| 学生列表 | Scroll + Column + Divider | Scroll() { Column({space:0}) {} } |
| 展开详情 | @State + if 条件渲染 | if (this.selectedId === stu.id) {} |
| 统计计算 | getter 计算属性 | private get avgScore(): number {} |
| 竖线分隔 | Divider 变形 | Divider().height(24).width(1) |
| 分数着色 | 条件函数 | scoreColor(s: number): string {} |
| 操作按钮 | Button + onClick | Button('📊 统计分析').onClick(()=>{}) |
| 交互反馈 | promptAction.showToast | showToast({message:'...', duration:1500}) |
14.2 从学生管理到通用数据应用
本文的学生管理应用虽然只是一个示例,但其架构模式可以轻松迁移到任何数据管理类应用。只需要替换数据模型和界面文案,底层的架构完全不用动:
通讯录管理:将 Student 接口替换为 Contact 接口(字段改为姓名、电话、邮箱、分组),筛选条件改为"分组"或"首字母",统计改为"总人数/各分组人数"。Column + layoutWeight 的布局结构、Scroll + Divider 的列表渲染、@State + getter 的数据流——全部复用。
更多推荐

所有评论(0)