🚀 新手零基础学ArkUI12—— 手把手教你开发汇率转换器App

汇率转换器 是移动应用中最经典、也是最适合新手的入门案例。通过开发这个应用,你将学会:

技术点 说明
@State 装饰器 ArkUI 最核心的状态管理,让数据变化自动刷新 UI
TextInput 组件 用户输入金额,理解输入绑定与类型控制
Select 下拉选择器 货币选择,掌握列表数据绑定
@Builder 方法 组件化思维,把重复 UI 抽成独立构建函数
事件处理 onChangeonClickonSelect 三大高频事件
动画基础 animation 属性实现数值变化动效
数据持久化 使用数组管理转换历史记录
深色模式 条件样式切换,为进阶主题打下基础

⚙️ 运行环境要求

硬件要求

项目 要求
操作系统 Windows 10/11 或 macOS 12+
内存 推荐 8GB 以上
磁盘空间 至少 10GB 可用空间
开发者设备 自备 HarmonyOS 手机或使用模拟器

软件要求

  1. DevEco Studio(必需)

  2. Node.js(必需,用于构建工具链)

  3. Ohpm(OpenHarmony 包管理器)

    • 随 DevEco Studio 自动安装,无需单独配置

环境配置步骤

👉 第一步:安装 DevEco Studio
   - 双击下载的 exe 安装包,一路默认下一步即可
   - 首次启动会提示安装 SDK,保持网络畅通等待下载

👉 第二步:创建项目
   - 启动 DevEco Studio → Create Project
   - 选择 "Empty Ability" 模板(API 9+)
   - Project Name 填:CurrencyConverter
   - 点击 Finish 等待 Gradle 同步完成

👉 第三步:找到页面文件
   - 项目结构导航到:entry/src/main/ets/pages/Index.ets
   - 这就是我们将要编辑的主页面

⚠️ 新手提示: 不同版本的 DevEco Studio 界面可能有细微差异,但核心操作一致。如果遇到同步失败,检查网络能否访问 repo.harmonyos.com


🧩 实战小案例:汇率转换器

📁 项目结构总览

在开始写代码前,我们先看看最终要完成的文件结构:

CurrencyConverter/
├── entry/
│   ├── src/main/ets/
│   │   ├── pages/
│   │   │   └── Index.ets          ← 主页面(我们改它)
│   │   ├── Application/
│   │   │   └── MyAbilityStage.ts
│   │   └── MainAbility/
│   │       └── MainAbility.ts
│   └── resources/
│       ├── base/
│       │   ├── element/
│       │   │   └── string.json
│       │   └── profile/
│       │       └── main_pages.json
│       └── rawfile/
├── AppScope/
│   └── app.json5
└── build-profile.json5

⚠️ 新手避坑: 初学者最容易犯的错误是——试图从头开始创建所有文件。实际上,你只需要修改 Index.ets 这一个文件! 其他文件由 DevEco Studio 自动生成。


🧠 第一步:理解 ArkUI 的声明式语法

在开始写代码前,必须搞懂 ArkUI 最核心的编程思想:声明式 UI

传统方式(命令式) vs ArkUI(声明式)
特性 传统命令式(Java Android) ArkUI 声明式
写法 textView.setText("你好") Text("你好")
刷新 手动调用 updateView() 自动响应状态变化
代码量 较多(findViewById 等模板代码) 极简
学习曲线 陡峭 快速上手

核心公式: UI = f(state)
—— 界面由状态决定,状态变了,界面自动更新。

关键装饰器速查
@Entry       // 标记这是一个入口页面
@Component   // 标记这是一个自定义组件
@State       // 状态变量 → 变了 UI 自动刷新
@Builder     // 抽取重复 UI 片段
@Prop        // 父传子的数据(后面文章详解)

💡 最佳实践: 刚开始学习时,先把 @State 理解为"会动的变量"。普通变量改了界面不变,@State 变量改了界面跟着变。


🧱 第二步:定义数据模型

打开 Index.ets,首先在最上方定义数据类型。这相当于给数据"建个档案",让代码更规范。

// ===== 货币数据类型 =====
interface Currency {
  code: string;       // 货币代码:CNY、USD、EUR...
  name: string;       // 中文名称
  symbol: string;     // 货币符号:¥、$、€
  rateToCNY: number;  // 兑人民币的汇率
  flag: string;       // 国旗 emoji
}

// ===== 转换记录数据类型 =====
interface ConversionRecord {
  id: number;
  fromAmount: number;
  fromCurrency: string;
  toAmount: number;
  toCurrency: string;
  date: string;
  rate: number;
}

🤔 为什么要有 Interface?

  • 让代码有"类型提示"——写错了 DevEco Studio 会报红
  • 团队协作时一目了然
  • 未来维护时快速理解数据结构

⚠️ 避坑指南: TypeScript 的 Interface 只存在于编译阶段,运行时不存在。这和 Java/Kotlin 的数据类不同,但学习阶段先记住"用来约束数据结构"就够了。


