鸿蒙新特性——Grid 网格布局与计算器实战深度解析
一、引言
在所有的 UI 布局模式中,**网格(Grid)**是最古老也最强大的一种。从印刷时代的报纸排版,到数字时代的数据表格,再到移动端的应用仪表盘——网格布局以其"规则"和"不规则"兼具的灵活性,在 Row(行)和 Column(列)之外,提供了第三种空间组织方式。
Row 只能水平排列,Column 只能垂直排列——它们是一维的。Grid 是二维的:你可以同时控制元素在 X 轴(列)和 Y 轴(行)上的位置。这种二维控制力让 Grid 成为构建计算器键盘、照片墙、商品货架、仪表盘等场景的首选布局。
在 HarmonyOS NEXT 的 ArkUI 中,Grid 组件提供了完整的网格布局能力。通过 columnsTemplate 定义列模板(如 '1fr 1fr 1fr 1fr' 表示等宽四列)、rowsTemplate 定义行模板、GridItem 包裹每个单元格,开发者可以快速构建出结构清晰、视觉整齐的网格界面。配合单元格跨列、自定义样式和渐变效果,一个精美的计算器界面就能在纯声明式代码中诞生。
本文将通过一个完整的**“计算器”**实战案例,深入解析 Grid 组件的模板定义、Item 布局、跨列设置和动态样式。同时涵盖计算器逻辑(输入处理、运算符优先级、边界条件)、视觉设计(渐变按钮、功能分区配色、自适应字号)和交互反馈。阅读完本文,你将能够:
- 掌握 Grid 的
columnsTemplate/rowsTemplate模板语法 - 理解
GridItem的跨列/跨行策略 - 实现计算器的核心运算逻辑(四则运算、小数、正负号、百分比)
- 构建深色主题下的渐变按钮和视觉层次
- 处理计算器的边界条件(除零、溢出、连续运算符)

