Flutter for OpenHarmony 第三方库实战:使用 animated_text_kit 构建学习计划动态标语应用
欢迎加入开源鸿蒙跨平台社区:
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 原生动画实现打字机效果、淡入淡出效果、彩色文字动画,就需要处理动画控制器、字符显示进度、动画循环、文本切换等逻辑。能写,但为了几个字去手搓动画控制器,多少有点像为了吃个苹果先种一棵树。
因此本文选择使用 Flutter 第三方库 animated_text_kit 来实现文字动画。它提供了多种常见文字动画组件,可以快速实现打字机、淡入淡出、彩色渐变、波浪文字等效果,非常适合 Flutter for OpenHarmony 项目中的首页标题、提示语和任务说明场景。
本项目以“学习计划动态标语应用”为例,使用 animated_text_kit 展示动态学习标语,并结合任务统计、任务列表、任务筛选和当前任务详情完成一个完整页面。
一、项目目标
本次实践主要实现以下目标:
- 创建 Flutter for OpenHarmony 项目;
- 在
pubspec.yaml中添加第三方库animated_text_kit; - 使用
flutter pub get获取依赖; - 在
lib/main.dart中引入animated_text_kit; - 使用
AnimatedTextKit构建动态文字区域; - 使用
TypewriterAnimatedText实现打字机文字效果; - 使用
FadeAnimatedText实现淡入淡出文字效果; - 使用
ColorizeAnimatedText实现彩色文字动画; - 使用
WavyAnimatedText实现波浪文字效果; - 使用
TyperAnimatedText实现逐字显示提示; - 实现学习任务勾选和统计;
- 实现未完成任务筛选;
- 使用 Flutter Material 组件构建完整页面;
- 将应用运行到 OpenHarmony 设备或模拟器中。
二、技术栈
| 类型 | 内容 |
|---|---|
| 开发方向 | Flutter for OpenHarmony |
| 开发语言 | Dart |
| UI 框架 | Flutter |
| 第三方库 | animated_text_kit |
| 功能场景 | 动态文字 / 学习计划 / 任务提醒 |
| 核心组件 | AnimatedTextKit / TypewriterAnimatedText / ColorizeAnimatedText |
| 项目入口 | lib/main.dart |
| 依赖配置 | pubspec.yaml |
| 运行平台 | OpenHarmony 设备或模拟器 |
三、为什么选择 animated_text_kit
在实际开发中,动态文字可以用于很多场景,例如:
- 应用启动页;
- 首页欢迎语;
- 学习打卡页面;
- 活动宣传页;
- 搜索空状态提示;
- 任务提醒页面;
- 课程推荐标题;
- 数据加载提示;
- 用户引导页面;
- 个人主页标语。
Flutter 原生可以通过 AnimationController 实现文字动画,但代码量相对较大,而且不同类型动画需要分别处理。
animated_text_kit 已经封装好了常见文字动画效果,可以直接通过 AnimatedTextKit 组合不同动画文字。
在本项目中,animated_text_kit 主要完成以下工作:
- 展示首页动态标语;
- 实现打字机文字效果;
- 实现淡入淡出提示语;
- 实现彩色渐变标题;
- 实现波浪文字提示;
- 实现任务详情中的动态状态提示;
- 让学习计划页面更有动态效果;
- 减少手写动画代码量。
四、创建 Flutter for OpenHarmony 项目
在已经配置好 Flutter for OpenHarmony 开发环境的前提下,可以创建一个 Flutter 项目。
示例项目名称:
flutter create study_text_demo
进入项目目录:
cd study_text_demo
项目创建完成后,主要关注两个文件:
study_text_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: study_text_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';,这才是正确方向。不是写一堆普通 Text 组件就能骗过审核,审核虽然累,但还没完全失明。
七、核心实现思路
本项目的核心流程如下:
- 在
pubspec.yaml中添加animated_text_kit; - 在
main.dart中引入第三方库; - 定义学习任务数据模型;
- 准备多条学习任务数据;
- 使用
AnimatedTextKit构建动态标题; - 使用
TypewriterAnimatedText实现打字机效果; - 使用
FadeAnimatedText实现提示语切换; - 使用
ColorizeAnimatedText实现彩色文字动画; - 使用
WavyAnimatedText实现空状态提示; - 使用
TyperAnimatedText实现任务详情提示; - 使用
setState()更新任务完成状态; - 使用筛选开关展示未完成任务;
- 使用 Flutter Material 组件构建完整页面。
第三方库引入代码如下:
import 'package:animated_text_kit/animated_text_kit.dart';
动态文字核心代码如下:
AnimatedTextKit(
repeatForever: true,
pause: const Duration(milliseconds: 900),
displayFullTextOnTap: true,
stopPauseOnTap: true,
animatedTexts: [
TypewriterAnimatedText(
'今天先完成一个小目标',
textStyle: titleStyle,
speed: const Duration(milliseconds: 80),
),
FadeAnimatedText(
'学习不靠玄学,靠重复',
textStyle: titleStyle,
),
ColorizeAnimatedText(
'Flutter for OpenHarmony',
textStyle: colorizeTextStyle,
colors: colorizeColors,
),
],
)
这段代码是本文重点,说明项目确实使用了 Flutter 第三方库实现动态文字效果。
八、main.dart 完整代码
打开文件:
lib/main.dart
将其中内容替换为下面代码:
import 'package:animated_text_kit/animated_text_kit.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const StudyTextApp());
}
class StudyTextApp extends StatelessWidget {
const StudyTextApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Study Text Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.light,
),
useMaterial3: true,
),
home: const StudyHomePage(),
);
}
}
class StudyTask {
StudyTask({
required this.id,
required this.title,
required this.subject,
required this.description,
required this.minutes,
required this.icon,
required this.color,
this.done = false,
});
final int id;
final String title;
final String subject;
final String description;
final int minutes;
final IconData icon;
final Color color;
bool done;
}
class StudyHomePage extends StatefulWidget {
const StudyHomePage({super.key});
State<StudyHomePage> createState() => _StudyHomePageState();
}
class _StudyHomePageState extends State<StudyHomePage> {
static const List<Color> _colorizeColors = [
Colors.indigo,
Colors.purple,
Colors.blue,
Colors.teal,
];
static const TextStyle _colorizeTextStyle = TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
);
bool _showOnlyTodo = false;
int _selectedTaskId = 1;
int _textSeed = 0;
final List<StudyTask> _tasks = [
StudyTask(
id: 1,
title: '复习 Dart 基础语法',
subject: 'Flutter',
description: '整理变量、函数、类、List、Map 的基本用法,重点看自己容易忘的地方。',
minutes: 35,
icon: Icons.code,
color: Colors.indigo,
done: true,
),
StudyTask(
id: 2,
title: '完成一个页面小练习',
subject: 'OpenHarmony',
description: '把今天的 Flutter 第三方库示例运行起来,并截图放到文章开头。',
minutes: 45,
icon: Icons.phone_iphone,
color: Colors.blue,
),
StudyTask(
id: 3,
title: '整理英文表达',
subject: 'English',
description: '记录 5 个今天能用到的英文句子,不要只背单词,句子才更容易拿来开口。',
minutes: 20,
icon: Icons.translate,
color: Colors.green,
),
StudyTask(
id: 4,
title: '阅读技术文章',
subject: 'Reading',
description: '阅读一篇 Flutter 相关技术文章,记录第三方库名称、作用和核心组件。',
minutes: 25,
icon: Icons.menu_book,
color: Colors.orange,
),
StudyTask(
id: 5,
title: '检查项目运行效果',
subject: 'Debug',
description: '运行项目后检查页面是否能正常显示,确认截图中能看出第三方库效果。',
minutes: 30,
icon: Icons.bug_report,
color: Colors.redAccent,
),
];
List<StudyTask> get _visibleTasks {
if (_showOnlyTodo) {
return _tasks.where((task) => !task.done).toList();
}
return _tasks;
}
StudyTask get _selectedTask {
return _tasks.firstWhere(
(task) {
return task.id == _selectedTaskId;
},
orElse: () {
return _tasks.first;
},
);
}
int get _doneCount {
return _tasks.where((task) => task.done).length;
}
int get _todoCount {
return _tasks.length - _doneCount;
}
int get _totalMinutes {
int total = 0;
for (final StudyTask task in _tasks) {
total += task.minutes;
}
return total;
}
double get _progressValue {
if (_tasks.isEmpty) {
return 0;
}
return _doneCount / _tasks.length;
}
void _toggleTask(StudyTask task) {
setState(() {
task.done = !task.done;
_selectedTaskId = task.id;
});
}
void _selectTask(StudyTask task) {
setState(() {
_selectedTaskId = task.id;
});
}
void _toggleFilter(bool value) {
setState(() {
_showOnlyTodo = value;
final List<StudyTask> visibleTasks = _visibleTasks;
if (visibleTasks.isNotEmpty) {
_selectedTaskId = visibleTasks.first.id;
}
});
}
void _restartTextAnimation() {
setState(() {
_textSeed++;
});
ScaffoldMessenger.of(context)
..clearSnackBars()
..showSnackBar(
const SnackBar(
content: Text('动态文字已重新播放'),
behavior: SnackBarBehavior.floating,
duration: Duration(milliseconds: 1200),
),
);
}
void _finishSelectedTask() {
setState(() {
_selectedTask.done = true;
});
}
String get _progressText {
if (_doneCount == _tasks.length) {
return '今日任务已全部完成';
}
if (_doneCount == 0) {
return '还没开始,先动第一步';
}
return '已完成 $_doneCount 项,还剩 $_todoCount 项';
}
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final List<StudyTask> visibleTasks = _visibleTasks;
return Scaffold(
appBar: AppBar(
title: const Text('学习计划动态标语'),
centerTitle: true,
),
body: SafeArea(
child: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildAnimatedHeader(theme),
const SizedBox(height: 16),
_buildOverviewCard(theme),
const SizedBox(height: 16),
_buildFilterCard(theme),
const SizedBox(height: 16),
_buildTaskListCard(theme, visibleTasks),
const SizedBox(height: 16),
_buildSelectedTaskCard(theme),
const SizedBox(height: 16),
_buildActionCard(theme),
const SizedBox(height: 16),
_buildLibraryCard(theme),
],
),
),
);
}
Widget _buildAnimatedHeader(ThemeData theme) {
final TextStyle titleStyle = theme.textTheme.titleLarge!.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
);
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_awesome,
size: 42,
color: theme.colorScheme.onPrimaryContainer,
),
),
const SizedBox(height: 18),
SizedBox(
height: 64,
child: Center(
child: AnimatedTextKit(
key: ValueKey(_textSeed),
repeatForever: true,
pause: const Duration(milliseconds: 900),
displayFullTextOnTap: true,
stopPauseOnTap: true,
animatedTexts: [
TypewriterAnimatedText(
'今天先完成一个小目标',
textStyle: titleStyle,
speed: const Duration(milliseconds: 80),
textAlign: TextAlign.center,
),
FadeAnimatedText(
'学习不靠玄学,靠重复',
textStyle: titleStyle,
textAlign: TextAlign.center,
),
ColorizeAnimatedText(
'Flutter for OpenHarmony',
textStyle: _colorizeTextStyle,
colors: _colorizeColors,
textAlign: TextAlign.center,
),
],
),
),
),
const SizedBox(height: 10),
Text(
'使用 animated_text_kit 构建动态文字学习计划页面',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
height: 1.5,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
Widget _buildOverviewCard(ThemeData theme) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
Icon(
Icons.track_changes,
color: theme.colorScheme.primary,
),
const SizedBox(width: 10),
Expanded(
child: Text(
'今日学习进度',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 16),
LinearProgressIndicator(
value: _progressValue,
minHeight: 9,
borderRadius: BorderRadius.circular(9),
),
const SizedBox(height: 12),
Text(
_progressText,
style: theme.textTheme.titleMedium?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 18),
Row(
children: [
_buildStatItem(
theme,
title: '总任务',
value: '${_tasks.length}',
icon: Icons.list_alt,
),
_buildStatItem(
theme,
title: '已完成',
value: '$_doneCount',
icon: Icons.done_all,
),
_buildStatItem(
theme,
title: '未完成',
value: '$_todoCount',
icon: Icons.pending_actions,
),
_buildStatItem(
theme,
title: '预计',
value: '$_totalMinutes分',
icon: Icons.timer,
),
],
),
],
),
),
);
}
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.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
title,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
);
}
Widget _buildFilterCard(ThemeData theme) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: SwitchListTile(
value: _showOnlyTodo,
onChanged: _toggleFilter,
secondary: Icon(
Icons.filter_alt,
color: theme.colorScheme.primary,
),
title: const Text('只显示未完成任务'),
subtitle: const Text('用于快速查看今天还需要继续处理的内容'),
),
);
}
Widget _buildTaskListCard(
ThemeData theme,
List<StudyTask> visibleTasks,
) {
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'今日学习任务',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Text(
'显示 ${visibleTasks.length} 项',
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 14),
if (visibleTasks.isEmpty)
Padding(
padding: const EdgeInsets.all(18),
child: Center(
child: AnimatedTextKit(
repeatForever: true,
animatedTexts: [
WavyAnimatedText(
'没有未完成任务',
textStyle: theme.textTheme.titleMedium!.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
),
],
),
),
)
else
...visibleTasks.map((task) {
return _buildTaskItem(theme, task);
}),
],
),
),
);
}
Widget _buildTaskItem(ThemeData theme, StudyTask task) {
final bool selected = task.id == _selectedTaskId;
return GestureDetector(
onTap: () {
_selectTask(task);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 220),
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: selected
? task.color.withOpacity(0.16)
: task.color.withOpacity(0.08),
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: selected
? task.color.withOpacity(0.70)
: task.color.withOpacity(0.20),
width: selected ? 2 : 1,
),
),
child: Row(
children: [
Container(
width: 54,
height: 54,
decoration: BoxDecoration(
color: task.color.withOpacity(0.18),
borderRadius: BorderRadius.circular(18),
),
child: Icon(
task.icon,
color: task.color,
size: 29,
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
task.title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
decoration:
task.done ? TextDecoration.lineThrough : null,
),
),
const SizedBox(height: 5),
Text(
'${task.subject} · ${task.minutes} 分钟',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
Text(
task.description,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
height: 1.45,
),
),
],
),
),
const SizedBox(width: 8),
Checkbox(
value: task.done,
onChanged: (_) {
_toggleTask(task);
},
),
],
),
),
);
}
Widget _buildSelectedTaskCard(ThemeData theme) {
final StudyTask task = _selectedTask;
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
Icon(
task.icon,
color: task.color,
),
const SizedBox(width: 10),
Expanded(
child: Text(
'当前选中任务',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 18),
SizedBox(
height: 38,
child: Center(
child: AnimatedTextKit(
key: ValueKey('${task.id}-${task.done}'),
isRepeatingAnimation: false,
animatedTexts: [
TyperAnimatedText(
task.done ? '这个任务已经完成' : '这个任务还需要继续推进',
textStyle: theme.textTheme.titleMedium!.copyWith(
color: task.color,
fontWeight: FontWeight.bold,
),
speed: const Duration(milliseconds: 70),
),
],
),
),
),
const SizedBox(height: 12),
_buildDetailRow(
theme,
title: '任务',
value: task.title,
color: task.color,
),
_buildDetailRow(
theme,
title: '分类',
value: task.subject,
color: task.color,
),
_buildDetailRow(
theme,
title: '状态',
value: task.done ? '已完成' : '未完成',
color: task.color,
),
_buildDetailRow(
theme,
title: '说明',
value: task.description,
color: task.color,
),
],
),
),
);
}
Widget _buildDetailRow(
ThemeData theme, {
required String title,
required String value,
required Color color,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 7,
height: 7,
margin: const EdgeInsets.only(top: 8),
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
),
const SizedBox(width: 10),
SizedBox(
width: 58,
child: Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: Text(
value,
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: Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _finishSelectedTask,
icon: const Icon(Icons.check_circle),
label: const Text('完成当前'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: _restartTextAnimation,
icon: const Icon(Icons.replay),
label: const Text('重播文字'),
),
),
],
),
),
);
}
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),
_buildLibraryInfoRow(
theme,
title: '库名称',
value: 'animated_text_kit',
),
_buildLibraryInfoRow(
theme,
title: '配置文件',
value: 'pubspec.yaml',
),
_buildLibraryInfoRow(
theme,
title: '导入方式',
value:
"import 'package:animated_text_kit/animated_text_kit.dart';",
),
_buildLibraryInfoRow(
theme,
title: '核心组件',
value:
'AnimatedTextKit / TypewriterAnimatedText / ColorizeAnimatedText',
),
_buildLibraryInfoRow(
theme,
title: '核心能力',
value: '打字机文字、淡入淡出文字、彩色文字、波浪文字',
),
_buildLibraryInfoRow(
theme,
title: '应用场景',
value: '启动页、首页标语、学习提醒、任务提示、活动标题',
),
],
),
),
);
}
Widget _buildLibraryInfoRow(
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,
height: 1.5,
),
),
),
],
),
);
}
}
九、代码实现说明
1. 引入 animated_text_kit 第三方库
代码开头引入第三方库:
import 'package:animated_text_kit/animated_text_kit.dart';
这说明项目确实使用了 Flutter 第三方库,而不是 OpenHarmony 原生库。
本项目中主要使用以下组件:
AnimatedTextKit
TypewriterAnimatedText
FadeAnimatedText
ColorizeAnimatedText
WavyAnimatedText
TyperAnimatedText
其中:
| 组件 | 作用 |
|---|---|
| AnimatedTextKit | 文字动画容器 |
| TypewriterAnimatedText | 打字机文字动画 |
| FadeAnimatedText | 淡入淡出文字动画 |
| ColorizeAnimatedText | 彩色渐变文字动画 |
| WavyAnimatedText | 波浪文字动画 |
| TyperAnimatedText | 逐字显示文字动画 |
2. 使用 AnimatedTextKit 构建动态标题
页面顶部动态标题代码如下:
AnimatedTextKit(
repeatForever: true,
pause: const Duration(milliseconds: 900),
displayFullTextOnTap: true,
stopPauseOnTap: true,
animatedTexts: [
TypewriterAnimatedText(...),
FadeAnimatedText(...),
ColorizeAnimatedText(...),
],
)
参数说明如下:
| 参数 | 作用 |
|---|---|
| repeatForever | 是否一直循环播放 |
| pause | 每段文字之间的停顿时间 |
| displayFullTextOnTap | 点击时直接显示完整文字 |
| stopPauseOnTap | 点击时跳过暂停 |
| animatedTexts | 需要播放的文字动画列表 |
这部分是项目中第三方库使用最明显的位置。
3. 使用 TypewriterAnimatedText 实现打字机效果
打字机文字代码如下:
TypewriterAnimatedText(
'今天先完成一个小目标',
textStyle: titleStyle,
speed: const Duration(milliseconds: 80),
)
其中:
| 参数 | 作用 |
|---|---|
| 文本内容 | 要显示的文字 |
| textStyle | 文字样式 |
| speed | 每个字符显示速度 |
这个效果适合用于欢迎语、学习提醒和提示语。
4. 使用 FadeAnimatedText 实现淡入淡出效果
淡入淡出文字代码如下:
FadeAnimatedText(
'学习不靠玄学,靠重复',
textStyle: titleStyle,
textAlign: TextAlign.center,
)
FadeAnimatedText 可以让文字以淡入淡出的方式切换,适合用于页面提示语或者状态说明。
这种效果比普通文字更有提示感,也不会像弹窗一样打扰用户。终于有一种 UI 效果不是靠吓人来获得注意力了。
5. 使用 ColorizeAnimatedText 实现彩色文字效果
彩色文字代码如下:
ColorizeAnimatedText(
'Flutter for OpenHarmony',
textStyle: _colorizeTextStyle,
colors: _colorizeColors,
)
颜色列表如下:
static const List<Color> _colorizeColors = [
Colors.indigo,
Colors.purple,
Colors.blue,
Colors.teal,
];
ColorizeAnimatedText 会让文字在多个颜色之间变化,适合用于页面标题和重点标语。
6. 使用 WavyAnimatedText 实现空状态提示
当没有未完成任务时,页面会显示波浪文字:
WavyAnimatedText(
'没有未完成任务',
textStyle: theme.textTheme.titleMedium!.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
),
)
这个效果用于空状态提示,比普通静态文字更明显。终于不是一行冷冰冰的“暂无数据”了,UI 也该有点尊严。
7. 使用 TyperAnimatedText 实现任务详情提示
任务详情区域使用了 TyperAnimatedText:
TyperAnimatedText(
task.done ? '这个任务已经完成' : '这个任务还需要继续推进',
textStyle: theme.textTheme.titleMedium!.copyWith(
color: task.color,
fontWeight: FontWeight.bold,
),
speed: const Duration(milliseconds: 70),
)
当用户点击不同任务或者修改任务状态时,详情区域会显示对应的动态提示。
如果任务已经完成,显示“这个任务已经完成”。
如果任务未完成,显示“这个任务还需要继续推进”。
这样可以让任务状态更加直观。
8. 使用任务模型管理数据
项目中定义了学习任务模型:
class StudyTask {
StudyTask({
required this.id,
required this.title,
required this.subject,
required this.description,
required this.minutes,
required this.icon,
required this.color,
this.done = false,
});
final int id;
final String title;
final String subject;
final String description;
final int minutes;
final IconData icon;
final Color color;
bool done;
}
字段说明如下:
| 字段 | 作用 |
|---|---|
| id | 任务编号 |
| title | 任务标题 |
| subject | 学习分类 |
| description | 任务说明 |
| minutes | 预计耗时 |
| icon | 任务图标 |
| color | 任务主题色 |
| done | 是否完成 |
其中 done 是可变化状态,用于实现任务勾选。
9. 实现任务完成状态切换
任务完成状态切换方法如下:
void _toggleTask(StudyTask task) {
setState(() {
task.done = !task.done;
_selectedTaskId = task.id;
});
}
点击任务右侧的复选框时,会切换当前任务的完成状态。
页面中的进度条、已完成数量、未完成数量和任务详情都会同步更新。
10. 实现未完成任务筛选
筛选逻辑如下:
List<StudyTask> get _visibleTasks {
if (_showOnlyTodo) {
return _tasks.where((task) => !task.done).toList();
}
return _tasks;
}
如果打开“只显示未完成任务”,页面只展示 done == false 的任务。
如果关闭筛选,则展示全部任务。
11. 实现当前任务详情
点击任务列表中的某一项时,会执行:
void _selectTask(StudyTask task) {
setState(() {
_selectedTaskId = task.id;
});
}
当前选中任务详情会展示:
- 任务标题;
- 学习分类;
- 完成状态;
- 任务说明;
- 动态文字提示。
详情区域也使用了 TyperAnimatedText,用于显示当前任务状态提示。
12. 重新播放动态文字
项目中使用 _textSeed 控制动态文字重新构建:
int _textSeed = 0;
点击“重播文字”按钮时:
void _restartTextAnimation() {
setState(() {
_textSeed++;
});
}
顶部 AnimatedTextKit 使用:
key: ValueKey(_textSeed),
当 key 改变时,动态文字组件会重新构建,从而重新播放动画。
十、运行项目
完成代码后,在终端执行:
flutter pub get
然后连接 OpenHarmony 设备或启动 OpenHarmony 模拟器。
查看设备:
flutter devices
运行项目:
flutter run
如果环境配置正确,应用会运行到 OpenHarmony 设备或模拟器中。
运行成功后,页面会显示“学习计划动态标语”。用户可以看到打字机标题、彩色文字动画、任务列表、学习进度统计和当前任务详情。
十一、开发中遇到的问题
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是否设置了合适颜色;- 外层是否给了足够高度;
- 项目是否成功运行。
本项目中使用了:
SizedBox(
height: 64,
child: Center(
child: AnimatedTextKit(...),
),
)
这样可以避免动态文字区域高度不足导致显示异常。
4. ColorizeAnimatedText 报错
ColorizeAnimatedText 需要提供颜色列表:
colors: _colorizeColors
颜色列表至少要有两个颜色。如果只给一个颜色,就失去了彩色变化的意义,组件也可能无法正常表现。
5. 动态文字切换后没有重新播放
如果希望动态文字重新播放,可以给 AnimatedTextKit 设置一个会变化的 key:
key: ValueKey(_textSeed)
当 _textSeed 改变时,组件会重新构建,动画也会重新开始。
6. 任务勾选后页面没有变化
如果点击复选框后页面没有刷新,需要检查状态修改是否放在 setState() 中:
setState(() {
task.done = !task.done;
});
Flutter 页面不会自己猜数据变化。它是框架,不是占卜摊。
7. WavyAnimatedText 没有触发
如果筛选后没有看到 WavyAnimatedText,可以先把所有任务都勾选完成,再打开“只显示未完成任务”。
当未完成任务列表为空时,页面才会进入空状态:
if (visibleTasks.isEmpty)
这时才会显示:
WavyAnimatedText('没有未完成任务')
所以不是组件失效,只是触发条件还没满足。条件判断这种东西很冷酷,它不会因为你期待它显示就显示。
8. 运行不到 OpenHarmony 设备
如果项目无法运行到 OpenHarmony 设备或模拟器,可以检查:
- Flutter for OpenHarmony 环境是否配置完成;
- 设备是否连接成功;
flutter devices是否能识别设备;- 是否执行了
flutter pub get; - 是否选择了正确的运行设备;
- 项目是否为 Flutter 项目,而不是原生鸿蒙项目。
如果 flutter devices 都识别不到设备,那应该先处理环境问题,而不是怀疑 animated_text_kit。文字动画没那么大本事把设备藏起来。
十二、本文和原生鸿蒙项目的区别
本文是 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实现彩色文字动画; - 使用
WavyAnimatedText实现空状态提示; - 使用
TyperAnimatedText实现任务详情动态提示; - 使用数据模型管理学习任务;
- 使用
setState()实现任务完成状态更新; - 使用 Flutter Material 组件构建完整页面;
- 将项目运行到 OpenHarmony 设备或模拟器中。
这个项目虽然只是一个基础学习计划页面,但完整展示了 Flutter for OpenHarmony 项目中第三方库的使用流程。
后续可以在这个基础上继续扩展,例如:
- 添加任务新增功能;
- 添加任务删除功能;
- 添加任务编辑功能;
- 添加学习分类管理;
- 添加本地数据保存;
- 添加每日打卡记录;
- 添加学习时长统计;
- 添加暗色主题;
- 添加更多文字动画类型;
- 添加页面启动动画;
- 添加学习提醒通知。
整体来看,animated_text_kit 可以帮助 Flutter 开发者快速实现动态文字效果。通过这个项目,可以理解 Flutter for OpenHarmony 中第三方库依赖配置、动态文字组件使用、任务状态管理和页面展示之间的基本关系。
更多推荐
所有评论(0)