💰 第三步:准备汇率数据

Interface 后面,定义一组常用的货币数据:

const CURRENCIES: Currency[] = [
  { code: 'CNY', name: '人民币',  symbol: '¥', rateToCNY: 1,      flag: '🇨🇳' },
  { code: 'USD', name: '美元',    symbol: '$', rateToCNY: 7.24,  flag: '🇺🇸' },
  { code: 'EUR', name: '欧元',    symbol: '€', rateToCNY: 7.87,  flag: '🇪🇺' },
  { code: 'GBP', name: '英镑',    symbol: '£', rateToCNY: 9.15,  flag: '🇬🇧' },
  { code: 'JPY', name: '日元',    symbol: '¥', rateToCNY: 0.048, flag: '🇯🇵' },
  { code: 'KRW', name: '韩元',    symbol: '₩', rateToCNY: 0.0053,flag: '🇰🇷' },
  { code: 'HKD', name: '港币',    symbol: 'HK$',rateToCNY: 0.93, flag: '🇭🇰' },
  { code: 'SGD', name: '新加坡元',symbol: 'S$', rateToCNY: 5.38, flag: '🇸🇬' },
];

💡 设计思考:

  • 以人民币为锚定,所有汇率都是"1 单位外币 = 多少人民币"
  • 这样任意两种货币转换只需一次除法:目标币/源币
  • 汇率硬编码是为了教学,真实项目应该网络请求

🏗️ 第四步:搭建页面骨架

这是整个应用的"骨架",定义了页面的整体布局结构:

@Entry
@Component
struct CurrencyConverter {
  // ===== 状态变量 =====
  @State fromAmount: string = '1.00';     // 输入金额
  @State toAmount: string = '';           // 转换结果
  @State fromIndex: number = 0;           // 源货币(默认CNY)
  @State toIndex: number = 1;             // 目标货币(默认USD)
  @State showHistory: boolean = false;    // 历史面板开关
  @State history: ConversionRecord[] = [];// 历史记录
  @State isDarkMode: boolean = false;     // 暗黑模式

