Flutter for OpenHarmony 第三方库实战:使用 animated_text_kit 构建英语单词动态记忆卡片应用
在学习类应用中,文字展示非常重要。比如单词记忆、口语练习、每日一句、课程提示、学习打卡等页面,核心内容往往都是文字。如果所有文字都静态显示,页面虽然能用,但缺少一点吸引力。尤其是学习类应用,本来就容易让人犯困,再配上死板页面,效果就像让人对着说明书冥想,实在是很考验人类意志力。因此本文选择使用 Flutter 第三方库来实现动态文字效果。它可以快速实现打字机文字、淡入文字、旋转文字和彩色文字动画,
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
项目效果
本文实现的是一个基于 Flutter for OpenHarmony 的英语单词动态记忆卡片应用。项目中使用 Flutter 第三方库 animated_text_kit 实现动态文字效果,让英语单词、学习提示和记忆短句以打字机动画、淡入动画和彩色文字动画的形式展示。
最终运行效果如下:

页面主要包含以下内容:
- 顶部标题栏;
- 动态学习标题;
- 当前单词记忆卡片;
- 单词释义和例句;
- 动态学习提示语;
- 单词掌握状态列表;
- 学习统计信息;
- 第三方库使用说明;
- 页面整体采用 Flutter Material 风格布局。
本文重点是演示如何在 Flutter for OpenHarmony 项目中使用 Flutter 第三方库 animated_text_kit。项目代码写在 lib/main.dart 中,依赖配置写在 pubspec.yaml 中,符合 Flutter for OpenHarmony 第三方库实践方向。
前言
在学习类应用中,文字展示非常重要。比如单词记忆、口语练习、每日一句、课程提示、学习打卡等页面,核心内容往往都是文字。
如果所有文字都静态显示,页面虽然能用,但缺少一点吸引力。尤其是学习类应用,本来就容易让人犯困,再配上死板页面,效果就像让人对着说明书冥想,实在是很考验人类意志力。
因此本文选择使用 Flutter 第三方库 animated_text_kit 来实现动态文字效果。它可以快速实现打字机文字、淡入文字、旋转文字和彩色文字动画,适合用于标题、标语、提示语和重点内容展示。
本项目以“英语单词动态记忆卡片应用”为例,使用 animated_text_kit 展示当前学习单词、每日学习提示和动态标题,并结合 Flutter 状态更新实现单词切换和掌握状态标记。
一、项目目标
本次实践主要实现以下目标:
- 创建 Flutter for OpenHarmony 项目;
- 在
pubspec.yaml中添加第三方库animated_text_kit; - 使用
flutter pub get获取依赖; - 在
lib/main.dart中引入animated_text_kit; - 使用
AnimatedTextKit构建动态文字区域; - 使用
TypewriterAnimatedText实现打字机文字效果; - 使用
FadeAnimatedText实现学习提示淡入效果; - 使用
ColorizeAnimatedText实现彩色标题动画; - 实现单词切换功能;
- 实现单词掌握状态标记;
- 使用 Flutter Material 组件构建完整页面;
- 将应用运行到 OpenHarmony 设备或模拟器中。
二、技术栈
| 类型 | 内容 |
|---|---|
| 开发方向 | Flutter for OpenHarmony |
| 开发语言 | Dart |
| UI 框架 | Flutter |
| 第三方库 | animated_text_kit |
| 功能场景 | 动态文字 / 英语单词 / 学习卡片 |
| 核心组件 | AnimatedTextKit / TypewriterAnimatedText / FadeAnimatedText / ColorizeAnimatedText |
| 项目入口 | lib/main.dart |
| 依赖配置 | pubspec.yaml |
| 运行平台 | OpenHarmony 设备或模拟器 |
三、为什么选择 animated_text_kit
在实际开发中,动态文字可以用于很多场景,例如:
- 英语单词记忆;
- 每日一句;
- 学习打卡;
- 首页欢迎语;
- 活动标题;
- 成就提示;
- 应用引导页;
- 课程宣传页;
- 重点信息展示。
如果完全自己使用 Flutter 原生动画实现文字逐字显示,需要处理字符串拆分、定时器、动画控制器、文字刷新和动画生命周期。能写,但为了让一句话慢慢出现就手搓一堆动画逻辑,多少有点像为了喝水先研究水利工程。
animated_text_kit 已经封装好了常见文字动画组件,可以让开发者更快完成动态文字页面。
在本项目中,animated_text_kit 主要完成以下工作:
- 展示彩色动态标题;
- 展示打字机单词动画;
- 展示淡入学习提示;
- 增强学习页面的视觉表现;
- 提升用户对重点文字的注意力。
四、创建 Flutter for OpenHarmony 项目
在已经配置好 Flutter for OpenHarmony 开发环境的前提下,可以创建一个 Flutter 项目。
示例项目名称:
flutter create vocabulary_animate_demo
进入项目目录:
cd vocabulary_animate_demo
项目创建完成后,主要关注两个文件:
vocabulary_animate_demo
├── pubspec.yaml
└── lib
└── main.dart
其中:
| 文件 | 作用 |
|---|---|
| pubspec.yaml | 配置 Flutter 项目依赖 |
| lib/main.dart | 编写 Flutter 页面和业务逻辑 |
五、添加 animated_text_kit 第三方库
打开项目根目录下的 pubspec.yaml 文件,在 dependencies 中添加 animated_text_kit。
示例配置如下:
dependencies:
flutter:
sdk: flutter
animated_text_kit: ^4.3.0
完整结构大致如下:
name: vocabulary_animate_demo
description: A Flutter for OpenHarmony animated text demo.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.4.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
animated_text_kit: ^4.3.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
添加完成后,在终端执行:
flutter pub get
执行成功后,就可以在 Dart 代码中使用 animated_text_kit 了。
六、项目结构
本项目主要修改 lib/main.dart 文件:
lib
└── main.dart
本项目不需要编写 OpenHarmony 原生 ArkTS 页面,也不需要修改 Index.ets。
因为这是 Flutter for OpenHarmony 项目,页面主体应该是 Flutter 代码。审核重点会看:
- 是否使用
pubspec.yaml添加 Flutter 第三方库; - 是否在 Dart 文件中
import package; - 是否在
lib/main.dart中实际调用第三方库; - 是否属于 Flutter for OpenHarmony 项目。
看到 pubspec.yaml、lib/main.dart、import 'package:animated_text_kit/animated_text_kit.dart';,这才是正确方向。别把 ArkTS 代码换个标题就叫 Flutter,审核员虽然痛苦,但不至于失明。
七、核心实现思路
本项目的核心流程如下:
- 在
pubspec.yaml中添加animated_text_kit; - 在
main.dart中引入第三方库; - 定义英语单词数据模型;
- 使用
AnimatedTextKit展示动态标题; - 使用
ColorizeAnimatedText实现彩色标题效果; - 使用
TypewriterAnimatedText展示当前单词; - 使用
FadeAnimatedText展示学习提示语; - 使用按钮切换上一个和下一个单词;
- 使用
setState()更新当前单词和掌握状态。
第三方库引入代码如下:
import 'package:animated_text_kit/animated_text_kit.dart';
打字机动画核心代码如下:
AnimatedTextKit(
key: ValueKey(currentWord.word),
animatedTexts: [
TypewriterAnimatedText(
currentWord.word,
speed: const Duration(milliseconds: 90),
),
],
totalRepeatCount: 1,
)
彩色标题动画核心代码如下:
AnimatedTextKit(
animatedTexts: [
ColorizeAnimatedText(
'Vocabulary Boost',
colors: colorizeColors,
textStyle: textStyle,
),
],
repeatForever: true,
)
淡入提示动画核心代码如下:
AnimatedTextKit(
animatedTexts: [
FadeAnimatedText('Read it. Speak it. Use it.'),
],
repeatForever: true,
)
这几段代码是本文的重点,说明项目确实使用了 Flutter 第三方库实现动态文字效果。
八、main.dart 完整代码
打开文件:
lib/main.dart
将其中内容替换为下面代码:
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const VocabularyAnimateApp());
}
class VocabularyAnimateApp extends StatelessWidget {
const VocabularyAnimateApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Vocabulary Animate Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.light,
),
useMaterial3: true,
),
home: const VocabularyHomePage(),
);
}
}
class VocabularyItem {
VocabularyItem({
required this.word,
required this.phonetic,
required this.meaning,
required this.example,
required this.tip,
required this.icon,
required this.color,
this.mastered = false,
});
final String word;
final String phonetic;
final String meaning;
final String example;
final String tip;
final IconData icon;
final Color color;
bool mastered;
}
class VocabularyHomePage extends StatefulWidget {
const VocabularyHomePage({super.key});
State<VocabularyHomePage> createState() => _VocabularyHomePageState();
}
class _VocabularyHomePageState extends State<VocabularyHomePage> {
final List<Color> _colorizeColors = const [
Colors.deepPurple,
Colors.blue,
Colors.teal,
Colors.orange,
];
final List<VocabularyItem> _words = [
VocabularyItem(
word: 'efficient',
phonetic: '/ɪˈfɪʃnt/',
meaning: 'adj. 高效的;有效率的',
example: 'A clear plan makes study more efficient.',
tip: '常用于描述方法、系统、工具或工作方式很高效。',
icon: Icons.speed,
color: Colors.deepPurple,
mastered: true,
),
VocabularyItem(
word: 'progress',
phonetic: '/ˈprəʊɡres/',
meaning: 'n. 进步;进展',
example: 'Small daily progress is still progress.',
tip: '可以用于学习、项目、任务等各种进展场景。',
icon: Icons.trending_up,
color: Colors.blue,
),
VocabularyItem(
word: 'focus',
phonetic: '/ˈfəʊkəs/',
meaning: 'v. 集中注意力;n. 焦点',
example: 'Focus on one task before starting another.',
tip: '学习和工作场景中非常常用,注意搭配 focus on。',
icon: Icons.center_focus_strong,
color: Colors.teal,
),
VocabularyItem(
word: 'review',
phonetic: '/rɪˈvjuː/',
meaning: 'v. 复习;回顾;n. 评价',
example: 'Review your notes before the exam.',
tip: '既可以表示复习知识,也可以表示评价产品或文章。',
icon: Icons.rate_review,
color: Colors.orange,
),
VocabularyItem(
word: 'improve',
phonetic: '/ɪmˈpruːv/',
meaning: 'v. 提高;改善',
example: 'Speaking every day can improve your English.',
tip: '后面可以直接接能力、成绩、方法或状态。',
icon: Icons.upgrade,
color: Colors.green,
),
];
int _currentIndex = 0;
VocabularyItem get _currentWord {
return _words[_currentIndex];
}
int get _masteredCount {
return _words.where((word) => word.mastered).length;
}
int get _unmasteredCount {
return _words.length - _masteredCount;
}
double get _masteredProgress {
if (_words.isEmpty) {
return 0;
}
return _masteredCount / _words.length;
}
void _goPrevious() {
setState(() {
if (_currentIndex == 0) {
_currentIndex = _words.length - 1;
} else {
_currentIndex--;
}
});
}
void _goNext() {
setState(() {
if (_currentIndex == _words.length - 1) {
_currentIndex = 0;
} else {
_currentIndex++;
}
});
}
void _toggleMastered() {
setState(() {
_currentWord.mastered = !_currentWord.mastered;
});
}
void _selectWord(int index) {
setState(() {
_currentIndex = index;
});
}
void _resetWords() {
setState(() {
for (final VocabularyItem word in _words) {
word.mastered = false;
}
_currentIndex = 0;
});
}
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final VocabularyItem currentWord = _currentWord;
return Scaffold(
appBar: AppBar(
title: const Text('英语单词动态记忆'),
centerTitle: true,
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildHeaderCard(theme),
const SizedBox(height: 16),
_buildWordCard(theme, currentWord),
const SizedBox(height: 16),
_buildActionCard(theme),
const SizedBox(height: 16),
_buildWordListCard(theme),
const SizedBox(height: 16),
_buildLibraryCard(theme),
],
),
),
);
}
Widget _buildHeaderCard(ThemeData theme) {
final TextStyle colorizeTextStyle =
theme.textTheme.headlineSmall!.copyWith(
fontWeight: FontWeight.bold,
);
return Card(
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(22),
),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
width: 76,
height: 76,
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(24),
),
child: Icon(
Icons.auto_stories,
size: 42,
color: theme.colorScheme.onPrimaryContainer,
),
),
const SizedBox(height: 18),
AnimatedTextKit(
animatedTexts: [
ColorizeAnimatedText(
'Vocabulary Boost',
textStyle: colorizeTextStyle,
colors: _colorizeColors,
speed: const Duration(milliseconds: 450),
),
],
repeatForever: true,
pause: const Duration(milliseconds: 600),
),
const SizedBox(height: 10),
AnimatedTextKit(
animatedTexts: [
FadeAnimatedText(
'Read it. Speak it. Use it.',
textStyle: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w600,
),
duration: const Duration(milliseconds: 1800),
),
FadeAnimatedText(
'每天记一点,别指望大脑自动开窍。',
textStyle: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w600,
),
duration: const Duration(milliseconds: 1800),
),
],
repeatForever: true,
pause: const Duration(milliseconds: 600),
),
const SizedBox(height: 22),
Row(
children: [
_buildStatItem(
theme,
title: '单词总数',
value: '${_words.length}',
icon: Icons.menu_book,
),
_buildStatItem(
theme,
title: '已掌握',
value: '$_masteredCount',
icon: Icons.check_circle,
),
_buildStatItem(
theme,
title: '待复习',
value: '$_unmasteredCount',
icon: Icons.pending_actions,
),
],
),
const SizedBox(height: 18),
LinearProgressIndicator(
value: _masteredProgress,
minHeight: 10,
borderRadius: BorderRadius.circular(10),
),
const SizedBox(height: 8),
Align(
alignment: Alignment.centerRight,
child: Text(
'掌握进度 ${(100 * _masteredProgress).toStringAsFixed(0)}%',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}
Widget _buildStatItem(
ThemeData theme, {
required String title,
required String value,
required IconData icon,
}) {
return Expanded(
child: Column(
children: [
Icon(
icon,
color: theme.colorScheme.primary,
),
const SizedBox(height: 6),
Text(
value,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
const SizedBox(height: 2),
Text(
title,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
);
}
Widget _buildWordCard(ThemeData theme, VocabularyItem word) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(22),
child: Column(
children: [
Container(
width: 72,
height: 72,
decoration: BoxDecoration(
color: word.color.withOpacity(0.16),
borderRadius: BorderRadius.circular(24),
),
child: Icon(
word.icon,
size: 38,
color: word.color,
),
),
const SizedBox(height: 20),
SizedBox(
height: 48,
child: Center(
child: AnimatedTextKit(
key: ValueKey(word.word),
animatedTexts: [
TypewriterAnimatedText(
word.word,
textStyle: theme.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: word.color,
),
speed: const Duration(milliseconds: 90),
),
],
totalRepeatCount: 1,
displayFullTextOnTap: true,
stopPauseOnTap: true,
),
),
),
const SizedBox(height: 6),
Text(
word.phonetic,
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 18),
_buildInfoBlock(
theme,
title: '中文释义',
content: word.meaning,
icon: Icons.translate,
color: word.color,
),
const SizedBox(height: 12),
_buildInfoBlock(
theme,
title: '例句',
content: word.example,
icon: Icons.format_quote,
color: word.color,
),
const SizedBox(height: 12),
_buildInfoBlock(
theme,
title: '记忆提示',
content: word.tip,
icon: Icons.lightbulb,
color: word.color,
),
const SizedBox(height: 18),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 8,
),
decoration: BoxDecoration(
color: word.mastered
? Colors.green.withOpacity(0.14)
: Colors.orange.withOpacity(0.14),
borderRadius: BorderRadius.circular(20),
),
child: Text(
word.mastered ? '当前状态:已掌握' : '当前状态:待复习',
style: theme.textTheme.bodyMedium?.copyWith(
color: word.mastered ? Colors.green : Colors.orange,
fontWeight: FontWeight.bold,
),
),
),
],
),
),
);
}
Widget _buildInfoBlock(
ThemeData theme, {
required String title,
required String content,
required IconData icon,
required Color color,
}) {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: color.withOpacity(0.10),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: color.withOpacity(0.22),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
icon,
color: color,
size: 24,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.bodySmall?.copyWith(
color: color,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 6),
Text(
content,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
height: 1.5,
),
),
],
),
),
],
),
);
}
Widget _buildActionCard(ThemeData theme) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
Expanded(
child: OutlinedButton.icon(
onPressed: _goPrevious,
icon: const Icon(Icons.arrow_back),
label: const Text('上一个'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: _goNext,
icon: const Icon(Icons.arrow_forward),
label: const Text('下一个'),
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _toggleMastered,
icon: Icon(
_currentWord.mastered
? Icons.undo
: Icons.check_circle,
),
label: Text(
_currentWord.mastered ? '取消掌握' : '标记掌握',
),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: _resetWords,
icon: const Icon(Icons.refresh),
label: const Text('重置状态'),
),
),
],
),
],
),
),
);
}
Widget _buildWordListCard(ThemeData theme) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'单词列表',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
...List.generate(_words.length, (index) {
final VocabularyItem word = _words[index];
final bool selected = index == _currentIndex;
return Container(
margin: const EdgeInsets.only(bottom: 10),
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () {
_selectWord(index);
},
child: Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: selected
? word.color.withOpacity(0.14)
: theme.colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: selected ? word.color : Colors.transparent,
width: 1.2,
),
),
child: Row(
children: [
Icon(
word.icon,
color: word.color,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
word.word,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
word.meaning,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
Icon(
word.mastered
? Icons.check_circle
: Icons.radio_button_unchecked,
color: word.mastered
? Colors.green
: theme.colorScheme.onSurfaceVariant,
),
],
),
),
),
);
}),
],
),
),
);
}
Widget _buildLibraryCard(ThemeData theme) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'第三方库说明',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 12),
_buildInfoRow(
theme,
title: '库名称',
value: 'animated_text_kit',
),
_buildInfoRow(
theme,
title: '配置文件',
value: 'pubspec.yaml',
),
_buildInfoRow(
theme,
title: '导入方式',
value: "import 'package:animated_text_kit/animated_text_kit.dart';",
),
_buildInfoRow(
theme,
title: '核心组件',
value:
'AnimatedTextKit / TypewriterAnimatedText / FadeAnimatedText / ColorizeAnimatedText',
),
_buildInfoRow(
theme,
title: '应用场景',
value: '动态标题、单词记忆、每日一句、学习提示、活动标语',
),
],
),
),
);
}
Widget _buildInfoRow(
ThemeData theme, {
required String title,
required String value,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 82,
child: Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text(
value,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
);
}
}
九、代码实现说明
1. 引入 animated_text_kit 第三方库
代码开头引入第三方库:
import 'package:animated_text_kit/animated_text_kit.dart';
这说明项目确实使用了 Flutter 第三方库,而不是 OpenHarmony 原生库。
本项目中主要使用以下组件:
AnimatedTextKit
TypewriterAnimatedText
FadeAnimatedText
ColorizeAnimatedText
其中:
| 组件 | 作用 |
|---|---|
| AnimatedTextKit | 文字动画容器 |
| TypewriterAnimatedText | 打字机文字动画 |
| FadeAnimatedText | 淡入淡出文字动画 |
| ColorizeAnimatedText | 彩色渐变文字动画 |
2. 定义英语单词数据模型
项目中定义了单词模型:
class VocabularyItem {
VocabularyItem({
required this.word,
required this.phonetic,
required this.meaning,
required this.example,
required this.tip,
required this.icon,
required this.color,
this.mastered = false,
});
final String word;
final String phonetic;
final String meaning;
final String example;
final String tip;
final IconData icon;
final Color color;
bool mastered;
}
字段说明如下:
| 字段 | 作用 |
|---|---|
| word | 英语单词 |
| phonetic | 音标 |
| meaning | 中文释义 |
| example | 英文例句 |
| tip | 记忆提示 |
| icon | 单词图标 |
| color | 单词主题色 |
| mastered | 是否已经掌握 |
其中 mastered 是可变化状态,用来记录当前单词是否已经掌握。
3. 使用 AnimatedTextKit 作为动画容器
所有文字动画都放在 AnimatedTextKit 中,例如:
AnimatedTextKit(
animatedTexts: [
TypewriterAnimatedText('efficient'),
],
)
AnimatedTextKit 本身负责播放动画,里面的 animatedTexts 决定播放哪一种文字动画。
4. 使用 TypewriterAnimatedText 实现打字机效果
当前单词使用打字机动画展示:
AnimatedTextKit(
key: ValueKey(word.word),
animatedTexts: [
TypewriterAnimatedText(
word.word,
speed: const Duration(milliseconds: 90),
),
],
totalRepeatCount: 1,
)
其中:
| 参数 | 作用 |
|---|---|
| key | 单词变化时重新播放动画 |
| animatedTexts | 动画文字列表 |
| speed | 每个字符出现速度 |
| totalRepeatCount | 动画播放次数 |
这里使用:
key: ValueKey(word.word)
是为了让切换单词时,打字机动画可以重新播放。
如果不加 key,有时页面更新了但动画不重新开始。Flutter 不是魔法,它只按规则重建,别指望它理解人类“我想再播一次”的复杂心情。
5. 使用 FadeAnimatedText 展示学习提示
页面顶部使用淡入动画展示学习提示:
AnimatedTextKit(
animatedTexts: [
FadeAnimatedText('Read it. Speak it. Use it.'),
FadeAnimatedText('每天记一点,别指望大脑自动开窍。'),
],
repeatForever: true,
)
这里设置:
repeatForever: true
表示提示文字会循环播放。
适合用于学习提醒、标语、欢迎语等场景。
6. 使用 ColorizeAnimatedText 展示彩色标题
页面标题使用彩色文字动画:
ColorizeAnimatedText(
'Vocabulary Boost',
textStyle: colorizeTextStyle,
colors: _colorizeColors,
)
颜色列表如下:
final List<Color> _colorizeColors = const [
Colors.deepPurple,
Colors.blue,
Colors.teal,
Colors.orange,
];
ColorizeAnimatedText 会让文字颜色动态变化,使标题更有视觉重点。
7. 实现单词切换功能
上一个单词:
void _goPrevious() {
setState(() {
if (_currentIndex == 0) {
_currentIndex = _words.length - 1;
} else {
_currentIndex--;
}
});
}
下一个单词:
void _goNext() {
setState(() {
if (_currentIndex == _words.length - 1) {
_currentIndex = 0;
} else {
_currentIndex++;
}
});
}
这里做了循环切换。到第一个单词时继续点“上一个”,会跳到最后一个单词;到最后一个单词时继续点“下一个”,会回到第一个单词。
8. 实现掌握状态标记
点击“标记掌握”按钮后执行:
void _toggleMastered() {
setState(() {
_currentWord.mastered = !_currentWord.mastered;
});
}
如果当前单词未掌握,会标记为已掌握。
如果当前单词已经掌握,会取消掌握状态。
页面中的统计数据、进度条和单词列表状态都会跟着更新。
9. 计算学习进度
已掌握数量:
int get _masteredCount {
return _words.where((word) => word.mastered).length;
}
掌握进度:
double get _masteredProgress {
if (_words.isEmpty) {
return 0;
}
return _masteredCount / _words.length;
}
进度条展示:
LinearProgressIndicator(
value: _masteredProgress,
)
这样可以直观看到当前单词掌握情况。
10. 单词列表点击切换
单词列表中点击某一项后执行:
void _selectWord(int index) {
setState(() {
_currentIndex = index;
});
}
这样用户不仅可以通过“上一个”“下一个”切换单词,也可以直接点击列表中的单词进行查看。
十、运行项目
完成代码后,在终端执行:
flutter pub get
然后连接 OpenHarmony 设备或启动 OpenHarmony 模拟器。
查看设备:
flutter devices
运行项目:
flutter run
如果环境配置正确,应用会运行到 OpenHarmony 设备或模拟器中。
运行成功后,页面会显示“英语单词动态记忆”。用户可以点击“上一个”“下一个”切换单词,也可以点击“标记掌握”更新学习状态。页面中的标题、提示语和当前单词会通过 animated_text_kit 展示动态文字效果。
十一、开发中遇到的问题
1. animated_text_kit 依赖没有生效
如果代码中出现找不到 animated_text_kit 的问题,可以检查 pubspec.yaml 中是否添加了:
animated_text_kit: ^4.3.0
然后重新执行:
flutter pub get
如果还是不行,可以重启编辑器。编辑器有时像刚从午睡里醒来,依赖明明装了,它还一脸茫然,真让人安心。
2. import 导入报错
如果下面代码报错:
import 'package:animated_text_kit/animated_text_kit.dart';
通常有几种原因:
pubspec.yaml中没有添加依赖;- 没有执行
flutter pub get; - YAML 缩进错误;
- 包名写错;
- 编辑器没有刷新依赖。
其中 YAML 缩进最容易出问题。依赖必须写在 dependencies 下面,并且缩进要正确。一个空格就能毁掉项目运行,编程世界真是体贴得令人窒息。
3. 文字动画没有播放
如果文字动画没有播放,可以检查:
- 是否使用了
AnimatedTextKit; animatedTexts中是否添加了动画文字;- 是否设置了有效的
textStyle; - 动画组件是否被其他组件遮挡;
- 页面是否成功运行。
基础结构应该类似:
AnimatedTextKit(
animatedTexts: [
TypewriterAnimatedText('Hello'),
],
)
4. 切换单词后动画没有重新播放
如果切换单词后,打字机动画没有重新播放,可以给 AnimatedTextKit 添加 key:
key: ValueKey(word.word)
这样当前单词变化时,Flutter 会把它识别为新的动画组件,从而重新播放动画。
5. 彩色文字不显示
如果 ColorizeAnimatedText 没有正常显示,可以检查是否设置了:
textStyle
colors
例如:
ColorizeAnimatedText(
'Vocabulary Boost',
textStyle: colorizeTextStyle,
colors: _colorizeColors,
)
颜色列表至少要有多个颜色,否则彩色变化效果不明显。只给一个颜色还期待它渐变,那属于对颜色提出了不合理劳动要求。
6. 中文和英文混排显示不整齐
如果中文和英文混排显示效果不好,可以调整:
- 字体大小;
- 行高;
- 文本对齐方式;
- 卡片宽度;
- 外边距和内边距。
本项目中主要通过 height: 1.5 改善多行文字可读性。
7. 点击按钮后状态没有变化
如果点击“标记掌握”后页面没有变化,可以检查是否调用了:
setState(() {
_currentWord.mastered = !_currentWord.mastered;
});
Flutter 中状态变化后必须调用 setState(),否则页面不会刷新。它只是框架,不是读心术机器。
8. 运行不到 OpenHarmony 设备
如果项目无法运行到 OpenHarmony 设备或模拟器,可以检查:
- Flutter for OpenHarmony 环境是否配置完成;
- 设备是否连接成功;
flutter devices是否能识别设备;- 是否执行了
flutter pub get; - 是否选择了正确的运行设备;
- 项目是否为 Flutter 项目,而不是原生鸿蒙项目。
如果 flutter devices 都识别不到设备,那应该先处理环境问题,而不是盯着文字动画代码反复怀疑人生。动画很无辜,至少这次大概率是。
十二、本文和原生鸿蒙项目的区别
本文是 Flutter for OpenHarmony 第三方库实践,不是 OpenHarmony 原生 ArkTS 项目。
主要区别如下:
| 对比项 | 本文写法 | 原生鸿蒙写法 |
|---|---|---|
| UI 技术 | Flutter | ArkUI |
| 主要语言 | Dart | ArkTS |
| 页面入口 | lib/main.dart | Index.ets |
| 依赖配置 | pubspec.yaml | oh-package.json5 |
| 依赖安装 | flutter pub get | ohpm install |
| 第三方库 | animated_text_kit | OpenHarmony 原生库 |
| 页面组件 | MaterialApp / Scaffold / AnimatedTextKit | @Entry / @Component |
因此本文符合 Flutter for OpenHarmony 第三方库实践方向。
十三、总结
本篇完成了一个基于 animated_text_kit 的 Flutter for OpenHarmony 英语单词动态记忆卡片应用。项目通过 Flutter 第三方库实现动态文字效果,并结合单词切换、掌握状态标记和学习进度展示构建了一个完整的学习类页面。
通过本次实践,我主要完成了以下内容:
- 创建 Flutter for OpenHarmony 项目;
- 在
pubspec.yaml中添加animated_text_kit依赖; - 使用
flutter pub get获取第三方库; - 在
lib/main.dart中引入animated_text_kit; - 使用
AnimatedTextKit构建动态文字区域; - 使用
TypewriterAnimatedText实现打字机单词动画; - 使用
FadeAnimatedText实现学习提示动画; - 使用
ColorizeAnimatedText实现彩色标题动画; - 使用
setState()实现单词切换和状态更新; - 使用 Flutter Material 组件构建完整页面;
- 将项目运行到 OpenHarmony 设备或模拟器中。
这个项目虽然只是一个基础单词记忆应用,但完整展示了 Flutter for OpenHarmony 项目中第三方库的使用流程。
后续可以在这个基础上继续扩展,例如:
- 添加更多单词数据;
- 添加单词搜索功能;
- 添加错题本;
- 添加本地数据保存;
- 添加每日学习目标;
- 添加发音音频;
- 添加随机测试;
- 添加学习打卡;
- 添加暗色主题;
- 添加云端同步。
整体来看,animated_text_kit 可以帮助 Flutter 开发者快速实现动态文字展示。通过这个项目,可以理解 Flutter for OpenHarmony 中第三方库依赖配置、动态文字组件使用和页面状态更新之间的基本关系。
更多推荐
所有评论(0)