新手入门:用 ArkUI 11 开发一个单词卡 App
背单词是大多数语言学习者的日常,而"单词卡"(Flashcard)是其中最经典的工具形态——正面显示单词,背面显示释义,手指一翻即可切换。本文从零开始,带你用 ArkUI 11(HarmonyOS NEXT 的声明式 UI 框架)构建一个可运行的单词卡应用。
一、你将要构建什么
一个单词卡 App,核心交互:
| 功能 | 说明 |
|---|---|
| 卡片正反面 | 正面显示英文单词,点击翻转显示中文释义 |
| 左右滑动 | 切换上一张 / 下一张单词 |
| 记忆状态标记 | 每张卡片可标记"记住了"或"还需复习" |
| 本地持久化 | 关闭 App 再打开,进度不丢失 |
| 平移动画 | 卡片切换时有平滑的出入动画 |
下面是单词卡App的功能架构图:
二、环境准备
2.1 安装 DevEco Studio
去 HarmonyOS 开发者官网 下载 DevEco Studio NEXT(对应 API 12+,即 ArkUI 11)。安装后打开,在 Settings → SDK Manager 中确保安装了以下 SDK:
HarmonyOS SDK API 12 (or later)
• ArkTS 3.2+
• Native API (可选)
2.2 创建项目
- File → New → Create Project
- 选择模板:Empty Ability(ArkTS)
- 填写项目名:
WordCardApp - Compile SDK:选
API 12 - Finish
创建后的目录结构:
WordCardApp/
├── entry/
│ ├── src/main/
│ │ ├── ets/
│ │ │ ├── pages/
│ │ │ │ └── Index.ets ← 主页面
│ │ │ ├── model/
│ │ │ │ └── WordData.ets ← 数据模型
│ │ │ └── view/
│ │ │ └── WordCard.ets ← 卡片组件
│ │ └── resources/
│ └── oh-package.json5
└── build-profile.json5
下面是项目创建后的完整目录结构图:
三、数据模型层
在 model/WordData.ets 中定义单词的数据结构和内置词库:
// model/WordData.ets
// 单词卡片数据结构
export interface Word {
id: number;
word: string; // 英文单词
phonetic?: string; // 音标(可选)
meaning: string; // 中文释义
example?: string; // 例句(可选)
remembered: boolean; // 是否已记住
}
// 初始词库(可扩展)
export const defaultWords: Word[] = [
{ id: 1, word: 'abandon', phonetic: '/əˈbændən/', meaning: '放弃;遗弃', remembered: false },
{ id: 2, word: 'brilliant', phonetic: '/ˈbrɪliənt/', meaning: '灿烂的;杰出的', remembered: false },
{ id: 3, word: 'collapse', phonetic: '/kəˈlæps/', meaning: '倒塌;崩溃', remembered: false },
{ id: 4, word: 'diverse', phonetic: '/daɪˈvɜːrs/', meaning: '不同的;多样的', remembered: false },
{ id: 5, word: 'elaborate', phonetic: '/ɪˈlæbərət/', meaning: '精心制作的;阐述', remembered: false },
];
四、卡片组件(核心 UI)
ArkUI 11 使用 @Component 装饰器定义可复用的自定义组件。单词卡的核心是两个状态:
- 正面:显示单词 + 音标
- 背面:显示释义 + 例句
翻转效果通过 animateTo + 旋转 rotate 实现。
// view/WordCard.ets
@Component
export struct WordCard {
// 父组件传入的单词数据
private word: Word = { id: 0, word: '', meaning: '', remembered: false };
// 当前是否显示背面
@State isFlipped: boolean = false;
// 翻转动画的角度
@State flipDegree: number = 0;
// 翻转卡片
flip() {
// 如果当前角度 < 90,翻到 180(背面);否则翻回 0(正面)
let target = this.flipDegree < 90 ? 180 : 0;
animateTo({ duration: 400, curve: Curve.EaseInOut }, () => {
this.flipDegree = target;
});
// 动画中途切换内容层(旋转超过 90° 时切换文字)
setTimeout(() => {
this.isFlipped = !this.isFlipped;
}, 200);
}
build() {
Column() {
Stack() {
// === 正面 ===
if (!this.isFlipped) {
Column() {
Text(this.word.word)
.fontSize(32)
.fontWeight(FontWeight.Bold)
.textAlign(TextAlign.Center)
if (this.word.phonetic) {
Text(this.word.phonetic)
.fontSize(16)
.fontColor('#888')
.margin({ top: 8 })
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
// === 背面 ===
if (this.isFlipped) {
Column() {
Text(this.word.meaning)
.fontSize(28)
.fontColor('#2B6CB0')
.fontWeight(FontWeight.Medium)
.textAlign(TextAlign.Center)
if (this.word.example) {
Text(this.word.example)
.fontSize(16)
.fontColor('#666')
.margin({ top: 16 })
.textAlign(TextAlign.Center)
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
.width(300)
.height(200)
.backgroundColor('#FFF')
.borderRadius(16)
.shadow({ radius: 12, color: '#33000000', offsetX: 0, offsetY: 4 })
.rotate({ x: 0, y: 1, z: 0, angle: this.flipDegree })
.onClick(() => { this.flip(); })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
}
}
关键点:使用
rotate({ y: 1 })绕 Y 轴旋转,配合animateTo产生 3D 翻转效果。正反面内容通过if/else条件渲染,在旋转角度越过 90° 时切换,避免用户看到背面文字的镜像。
下面是卡片翻转的状态转换图:
五、主页面(卡片管理与滑动导航)
主页面 Index.ets 负责:
- 维护单词列表和当前索引
- 处理"上一张 / 下一张"滑动
- 标记"记住了" / “还需复习”
- 持久化进度
5.1 持久化工具函数
ArkUI 11 推荐使用 @ohos.data.preferences 做轻量级 KV 存储:
// 在 Index.ets 顶部引入
import { preferences } from '@kit.ArkData';
const KEY_WORDS = 'word_list';
async function saveWords(context: Context, words: Word[]): Promise<void> {
let store = await preferences.getPreferences(context, 'wordcard_db');
await store.put(KEY_WORDS, JSON.stringify(words));
await store.flush();
}
async function loadWords(context: Context): Promise<Word[] | null> {
let store = await preferences.getPreferences(context, 'wordcard_db');
let json = await store.get(KEY_WORDS, '');
return json ? JSON.parse(json) as Word[] : null;
}
5.2 主页面完整代码
// pages/Index.ets
import { Word, defaultWords } from '../model/WordData';
import { WordCard } from '../view/WordCard';
import { preferences } from '@kit.ArkData';
@Entry
@Component
struct Index {
@State wordList: Word[] = [];
@State currentIndex: number = 0;
// 滑动偏移量(用于手势跟踪)
@State offsetX: number = 0;
aboutToAppear() {
this.loadData();
}
async loadData() {
let saved = await loadWords(getContext(this));
this.wordList = saved ?? defaultWords;
}
async saveData() {
await saveWords(getContext(this), this.wordList);
}
// 切换到上一张
prevCard() {
if (this.currentIndex > 0) {
animateTo({ duration: 250 }, () => {
this.offsetX = 0; // 重置位移
this.currentIndex--;
});
}
}
// 切换到下一张
nextCard() {
if (this.currentIndex < this.wordList.length - 1) {
animateTo({ duration: 250 }, () => {
this.offsetX = 0;
this.currentIndex++;
});
}
}
// 切换记忆状态
toggleRemembered() {
let idx = this.currentIndex;
this.wordList[idx].remembered = !this.wordList[idx].remembered;
this.wordList = [...this.wordList]; // 触发 @State 刷新
this.saveData();
}
build() {
Column() {
// === 顶部进度条 ===
Text(`${this.currentIndex + 1} / ${this.wordList.length}`)
.fontSize(14)
.fontColor('#999')
.margin({ top: 24 });
// === 进度指示条 ===
Progress({
value: this.currentIndex + 1,
total: this.wordList.length,
type: ProgressType.Linear
})
.width('70%')
.height(6)
.color('#4A90D9')
.margin({ top: 8, bottom: 24 });
// === 单词卡片(带手势滑动) ===
if (this.wordList.length > 0) {
Stack() {
// 当前卡片(带拖拽手势)
WordCard({ word: this.wordList[this.currentIndex] })
.offset({ x: this.offsetX })
.gesture(
GestureGroup(GestureMode.Sequence,
PanGesture({ distance: 30 })
.onActionUpdate((event: GestureEvent) => {
this.offsetX = event.offsetX;
})
.onActionEnd(() => {
if (this.offsetX > 80) {
this.prevCard();
} else if (this.offsetX < -80) {
this.nextCard();
} else {
// 回弹
animateTo({ duration: 200 }, () => {
this.offsetX = 0;
});
}
})
)
)
}
.width('100%')
.height(240)
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
// === 底部按钮区 ===
Row() {
// 上一张
Button({ type: ButtonType.Circle, stateEffect: true }) {
Text('◀').fontSize(24).fontColor('#FFF')
}
.width(52).height(52)
.backgroundColor('#E2E8F0')
.onClick(() => { this.prevCard(); })
// 标记"记住了" / "还需复习"
Button({ type: ButtonType.Circle, stateEffect: true }) {
Text(this.wordList[this.currentIndex]?.remembered ? '✓' : '○')
.fontSize(24)
.fontColor('#FFF')
}
.width(64).height(64)
.backgroundColor(
this.wordList[this.currentIndex]?.remembered ? '#48BB78' : '#A0AEC0'
)
.margin({ left: 32, right: 32 })
.onClick(() => { this.toggleRemembered(); })
// 下一张
Button({ type: ButtonType.Circle, stateEffect: true }) {
Text('▶').fontSize(24).fontColor('#FFF')
}
.width(52).height(52)
.backgroundColor('#E2E8F0')
.onClick(() => { this.nextCard(); })
}
.margin({ bottom: 48 })
.justifyContent(FlexAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('#F7FAFC')
}
}
下面是应用的数据流和交互流程图:
六、运行效果
在 DevEco Studio 中点击 Run(选择模拟器或真机),你将看到:
- 一张白色卡片浮在浅灰背景中央,显示第一个单词
abandon - 点击卡片 —— 卡片沿 Y 轴翻转,显示中文释义"放弃;遗弃"
- 左右拖动卡片 —— 跟随手指位移;超过 80px 松手即切换到上一张/下一张,不足则回弹
- 底部圆形按钮 —— ◀ 上一张 / ▶ 下一张 / ○ 标记记住(变绿 ✓)
- 关闭 App 再打开 —— 进度和记忆状态依然保留
七、扩展思路
完成了基础版,你可以继续添加:
| 功能 | 实现方向 |
|---|---|
| 添加单词 | 弹窗输入框 + this.wordList.push() + 持久化 |
| 分组词库 | 用 @ohos.data.relationalStore(关系型数据库)建立词库表 |
| 学习统计 | @State 统计今日学习数、复习数,用 Chart 组件展示 |
| 语音朗读 | 调用 @ohos.multimedia.media 的 createAudioPlayer + TTS 服务 |
| 暗黑模式 | 使用 @Styles 定义两套主题色,通过 @LocalStorageProp 切换 |
| 云同步 | 接入 @kit.NetworkKit 的 HTTP 请求,同步到远端服务器 |
八、小结
通过这个项目,你实践了 ArkUI 11 的以下核心概念:
@Component/@Entry—— 声明式组件定义@State/ 数据驱动 UI —— 状态变化自动触发视图更新animateTo+rotate—— 声明式 3D 翻转动画PanGesture+GestureGroup—— 手势识别与响应链preferences—— 轻量级本地 KV 持久化Column/Row/Stack/Text/Button/Progress—— 常用布局和组件
单词卡 App 虽小,但"状态管理 → 手势交互 → 动画反馈 → 数据持久化"这条链路覆盖了大部分 ArkUI 应用开发的骨架。由此出发,你可以构建更复杂的笔记、题库、闪卡学习等应用。
Happy coding on HarmonyOS NEXT! 🚀
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)