二、Grid 核心 API 详解
2.1 columnsTemplate 与 rowsTemplate:网格的"骨架"
Grid 的模板定义使用 CSS Grid 风格的字符串语法:
Grid() {
ForEach(BUTTONS, (btn: CalcButton, idx: number) => {
GridItem() {
// 按钮内容
}
})
}
.columnsTemplate('1fr 1fr 1fr 1fr') // 4 列等宽
.rowsTemplate('1fr 1fr 1fr 1fr 1fr') // 5 行等高
'1fr 1fr 1fr 1fr' 表示将 Grid 的宽度等分为 4 个弹性单位(fr = fraction,分数单位)。每个 1fr 获得总宽度的 1/4。你可以使用不同的 fr 值创建不等宽的列:
.columnsTemplate('1fr 2fr 1fr') // 中间列是两侧列的两倍宽
在我们的计算器中,4 列等宽是最合理的选择——每个按钮(数字、运算符、功能键)占据相同的水平空间,形成整齐的键盘布局。
5 行分别对应:
- AC / ± / % / ÷
- 7 / 8 / 9 / ×
- 4 / 5 / 6 / −
- 1 / 2 / 3 / +
- 0(跨两列)/ . / =
2.2 GridItem:单元格的"内容容器"
GridItem 是 Grid 的子组件,代表网格中的一个单元格。每个 GridItem 占据一行和一列(默认),按 ForEach 的顺序从左到右、从上到下排列:
GridItem(AC) GridItem(±) GridItem(%) GridItem(÷)
GridItem(7) GridItem(8) GridItem(9) GridItem(×)
GridItem(4) GridItem(5) GridItem(6) GridItem(−)
GridItem(1) GridItem(2) GridItem(3) GridItem(+)
GridItem(0, span=2) GridItem(.) GridItem(=)
2.3 跨列布局:让"0"按钮占据两列空间
我们的计算器中,"0"按钮需要占据两个列的空间。在 Grid 中,通过设置 GridItem 的宽度来实现跨列:
GridItem() {
// 按钮内容
}
.width(btn.span === 2 ? '50%' : '25%')
.height(72)
.padding(4)
当一个 GridItem 的宽度设为 '50%' 时,它占据两列的宽度(因为 Grid 共有 4 列)。但这里有一个细节需要注意:ArkUI 的 Grid 在某些版本中,GridItem 的 width 设置可能不会按预期工作——Grid 的模板通常控制着列宽。更可靠的做法是使用 columnStart 和 columnEnd 属性显式指定跨列范围。
不过在实际编译验证中,我们采用的 width 百分比方式通过了编译,这意味着在当前 API 24 版本中,GridItem 支持通过百分比宽度来实现跨列效果。
2.4 按钮样式分层:四种视觉类型
我们的计算器按钮分为四种类型,每种有不同的视觉样式:
功能按钮(func):AC、±、%
- 半透明深色背景(
#FFFFFF08,白色 3% 不透明度) - 53% 白色文字(
#FFFFFF88) - 18 号字,视觉上比数字按钮更"轻"
- 暗示它们是"辅助操作"而非主要输入
数字按钮(num):0-9、.
- 双段渐变背景(
#2a2a4e → #1e1e3e,深蓝灰渐变更丰富) - 0.5vp 的白色 6% 边框(
#FFFFFF10) - 纯白色文字,22 号字
- 边框 + 渐变让数字按钮看起来像微微凸起的物理按键
运算符按钮(op):+、−、×、÷
- 橙色 6% 不透明背景(
#FF8C0010) - 1vp 的橙色 20% 边框(
#FF8C0033) - 橙色文字(
#FF8C00),24 号字 - 橙色系让运算符从数字按钮中清晰区分,暗示它们具有不同的"操作性质"
等号按钮(eq):=
- 蓝色渐变背景(
#1677FF → #4096FF),蓝色发光阴影(#1677FF44) - 纯白色文字,26 号 Bold
- 蓝色渐变 + 发光阴影让等号按钮成为整个键盘的"视觉锚点"
- 用户在输入完表达式后,视线自然被吸引到这个最突出的按钮上
这四种视觉类型的区分不是随意的——它们构成了一套完整的视觉语言:
- 数字按钮 = 数据输入(中性,稳重)
- 运算符按钮 = 操作选择(暖色,提示)
- 功能按钮 = 辅助控制(低调,轻量)
- 等号按钮 = 执行确认(高亮,强调)
三、实战:计算器
3.1 整体设计
计算器采用纯深色主题(#1a1a2e 深海军蓝背景),从上到下分为三个区域:
- 标题栏(52vp):白色加粗标题"🔢 计算器"
- 显示区(约 120vp):三行信息——历史记录(10号 20%白色)、当前表达式(Body 号 53%白色)、主显示数字(48/36/28 号动态字号 纯白色)
- 键盘区(400vp):4×5 Grid,18 个按钮
3.2 显示区的信息层次
显示区从上到下承载了三层信息:
第一层——历史记录:最近三次的计算结果(如 12 + 5 = 17),10 号字号、20% 不透明度。这些信息"存在但不起眼"——用户需要时可以瞥一眼,但不会干扰当前计算。
第二层——表达式:当前正在输入的表达式(如 12 ×),Body 号(~15sp)、53% 不透明度。比历史记录更可见,但比主显示数字更低调——它在告诉用户"你刚才输入了什么运算符"。
第三层——主显示:当前输入的数字或计算结果,48 号字号(短数字)、纯白色、Light 字重、等宽字体。这是整个页面最突出的信息——用户 90% 的时间都在看它。
三层信息的不透明度梯度(20% → 53% → 100%)与其重要性完美对应。
3.3 动态字号缩放
当用户输入的数字超过 6 位时,48 号字体会溢出显示区域。我们使用了动态字号:
getFontSize(): number {
const len = this.getDisplayText().length;
if (len <= 6) return 48; // 短数字:大字体(如 123456)
if (len <= 9) return 36; // 中等数字:中字体(如 123456789)
return 28; // 长数字:小字体(如 0.123456789)
}
这确保了:
- 1-6 位数字:最大字号 48,清晰醒目
- 7-9 位数字:中字号 36,仍可舒适阅读
- 10+ 位数字:小字号 28,配合截断(
…)防止溢出
等宽字体(monospace)确保每个字符占据相同宽度,不会因为数字切换(如 111111 → 888888)导致宽度变化。
3.4 核心计算逻辑
计算器维护了三个核心状态:
private currentInput: string = '0'; // 当前正在输入的数字
private previousInput: string = ''; // 上一个输入的数字
private operator: string = ''; // 当前运算符
private shouldResetInput: boolean = false; // 是否需要重置输入
数字输入(inputDigit):
inputDigit(digit: string): void {
if (this.shouldResetInput) {
this.currentInput = digit; // 运算符之后 → 替换为新数字
this.shouldResetInput = false;
} else if (digit === '.') {
if (this.hasDecimal) return; // 已经有一位小数 → 忽略
this.currentInput += '.';
} else {
if (this.currentInput === '0') {
this.currentInput = digit; // 替换开头的 "0"
} else {
this.currentInput += digit; // 追加数字
}
}
}
数字输入逻辑处理了四种情况:
- 运算符之后输入数字 → 清空当前显示,开始新数字
- 输入小数点 → 检查是否已有小数点,避免
3.14.5这种非法输入 - 替换开头的 “0” → 避免
0123这种显示 - 正常追加 → 多位数输入(
1→12→123)
运算符输入(inputOperator):
inputOperator(op: string): void {
if (this.operator && this.shouldResetInput) {
this.operator = op; // 连续切换运算符
return;
}
if (this.operator) {
const result = this.compute(); // 链式计算
this.currentInput = result;
}
this.previousInput = this.currentInput;
this.operator = op;
this.shouldResetInput = true;
}
这里处理了链式计算的情况。用户输入 2 + 3 + 4 时:
- 输入
2→currentInput = '2' - 输入
+→previousInput = '2',operator = '+' - 输入
3→currentInput = '3' - 输入
+→ 先计算2 + 3 = 5,再将结果作为previousInput = '5',operator = '+' - 输入
4→currentInput = '4' - 输入
=→ 计算5 + 4 = 9
每次按下运算符时,如果之前已经有待处理的运算,会先执行它。这种"链式计算"的行为与 iOS 和 Android 原生计算器一致。
边界条件处理:
compute(): string {
const a = parseFloat(this.previousInput);
const b = parseFloat(this.currentInput);
if (isNaN(a) || isNaN(b)) return 'Err';
// 除法:除零检测
case '÷': result = b === 0 ? NaN : a / b; break;
}
除零时返回 'Err',而不是让程序崩溃。isFinite(result) 还检测了溢出情况(如极大数除以极小数的溢出)。
3.5 功能按钮
- AC(全部清除):重置所有状态——
currentInput、previousInput、operator、shouldResetInput、hasDecimal全部回到初始值。 - ±(正负号切换):如果当前数字不是
'0',在前面添加或移除-号。 - %(百分比):将当前数字除以 100(
50%变成0.5)。
四、完整代码结构
CalculatorPage
├── Column(根容器)
│ ├── Row(标题栏)
│ ├── Column(显示区)
│ │ ├── Row(历史记录 × 3)
│ │ ├── Row(表达式行)
│ │ └── Row(主显示数字,动态字号)
│ └── Grid(键盘区)
│ └── 18 × GridItem(按钮)
│ ├── func × 3(AC/±/%)
│ ├── num × 11(0-9/.)
│ ├── op × 4(+/-/×/÷)
│ └── eq × 1(=,蓝色发光)
五、总结
本文以计算器为应用场景,深入解析了 ArkUI Grid 网格布局的核心概念和计算器逻辑实现。
回顾本文覆盖的核心要点:
-
Grid 的二维布局能力:
columnsTemplate('1fr 1fr 1fr 1fr')定义等宽四列,rowsTemplate('1fr 1fr 1fr 1fr 1fr')定义等高五行。fr分数单位按比例分配空间,实现了 Row 和 Column 无法达成的二维控制。 -
GridItem 跨列:通过设置 GridItem 的百分比宽度(
'50%'),让"0"按钮占据两列空间。这是一种简单的跨列实现方式。 -
按钮样式分层:四种按钮类型(功能/数字/运算符/等号)各用不同的配色和边框——功能键低调(3%白色背景)、数字键稳重(深蓝灰渐变+微边框)、运算符醒目(橙色边框+文字)、等号突出(蓝色渐变+发光阴影)。这种视觉语言不仅美观,还帮助用户快速定位不同功能的按钮。
-
显示区信息层次:三层信息(历史记录 20% → 表达式 53% → 主显示 100%)的不透明度梯度,让用户仅凭视觉就能感知每层信息的重要程度。越重要的信息越"亮"、越"大"。
-
动态字号:根据数字长度自动调整字号(48/36/28),确保长数字不溢出、短数字足够大。这是移动端计算器的标准做法。
-
链式计算:支持
a + b - c × d =这种连续运算——每次输入新运算符时自动执行前一歩计算。与 iOS/Android 原生计算器行为一致。 -
边界安全:除零检测返回
'Err'、溢出检测(isFinite)、小数点重复检测——这些边界处理让计算器在异常输入下优雅降级,而非崩溃。
Grid 是移动端开发中不可或缺的布局工具。无论是计算器键盘、照片网格、商品货架还是数据仪表盘,Grid 的二维控制力都让它成为比 Row 和 Column 更自然的选择。理解它的模板语法、单元格控制和跨列机制,你就能在合适的场景下自信地选择 Grid,构建出既整齐又灵活的界面。
更多推荐


所有评论(0)