  build() {
    Column() {
      // 顶部标题栏
      this.TitleBar()
      // 主转换区域(带渐变背景)
      this.ConverterCard()
      // 当前汇率展示
      this.RateDisplay()
      // 操作按钮(转换 + 保存)
      this.ActionButtons()
      // 历史记录
      if (this.showHistory) {
        this.HistorySection()
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F7FAFC')
  }
}

📐 布局讲解:

组件 作用 相当于网页的
Column 垂直排列子组件 flex-direction: column
Row 水平排列子组件 flex-direction: row
Stack 层叠放置子组件 position: absolute
Blank 占据剩余空间(弹性布局) flex: 1

💡 新手口诀: Column 竖着排,Row 横着排,多层套一起,布局不会歪。


🎨 第五步:用 @Builder 拆分组件

@Builder 是 ArkUI 最实用的特性之一。它让你把重复的 UI 代码"打包"成独立方法。

标题栏
@Builder
TitleBar() {
  Row() {
    Text('💱 汇率转换器')
      .fontSize(20)
      .fontWeight(FontWeight.Bold)

    Blank()  // ← 自动填充剩余空间,实现左右对齐

    Text(isDarkMode ? '☀️' : '🌙')
      .fontSize(22)
      .onClick(() => { this.isDarkMode = !this.isDarkMode; })

    Text('📋')
      .fontSize(22)
      .margin({ left: 12 })
      .onClick(() => { this.showHistory = !this.showHistory; })
  }
  .width('100%')
  .padding({ left: 20, right: 20, top: 12 })
}

🔑 关键理解: Blank() 是弹性布局的"弹簧",占据剩余空间。左侧是标题,右侧是两个图标按钮,Blank 在中间把它们"弹"到两端。

输入转换区域

核心的转换卡片包含三部分:

  1. 源货币 — 用户输入金额 + 选择货币种类
  2. 互换按钮 — 点击交换源/目标货币
  3. 目标货币 — 显示转换结果
@Builder
ConverterCard() {
  Stack({ alignContent: Alignment.TopStart }) {
    // 渐变背景
    Column()
      .width('100%')
      .height(280)
      .borderRadius(24)
      .backgroundColor('#667EEA')
      .shadow({ radius: 16, color: '#40667EEA', offsetY: 8 })

    Column() {
      this.CurrencyInputRow('兑换', ...)
      this.SwapButton()
      this.CurrencyResultRow()
    }
    .width('100%')
    .padding(20)
  }
  .width('100%')
  .padding({ left: 16, right: 16, top: 8 })
}

📝 第六步:核心转换逻辑

这个函数是整个App的灵魂:

// 计算汇率(交叉汇率法)
get currentRate(): number {
  return CURRENCIES[toIndex].rateToCNY / CURRENCIES[fromIndex].rateToCNY;
}

// 执行转换
convert(): void {
  const amountNum = parseFloat(this.fromAmount);
  if (isNaN(amountNum) || amountNum <= 0) {
    this.toAmount = '请输入有效金额';
    return;
  }
  const result = amountNum * this.currentRate;
  this.toAmount = result.toFixed(4);
}

🧮 数学原理:

  • 我们有 CNY→USD = 7.24CNY→EUR = 7.87
  • 要算 USD→EUR7.87 ÷ 7.24 = 1.087
  • 即 1 美元 ≈ 1.087 欧元
  • 这叫交叉汇率计算法,国际外汇市场也是这样算的!

📋 第七步:管理历史记录

历史记录是展示 ArkUI 列表操作的经典案例:

saveToHistory(): void {
  const record: ConversionRecord = {
    id: Date.now(),                                // 唯一标识
    fromAmount: parseFloat(this.fromAmount),
    fromCurrency: CURRENCIES[fromIndex].code,
    toAmount: parseFloat(this.toAmount),
    toCurrency: CURRENCIES[toIndex].code,
    date: new Date().toLocaleString('zh-CN'),      // 中文时间
    rate: this.currentRate
  };
  this.history = [record, ...this.history];  // 新记录插到最前面
}

🔄 不可变更新模式:

在 ArkUI 中,不要用 this.history.push(record)
要用 this.history = [record, ...this.history] 创建新数组。
原因:@State 通过引用变化来检测更新,push 不会改变数组引用。

列表渲染
List({ space: 8 }) {
  ForEach(this.history, (item: ConversionRecord) => {
    ListItem() {
      // 每一项的 UI
      Row() { /* ... 展示记录详情 ... */ }
        .padding(12)
        .backgroundColor('#FFFFFF')
        .borderRadius(12)
    }
  }, (item: ConversionRecord) => item.id.toString())
}

🔑 ForEach 的第二个参数(键值函数) 是性能关键!
给每个列表项一个唯一 key,列表更新时 ArkUI 就知道哪项变了、哪项是新增的。不传的话,每次刷新都会重新渲染所有项。


✨ 第八步:让 App 更精致 —— 动画与暗黑模式

数值变化动画
Text(this.toAmount || '0.0000')
  .animation({
    duration: 400,
    curve: Curve.FastOutSlowIn
  })

就这一行,每次 toAmount 变化时数字会平滑过渡。这是 ArkUI 最令人惊喜的特性——给属性加 .animation() 就有了原生级动效。

暗黑模式切换

所有文字和背景颜色都通过 isDarkMode 条件控制:

.backgroundColor(this.isDarkMode ? '#1A202C' : '#F7FAFC')
.fontColor(this.isDarkMode ? '#E2E8F0' : '#2D3748')

💡 最佳实践: 真正的大型项目会用 ThemeProvider 全局管理,但小项目用条件判断就足够了。


🚀 完整运行效果

在这里插入图片描述
在这里插入图片描述

🧠 技术深度总结

你学会了什么?

知识点 掌握程度 下一步学习方向
@State 装饰器 ✅ 基础 @Prop@Link 父子通信
TextInput 组件 ✅ 基础 TextAreaSearch 变体
@Builder 方法 ✅ 会用 带参数 @Builder、全局 @Builder
ForEach 列表 ✅ 基础 LazyForEach 大数据优化
条件渲染 ✅ 基础 if/elseswitch 高级用法
事件处理 ✅ 基础 手势系统(点击、滑动、捏合)

避坑指南 🚫

错误写法 正确写法
修改数组 this.history.push(item) this.history = [...this.history, item]
修改对象属性 obj.name = '新值' this.obj = { ...obj, name: '新值' }
TextInput 类型 忘记写 .type(InputType.Number) 加上显式键盘类型
数字精度 result * 100 / 100 result.toFixed(4) + parseFloat
路由跳转 直接在 @Entry 文件写 router 先 import router 模块

最佳实践 ✅

  1. 颜色用变量管理:定义 const Colors = { primary: '#667EEA', ... } 方便统一改主题
  2. 数值格式化先校验parseFloat + isNaN 双重保险
  3. @Builder 拆分粒度:一个 Builder 不超过 30 行,超过就拆子组件
  4. 善用注释分区:用 // ===== xxx ===== 分隔代码块,提升可读性
  5. 先搭建骨架再写细节:先 build() 里搭好 Column/Row 结构,再填充内容和样式

🔮 扩展练习(挑战自己)

既然你完成了基础版,试试这些进阶任务:

  1. 🌐 接入真实汇率 API
    http.request 从免费汇率 API 获取实时数据,代替硬编码

  2. 💾 数据持久化
    PreferencesLocalStorage 保存历史记录,App 重启不丢失

  3. 📊 图表展示
    Canvas 绘制汇率走势折线图

  4. 🔔 汇率提醒
    设置某个汇率达到阈值时发送通知


📚 参考资料

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