Flutter for OpenHarmony《智慧字典》 App 底部导航栏深度解析:构建多页面应用的核心骨架
在移动应用开发中,底部导航栏 (Bottom Navigation Bar) 是组织核心功能、提升用户导航效率的黄金标准。您提供的
import+pa.txt文件中的代码,展示了一个结构清晰、扩展性强且视觉精美的底部导航实现方案。本文将深入剖析其设计思路、核心代码和最佳实践。
完整效果展示



一、整体架构:解耦与职责分离
该实现最值得称赞的一点是 高度的模块化和职责分离,这为未来的功能扩展和维护奠定了坚实基础。
1.1 核心角色分工
MainScreen: 应用的"指挥中心"。它不关心每个标签页内部的具体逻辑,只负责:
- 管理当前选中的索引 (
_currentIndex)。
- 控制
PageView的页面切换。- 将用户的点击事件 (
onTap) 转发给PageView。
_pages列表 : 一个纯粹的 页面容器 。它将各个功能模块 (IdiomTab,EnglishTab,ProfileTab) 实例化并集中管理。要新增一个功能,只需在此列表中添加一项。- 独立的 Tab 组件 (
IdiomTab,EnglishTab,ProfileTab) : 每个标签页都是一个 完全自治的StatefulWidget。它们拥有自己的状态、UI 和业务逻辑,互不干扰。
✅ 优势 :这种设计使得代码 高内聚、低耦合。修改"我的"页面的逻辑,完全不会影响到"成语"页面,极大地提升了开发效率和代码可维护性。
1.2 关键代码结构
dart
// MainScreen 内部
final List<Widget> _pages = [
const IdiomTab(), // 成语页
const EnglishTab(), // 英语页
const ProfileTab(), // 我的页
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView( // 1. 页面主体
controller: _pageController,
onPageChanged: _onPageChanged, // 同步底部栏选中状态
children: _pages,
),
bottomNavigationBar: Container( // 2. 底部导航栏
child: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: _onTabTapped, // 同步PageView页面
items: [...], // 导航项配置
),
),
);
}

二、核心技术:PageView 与 BottomNavigationBar 的双向同步
为了让滑动切换页面和点击底部图标两种交互方式无缝衔接,代码实现了 双向状态同步。
2.1 数据流分析
- 用户点击底部图标:
- 触发
_onTabTapped(int index)。
- 该方法调用
_pageController.jumpToPage(index),强制PageView跳转到对应页面。- 注意 :这里使用
jumpToPage而非animateToPage,是为了让切换更迅速直接。
- 用户滑动
PageView:
- 触发
onPageChanged: _onPageChanged回调。
_onPageChanged方法通过setState更新_currentIndex。BottomNavigationBar监听到_currentIndex的变化,自动高亮对应的图标。
2.2 核心同步方法
dart
// 当PageView页面改变时,更新底部导航栏的选中状态
void _onPageChanged(int index) {
setState(() {
_currentIndex = index;
});
}
// 当底部导航栏被点击时,跳转PageView到对应页面
void _onTabTapped(int index) {
_pageController.jumpToPage(index);
}

⚠️ 关键点 :
_onPageChanged必须调用setState,因为_currentIndex是BottomNavigationBar的currentIndex属性的数据源。只有通过setState触发MainScreen的重建,底部栏才能感知到状态变化。
三、视觉与交互细节:打造精致体验
除了核心逻辑,代码在视觉呈现上也下足了功夫。
3.1 阴影与层次感
底部导航栏被包裹在一个 Container 中,并为其添加了 向上的阴影。
dart
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, -5), // 注意这里是负Y值,阴影在上方
),
],
)

- 效果 :这个阴影让底部导航栏看起来像是从屏幕底部"浮"起来的,与上方的
PageView内容形成了清晰的视觉分隔,符合 Material Design 的 Z轴层次 原则。
3.2 导航项配置
每个 BottomNavigationBarItem 都配置了 icon 和 activeIcon,虽然在此例中两者相同,但这种写法为未来实现 激活态图标变化(如填充 vs 线框)预留了空间。
dart
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.menu_book_rounded),
activeIcon: Icon(Icons.menu_book_rounded), // 可以设为不同的图标
label: '成语',
),
// ... 其他项
]

3.3 样式定制
- 颜色 :通过
selectedItemColor和unselectedItemColor精确控制图标的颜色。- 字体 :通过
selectedFontSize和unselectedFontSize微调标签文字大小,突出选中项。- 布局 :
type: BottomNavigationBarType.fixed确保所有标签(即使超过3个)都始终显示文字和图标。
四、各标签页 (Tab) 的实现策略
每个 Tab 都是一个独立的入口,其实现方式体现了不同的开发考量。
4.1 IdiomTab (成语页)
-
策略 :作为一个 代理 (Proxy)。
-
代码 :
dartclass IdiomTab extends StatefulWidget { @override State<IdiomTab> createState() => _IdiomTabState(); } class _IdiomTabState extends State<IdiomTab> { @override Widget build(BuildContext context) { return const DictionaryHomeScreen(); // 直接返回主页 } } -
目的 :将
DictionaryHomeScreen这个复杂的主功能页面 复用 为"成语"标签页的内容。这是一种避免代码重复的有效手段。
4.2 EnglishTab (英语页) & ProfileTab (我的页)
- 策略 :占位与引导。
- 代码 :两个页面都展示了 "功能开发中,敬请期待" 的提示信息,并通过
SnackBar响应用户点击。 - 目的 :
- 产品规划:清晰地向用户展示了 App 的未来功能蓝图。
- 交互反馈 :即使功能未完成,用户的点击操作也能得到即时响应 (
SnackBar),避免了"点击无反应"的糟糕体验。 - 开发便利:为后续开发提供了清晰的框架和入口,开发者只需替换占位内容即可。
五、总结:一个教科书式的底部导航实现
这段关于底部导航栏的代码,堪称 Flutter 应用架构的 优秀范例:
- 架构清晰 :
MainScreen作为协调者,各Tab作为独立模块,职责分明。- 交互流畅 :完美实现了
PageView滑动和BottomNavigationBar点击的双向同步。- 视觉精美:通过阴影、颜色和图标等细节,打造出符合现代审美的 UI。
- 扩展性强:无论是增加新页面,还是为现有页面填充功能,都变得异常简单。
- 用户体验佳 :即使是未完成的功能,也通过占位符和
SnackBar提供了良好的用户反馈。
🌐 加入社区
欢迎加入 开源鸿蒙跨平台开发者社区 ,获取最新资源与技术支持:
技术因分享而进步,生态因共建而繁荣 。
------ 晚霞的不甘 · 与您共赴鸿蒙跨平台开发之旅
完整代码
bash
import 'package:flutter/material.dart';
void main() {
runApp(const DictionaryApp());
}
class DictionaryApp extends StatelessWidget {
const DictionaryApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '智慧字典',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6366F1)),
useMaterial3: true,
),
home: const MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _currentIndex = 0;
final PageController _pageController = PageController();
final List<Widget> _pages = [
const IdiomTab(),
const EnglishTab(),
const ProfileTab(),
];
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void _onPageChanged(int index) {
setState(() {
_currentIndex = index;
});
}
void _onTabTapped(int index) {
_pageController.jumpToPage(index);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _pageController,
onPageChanged: _onPageChanged,
children: _pages,
),
bottomNavigationBar: Container(
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, -5),
),
],
),
child: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: _onTabTapped,
selectedItemColor: const Color(0xFF6366F1),
unselectedItemColor: Colors.grey,
selectedFontSize: 14,
unselectedFontSize: 12,
type: BottomNavigationBarType.fixed,
elevation: 0,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.menu_book_rounded),
activeIcon: Icon(Icons.menu_book_rounded),
label: '成语',
),
BottomNavigationBarItem(
icon: Icon(Icons.language_rounded),
activeIcon: Icon(Icons.language_rounded),
label: '英语',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_rounded),
activeIcon: Icon(Icons.person_rounded),
label: '我的',
),
],
),
),
);
}
}
// 成语标签页
class IdiomTab extends StatefulWidget {
const IdiomTab({super.key});
@override
State<IdiomTab> createState() => _IdiomTabState();
}
class _IdiomTabState extends State<IdiomTab> {
@override
Widget build(BuildContext context) {
return const DictionaryHomeScreen();
}
}
// 英语标签页
class EnglishTab extends StatefulWidget {
const EnglishTab({super.key});
@override
State<EnglishTab> createState() => _EnglishTabState();
}
class _EnglishTabState extends State<EnglishTab> {
final TextEditingController _searchController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF30CFD0),
const Color(0xFF330867),
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'英语学习',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
'探索英语的世界',
style: TextStyle(
fontSize: 16,
color: Colors.white.withOpacity(0.9),
),
),
const SizedBox(height: 32),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: TextField(
controller: _searchController,
style: const TextStyle(fontSize: 18),
decoration: InputDecoration(
hintText: '搜索英语单词...',
hintStyle: TextStyle(
color: Colors.grey[400],
fontSize: 16,
),
prefixIcon: const Icon(
Icons.search_rounded,
color: Color(0xFF6366F1),
size: 28,
),
border: InputBorder.none,
contentPadding: const EdgeInsets.all(20),
),
),
),
const SizedBox(height: 32),
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
),
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.language_rounded,
size: 64,
color: Color(0xFF30CFD0),
),
SizedBox(height: 16),
Text(
'英语功能开发中',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF2D3436),
),
),
SizedBox(height: 8),
Text(
'敬请期待',
style: TextStyle(
fontSize: 16,
color: Color(0xFF636E72),
),
),
],
),
),
),
),
],
),
),
),
),
);
}
}
// 我的标签页
class ProfileTab extends StatefulWidget {
const ProfileTab({super.key});
@override
State<ProfileTab> createState() => _ProfileTabState();
}
class _ProfileTabState extends State<ProfileTab> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF667EEA),
const Color(0xFF764BA2),
],
),
),
child: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 40),
// 用户头像
CircleAvatar(
radius: 50,
backgroundColor: Colors.white,
child: Icon(
Icons.person_rounded,
size: 60,
color: Color(0xFF667EEA),
),
),
const SizedBox(height: 16),
const Text(
'智慧字典',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
'探索词语的奥秘',
style: TextStyle(
fontSize: 16,
color: Colors.white.withOpacity(0.9),
),
),
const SizedBox(height: 40),
// 功能列表
Container(
margin: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
),
child: Column(
children: [
_buildMenuItem(
icon: Icons.history_rounded,
title: '搜索历史',
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('搜索历史功能开发中')),
);
},
),
_buildMenuItem(
icon: Icons.bookmark_rounded,
title: '我的收藏',
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('我的收藏功能开发中')),
);
},
),
_buildMenuItem(
icon: Icons.download_rounded,
title: '离线词典',
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('离线词典功能开发中')),
);
},
),
_buildMenuItem(
icon: Icons.settings_rounded,
title: '设置',
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('设置功能开发中')),
);
},
),
_buildMenuItem(
icon: Icons.info_rounded,
title: '关于',
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('关于功能开发中')),
);
},
),
],
),
),
const SizedBox(height: 40),
],
),
),
),
),
);
}
Widget _buildMenuItem({
required IconData icon,
required String title,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey[200]!,
width: 1,
),
),
),
child: Row(
children: [
Icon(
icon,
color: const Color(0xFF667EEA),
size: 24,
),
const SizedBox(width: 16),
Expanded(
child: Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
Icon(
Icons.chevron_right_rounded,
color: Colors.grey[400],
),
],
),
),
);
}
}
class DictionaryHomeScreen extends StatefulWidget {
const DictionaryHomeScreen({super.key});
@override
State<DictionaryHomeScreen> createState() => _DictionaryHomeScreenState();
}
class _DictionaryHomeScreenState extends State<DictionaryHomeScreen>
with SingleTickerProviderStateMixin {
final TextEditingController _searchController = TextEditingController();
List<DictionaryEntry> _searchResults = [];
bool _isSearching = false;
List<String> _searchHistory = [];
List<String> _searchSuggestions = [];
bool _showSuggestions = false;
late AnimationController _animationController;
late Animation<double> _fadeInAnimation;
// 模拟字典数据
final List<DictionaryEntry> _dictionaryData = [
// 成语类
DictionaryEntry(
word: '一丝不苟',
pinyin: 'yī sī bù gǒu',
partOfSpeech: '成语',
definition: '形容办事认真,连最细微的地方也不马虎。',
examples: [
'他工作一丝不苟,从不马虎。',
'老师批改作业一丝不苟,细致入微。',
'这位医生一丝不苟地检查每一个病人。',
'一丝不苟的工作态度值得我们学习。'
],
relatedWords: ['精益求精', '认真细致', '严谨'],
similarWords: ['粗心大意', '马马虎虎', '敷衍了事'],
type: '成语',
gradient: [const Color(0xFF667EEA), const Color(0xFF764BA2)],
),
DictionaryEntry(
word: '画蛇添足',
pinyin: 'huà shé tiān zú',
partOfSpeech: '成语',
definition: '画蛇时给蛇添上脚。比喻做了多余的事,非但无益,反而不合适。',
examples: [
'这篇作文写得很好,最后几句是画蛇添足。',
'你已经说得很清楚了,再说就是画蛇添足了。',
'不要做画蛇添足的事情,要学会适可而止。',
'他在完美的方案上画蛇添足,反而弄巧成拙。'
],
relatedWords: ['多此一举', '节外生枝', '弄巧成拙'],
similarWords: ['恰如其分', '恰到好处', '画龙点睛'],
type: '成语',
gradient: [const Color(0xFFF093FB), const Color(0xFFF5576C)],
),
DictionaryEntry(
word: '守株待兔',
pinyin: 'shǒu zhū dài tù',
partOfSpeech: '成语',
definition: '比喻希图不经过努力而得到成功的侥幸心理。',
examples: [
'我们不能守株待兔,要主动出击。',
'守株待兔的侥幸心理害人不浅。',
'与其守株待兔,不如积极行动。',
'机会总是留给有准备的人,守株待兔是等不到的。'
],
relatedWords: ['坐享其成', '不劳而获', '妄想'],
similarWords: ['积极进取', '主动出击', '勤奋努力'],
type: '成语',
gradient: [const Color(0xFF4FACFE), const Color(0xFF00F2FE)],
),
DictionaryEntry(
word: '亡羊补牢',
pinyin: 'wáng yáng bǔ láo',
partOfSpeech: '成语',
definition: '比喻出了问题以后想办法补救,可以防止继续受损失。',
examples: [
'虽然失败了,但要亡羊补牢,吸取教训。',
'亡羊补牢,为时不晚。',
'我们要学会亡羊补牢,及时纠正错误。',
'亡羊补牢的故事告诉我们:犯错后要及时改正。'
],
relatedWords: ['知错就改', '及时止损', '吸取教训'],
similarWords: ['执迷不悟', '一错再错', '错上加错'],
type: '成语',
gradient: [const Color(0xFFFA709A), const Color(0xFFFEE140)],
),
DictionaryEntry(
word: '掩耳盗铃',
pinyin: 'yǎn ěr dào líng',
partOfSpeech: '成语',
definition: '比喻自己欺骗自己,明明掩盖不住的事情偏要想法子掩盖。',
examples: [
'他试图掩耳盗铃,逃避责任。',
'掩耳盗铃的做法是自欺欺人。',
'不要掩耳盗铃,要正视自己的错误。',
'掩耳盗铃的结果往往是自欺欺人。'
],
relatedWords: ['自欺欺人', '掩人耳目', '自欺'],
similarWords: ['实事求是', '光明磊落', '坦诚相待'],
type: '成语',
gradient: [const Color(0xFF30CFD0), const Color(0xFF330867)],
),
DictionaryEntry(
word: '刻舟求剑',
pinyin: 'kè zhōu qiú jiàn',
partOfSpeech: '成语',
definition: '比喻拘泥成法,固执不知变通。',
examples: [
'时代在变,我们不能刻舟求剑。',
'刻舟求剑的做法跟不上时代发展。',
'要学会灵活应变,不要刻舟求剑。',
'用刻舟求剑的方式解决新问题是不行的。'
],
relatedWords: ['固步自封', '墨守成规', '抱残守缺'],
similarWords: ['随机应变', '灵活变通', '与时俱进'],
type: '成语',
gradient: [const Color(0xFFA8FF78), const Color(0xFF78FFA8)],
),
DictionaryEntry(
word: '对牛弹琴',
pinyin: 'duì niú tán qín',
partOfSpeech: '成语',
definition: '比喻对不懂道理的人讲道理,对不懂得美的人讲风雅。',
examples: [
'跟他说道理简直是对牛弹琴。',
'对牛弹琴说明没有找对听众。',
'不要对牛弹琴,要因材施教。',
'对牛弹琴的沟通方式效果很差。'
],
relatedWords: ['白费口舌', '徒劳无功', '鸡同鸭讲'],
similarWords: ['因材施教', '对症下药', '有的放矢'],
type: '成语',
gradient: [const Color(0xFFFF9A9E), const Color(0xFFFECFEF)],
),
DictionaryEntry(
word: '井底之蛙',
pinyin: 'jǐng dǐ zhī wā',
partOfSpeech: '成语',
definition: '比喻见识狭窄的人。',
examples: [
'不要做井底之蛙,要开阔眼界。',
'井底之蛙看不到外面的精彩世界。',
'多读书,不要当井底之蛙。',
'井底之蛙的格局限制了发展。'
],
relatedWords: ['目光短浅', '孤陋寡闻', '坐井观天'],
similarWords: ['见多识广', '博学多才', '开阔眼界'],
type: '成语',
gradient: [const Color(0xFFFFF9C), const Color(0xFFFFB199)],
),
DictionaryEntry(
word: '纸上谈兵',
pinyin: 'zhǐ shàng tán bīng',
partOfSpeech: '成语',
definition: '比喻空谈理论,不能解决实际问题。',
examples: [
'不要纸上谈兵,要注重实践。',
'纸上谈兵解决不了实际问题。',
'理论联系实际,避免纸上谈兵。',
'纸上谈兵的人往往缺乏实践经验。'
],
relatedWords: ['空谈理论', '夸夸其谈', '脱离实际'],
similarWords: ['脚踏实地', '实事求是', '身体力行'],
type: '成语',
gradient: [const Color(0xFF89F7FE), const Color(0xFF66A6FF)],
),
DictionaryEntry(
word: '半途而废',
pinyin: 'bàn tú ér fèi',
partOfSpeech: '成语',
definition: '指做事不能坚持到底,中途停顿,有始无终。',
examples: [
'做事要持之以恒,不能半途而废。',
'半途而废是成功的大敌。',
'不要半途而废,坚持就是胜利。',
'半途而废的努力都是白费。'
],
relatedWords: ['前功尽弃', '虎头蛇尾', '有始无终'],
similarWords: ['持之以恒', '善始善终', '坚持到底'],
type: '成语',
gradient: [const Color(0xFFE0C3FC), const Color(0xFF8EC5FC)],
),
DictionaryEntry(
word: '精益求精',
pinyin: 'jīng yì qiú jīng',
partOfSpeech: '成语',
definition: '已经很好了,还要求更好。比喻对事物追求完美。',
examples: [
'我们要精益求精,追求卓越。',
'精益求精的工匠精神值得学习。',
'精益求精是成功的品质之一。',
'精益求精的工作态度创造奇迹。'
],
relatedWords: ['尽善尽美', '完美无缺', '追求卓越'],
similarWords: ['得过且过', '敷衍了事', '粗制滥造'],
type: '成语',
gradient: [const Color(0xFF667EEA), const Color(0xFF764BA2)],
),
DictionaryEntry(
word: '见义勇为',
pinyin: 'jiàn yì yǒng wéi',
partOfSpeech: '成语',
definition: '看到正义的事,就勇敢地去做。',
examples: [
'见义勇为是中华民族的传统美德。',
'我们要学会见义勇为,帮助他人。',
'见义勇为的精神值得弘扬。',
'见义勇为的行为令人敬佩。'
],
relatedWords: ['挺身而出', '见义敢为', '仗义执言'],
similarWords: ['见死不救', '冷漠无情', '袖手旁观'],
type: '成语',
gradient: [const Color(0xFFF093FB), const Color(0xFFF5576C)],
),
DictionaryEntry(
word: '自强不息',
pinyin: 'zì qiáng bù xī',
partOfSpeech: '成语',
definition: '自觉地努力向上,永不松懈。',
examples: [
'自强不息是中华民族的精神。',
'我们要自强不息,奋发图强。',
'自强不息的人才能成就大事。',
'自强不息的精神推动着社会进步。'
],
relatedWords: ['发愤图强', '力争上游', '奋发向上'],
similarWords: ['自暴自弃', '自甘堕落', '自甘平庸'],
type: '成语',
gradient: [const Color(0xFF4FACFE), const Color(0xFF00F2FE)],
),
// 词语类
DictionaryEntry(
word: '学习',
pinyin: 'xué xí',
partOfSpeech: '动词',
definition: '通过阅读、听讲、研究、实践等途径获得知识或技能。',
example: '我们要努力学习科学文化知识。',
examples: [
'学习是终身的伴侣。',
'学习使人进步,不学则退步。',
'学习要有恒心,不能三天打鱼两天晒网。',
'学习方法比学习时间更重要。'
],
relatedWords: ['自学', '研习', '进修'],
similarWords: ['放弃', '荒废', '懈怠'],
type: '词语',
gradient: [const Color(0xFFFA709A), const Color(0xFFFEE140)],
),
DictionaryEntry(
word: '努力',
pinyin: 'nǔ lì',
partOfSpeech: '形容词',
definition: '尽最大力量;尽一切可能。',
example: '他工作非常努力。',
examples: [
'努力不一定成功,但不努力一定失败。',
'努力是成功的基础。',
'努力要讲究方法,方向要对。',
'努力的过程中收获最大。'
],
relatedWords: ['勤奋', '刻苦', '尽力'],
similarWords: ['懒惰', '松懈', '懈怠'],
type: '词语',
gradient: [const Color(0xFF30CFD0), const Color(0xFF330867)],
),
DictionaryEntry(
word: '知识',
pinyin: 'zhī shi',
partOfSpeech: '名词',
definition: '人们在认识世界、改造世界的过程中积累起来的经验。',
example: '知识就是力量。',
examples: ['知识改变命运。', '知识需要不断更新。', '知识在实践中升华。', '知识是无形的财富。'],
relatedWords: ['常识', '学识', '见识'],
similarWords: ['无知', '愚昧', '浅薄'],
type: '词语',
gradient: [const Color(0xFFA8FF78), const Color(0xFF78FFA8)],
),
DictionaryEntry(
word: '文化',
pinyin: 'wén huà',
partOfSpeech: '名词',
definition: '人类在社会实践中所创造的物质财富和精神财富的总和。',
example: '中华文明历史悠久,文化灿烂。',
examples: ['文化需要传承。', '文化促进交流。', '文化是国家的软实力。', '文化自信很重要。'],
relatedWords: ['文明', '艺术', '传统'],
similarWords: ['野蛮', '粗俗', '落后'],
type: '词语',
gradient: [const Color(0xFFFF9A9E), const Color(0xFFFECFEF)],
),
DictionaryEntry(
word: '创新',
pinyin: 'chuàng xīn',
partOfSpeech: '动词',
definition: '抛开旧的,创造新的。',
example: '科技创新推动了社会进步。',
examples: ['创新是发展的动力。', '创新需要勇气。', '创新要立足实际。', '创新带来无限可能。'],
relatedWords: ['改革', '创造', '发明'],
similarWords: ['守旧', '保守', '僵化'],
type: '词语',
gradient: [const Color(0xFFFFF9C), const Color(0xFFFFB199)],
),
];
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_fadeInAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
);
_animationController.forward();
}
@override
void dispose() {
_searchController.dispose();
_animationController.dispose();
super.dispose();
}
void _performSearch(String query) {
if (query.trim().isEmpty) {
setState(() {
_searchResults = [];
});
return;
}
setState(() {
_isSearching = true;
});
Future.delayed(const Duration(milliseconds: 300), () {
final results = _dictionaryData.where((entry) {
final queryLower = query.toLowerCase();
// 精确匹配词语
if (entry.word == query) return true;
// 模糊匹配词语(包含查询词)
if (entry.word.contains(query)) return true;
// 拼音匹配
if (entry.pinyin.toLowerCase().contains(queryLower)) return true;
// 释义匹配
if (entry.definition.contains(query)) return true;
// 例句匹配
if (entry.examples.any((ex) => ex.contains(query))) return true;
// 相关词语匹配
if (entry.relatedWords.any((word) => word.contains(query))) return true;
// 相似词语匹配
if (entry.similarWords.any((word) => word.contains(query))) return true;
// 首字母匹配(拼音首字母)
final pinyinInitials = _getPinyinInitials(entry.pinyin);
if (pinyinInitials.contains(queryLower)) return true;
return false;
}).toList();
// 保存搜索历史
if (query.isNotEmpty && !_searchHistory.contains(query)) {
setState(() {
_searchHistory.insert(0, query);
if (_searchHistory.length > 15) {
_searchHistory.removeLast();
}
});
}
// 按相关性排序结果
final sortedResults = _sortResultsByRelevance(results, query);
setState(() {
_searchResults = sortedResults;
_isSearching = false;
});
});
}
// 获取拼音首字母
String _getPinyinInitials(String pinyin) {
// 移除音调和空格,只保留首字母
return pinyin
.split(' ')
.map((s) => s.isEmpty ? '' : s[0].toLowerCase())
.join('');
}
// 按相关性排序结果
List<DictionaryEntry> _sortResultsByRelevance(
List<DictionaryEntry> results, String query) {
final queryLower = query.toLowerCase();
return results.toList()
..sort((a, b) {
// 精确匹配优先级最高
if (a.word == query && b.word != query) return -1;
if (b.word == query && a.word != query) return 1;
// 开头匹配优先级次高
final aStartsWith = a.word.startsWith(query);
final bStartsWith = b.word.startsWith(query);
if (aStartsWith && !bStartsWith) return -1;
if (bStartsWith && !aStartsWith) return 1;
// 拼音首字母匹配
final aPinyinMatch = _getPinyinInitials(a.pinyin) == queryLower;
final bPinyinMatch = _getPinyinInitials(b.pinyin) == queryLower;
if (aPinyinMatch && !bPinyinMatch) return -1;
if (bPinyinMatch && !aPinyinMatch) return 1;
// 按词语长度排序(较短的优先)
if (a.word.length != b.word.length) {
return a.word.length.compareTo(b.word.length);
}
return 0;
});
}
// 获取搜索建议
List<String> _getSearchSuggestions(String query) {
if (query.isEmpty) return [];
final queryLower = query.toLowerCase();
final suggestions = <String>[];
// 1. 完全匹配的词语
for (var entry in _dictionaryData) {
if (entry.word == query) {
suggestions.add(entry.word);
break;
}
}
// 2. 以查询开头的词语
for (var entry in _dictionaryData) {
if (entry.word.startsWith(query) && !suggestions.contains(entry.word)) {
suggestions.add(entry.word);
if (suggestions.length >= 5) break;
}
}
// 3. 包含查询的词语
for (var entry in _dictionaryData) {
if (entry.word.contains(query) && !suggestions.contains(entry.word)) {
suggestions.add(entry.word);
if (suggestions.length >= 8) break;
}
}
// 4. 拼音匹配的词语
for (var entry in _dictionaryData) {
if (entry.pinyin.toLowerCase().contains(queryLower) &&
!suggestions.contains(entry.word)) {
suggestions.add(entry.word);
if (suggestions.length >= 10) break;
}
}
return suggestions;
}
// 更新搜索建议
void _updateSearchSuggestions(String query) {
if (query.length >= 1) {
final suggestions = _getSearchSuggestions(query);
setState(() {
_searchSuggestions = suggestions;
_showSuggestions = suggestions.isNotEmpty;
});
} else {
setState(() {
_searchSuggestions = [];
_showSuggestions = false;
});
}
}
// 选择搜索建议
void _selectSuggestion(String suggestion) {
_searchController.text = suggestion;
setState(() {
_showSuggestions = false;
});
_performSearch(suggestion);
}
void _showDetailScreen(DictionaryEntry entry) {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
DictionaryDetailScreen(entry: entry),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(1.0, 0.0);
const end = Offset.zero;
const curve = Curves.ease;
var tween =
Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF667EEA),
const Color(0xFF764BA2),
const Color(0xFF66A6FF),
],
stops: const [0.0, 0.5, 1.0],
),
),
child: SafeArea(
child: Column(
children: [
// 顶部标题和装饰
Padding(
padding: const EdgeInsets.all(24),
child: FadeTransition(
opacity: _fadeInAnimation,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'智慧字典',
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1,
),
),
const SizedBox(height: 8),
Text(
'探索词语的奥秘',
style: TextStyle(
fontSize: 16,
color: Colors.white.withOpacity(0.9),
),
),
],
),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
),
child: const Icon(
Icons.menu_book_outlined,
size: 32,
color: Colors.white,
),
),
],
),
const SizedBox(height: 30),
],
),
),
),
// 搜索栏
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: TextField(
controller: _searchController,
style: const TextStyle(fontSize: 18),
decoration: InputDecoration(
hintText: '输入词语或文章进行查询...',
hintStyle: TextStyle(
color: Colors.grey[400],
fontSize: 16,
),
prefixIcon: Container(
margin: const EdgeInsets.only(left: 8, right: 4),
child: const Icon(
Icons.search_rounded,
color: Color(0xFF6366F1),
size: 28,
),
),
suffixIcon: _searchController.text.isNotEmpty
? Container(
margin: const EdgeInsets.only(right: 8),
child: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () {
_searchController.clear();
setState(() {
_searchResults = [];
});
},
),
)
: Container(
margin: const EdgeInsets.only(right: 12),
child: const Icon(
Icons.mic_rounded,
color: Color(0xFF6366F1),
size: 24,
),
),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
vertical: 18,
horizontal: 16,
),
),
onChanged: (value) {
_updateSearchSuggestions(value);
// 只有当用户输入较多内容时才进行实时搜索
if (value.length >= 2) {
_performSearch(value);
}
},
onSubmitted: (value) {
setState(() {
_showSuggestions = false;
});
_performSearch(value);
},
onTap: () {
if (_searchController.text.isNotEmpty) {
_updateSearchSuggestions(_searchController.text);
}
},
),
),
),
// 搜索建议列表
if (_showSuggestions && _searchSuggestions.isNotEmpty)
Container(
margin: const EdgeInsets.symmetric(horizontal: 24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(12),
itemCount: _searchSuggestions.take(6).length,
separatorBuilder: (context, index) => Divider(
height: 1,
color: Colors.grey[200],
),
itemBuilder: (context, index) {
final suggestion = _searchSuggestions[index];
return InkWell(
onTap: () => _selectSuggestion(suggestion),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 16,
),
child: Row(
children: [
Icon(
Icons.search_rounded,
size: 18,
color: Colors.grey[600],
),
const SizedBox(width: 12),
Expanded(
child: Text(
suggestion,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
Icon(
Icons.north_west_rounded,
size: 18,
color: Colors.grey[400],
),
],
),
),
);
},
),
// 更多建议提示
if (_searchSuggestions.length > 6)
Padding(
padding: const EdgeInsets.all(12),
child: InkWell(
onTap: () {
_showSuggestions = false;
_performSearch(_searchController.text);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'查看更多 (${_searchSuggestions.length - 6})',
style: TextStyle(
color: const Color(0xFF6366F1),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
],
),
),
SizedBox(height: _showSuggestions ? 16 : 24),
// 搜索结果或推荐区域
Expanded(
child: Container(
margin: const EdgeInsets.only(top: 20),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
child: _isSearching
? const Center(
child: CircularProgressIndicator(),
)
: _searchResults.isEmpty
? _buildEmptyState()
: _buildSearchResults(),
),
),
// 搜索历史浮层
if (_searchHistory.isNotEmpty && _searchController.text.isEmpty)
Positioned(
bottom: 20,
left: 20,
right: 20,
child: _buildSearchHistory(),
),
],
),
),
),
);
}
Widget _buildEmptyState() {
return Stack(
children: [
// 背景装饰
Positioned(
top: -50,
right: -50,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFF6366F1).withOpacity(0.05),
),
),
),
Positioned(
bottom: 50,
left: -30,
child: Container(
width: 150,
height: 150,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color(0xFF764BA2).withOpacity(0.05),
),
),
),
// 推荐词汇
Column(
children: [
const Padding(
padding: EdgeInsets.all(24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'每日推荐',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
Text(
'发现更多',
style: TextStyle(
fontSize: 16,
color: Color(0xFF6366F1),
),
),
],
),
),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 24),
itemCount: _dictionaryData.take(5).length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: _buildRecommendedCard(
_dictionaryData[index],
),
);
},
),
),
],
),
],
);
}
Widget _buildRecommendedCard(DictionaryEntry entry) {
return GestureDetector(
onTap: () {
_searchController.text = entry.word;
_performSearch(entry.word);
},
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: entry.gradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: entry.gradient[0].withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 8),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
entry.word,
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.3),
borderRadius: BorderRadius.circular(12),
),
child: Text(
entry.partOfSpeech,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 8),
Text(
entry.pinyin,
style: TextStyle(
fontSize: 16,
color: Colors.white.withOpacity(0.9),
),
),
const SizedBox(height: 12),
Text(
entry.definition,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.8),
height: 1.5,
),
),
],
),
),
);
}
Widget _buildSearchResults() {
return ListView.builder(
padding: const EdgeInsets.all(24),
itemCount: _searchResults.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: _buildResultCard(_searchResults[index]),
);
},
);
}
Widget _buildResultCard(DictionaryEntry entry) {
return GestureDetector(
onTap: () => _showDetailScreen(entry),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Column(
children: [
// 上半部分:主要信息
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
entry.gradient[0].withOpacity(0.05),
entry.gradient[1].withOpacity(0.05),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
// 左侧渐变条
Container(
width: 6,
height: 80,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: entry.gradient,
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 16),
// 内容区域
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
entry.word,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
// 操作按钮组
Row(
mainAxisSize: MainAxisSize.min,
children: [
// 发音按钮
_buildActionButton(
icon: Icons.volume_up_rounded,
onTap: () => _speakWord(entry.word),
color: entry.gradient[0],
),
const SizedBox(width: 8),
// 收藏按钮
_buildActionButton(
icon: Icons.bookmark_border_rounded,
onTap: () => _toggleFavorite(entry),
color: entry.gradient[0],
),
],
),
],
),
const SizedBox(height: 6),
Row(
children: [
Text(
entry.pinyin,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(width: 12),
// 热度标签
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.orange.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.local_fire_department,
size: 14,
color: Colors.orange[700],
),
const SizedBox(width: 4),
Text(
'${(entry.word.length * 100 % 500 + 100)}热度',
style: TextStyle(
fontSize: 12,
color: Colors.orange[700],
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
const SizedBox(height: 10),
// 类型标签
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: entry.gradient[0].withOpacity(0.15),
borderRadius: BorderRadius.circular(12),
),
child: Text(
'${entry.partOfSpeech} · ${entry.type}',
style: TextStyle(
color: entry.gradient[0],
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
),
),
// 下半部分:详情预览
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 释义
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 2),
child: Icon(
Icons.lightbulb_outline_rounded,
size: 16,
color: entry.gradient[0],
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
entry.definition,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey[700],
height: 1.5,
),
),
),
],
),
),
const SizedBox(height: 10),
// 例句预览
if (entry.examples.isNotEmpty)
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: entry.gradient[0].withOpacity(0.05),
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 2),
child: Icon(
Icons.format_quote_rounded,
size: 16,
color: entry.gradient[0],
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
entry.examples.first,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: entry.gradient[0].withOpacity(0.8),
fontStyle: FontStyle.italic,
),
),
),
],
),
),
// 相关词语预览
if (entry.relatedWords.isNotEmpty) ...[
const SizedBox(height: 10),
Wrap(
spacing: 6,
children: entry.relatedWords.take(3).map((word) {
return _buildWordChip(word, entry.gradient[0],
isSmall: true);
}).toList(),
),
],
],
),
),
],
),
),
),
);
}
Widget _buildSearchHistory() {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'搜索历史',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
TextButton(
onPressed: () {
setState(() {
_searchHistory.clear();
});
},
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF6366F1),
),
child: const Text('清除全部'),
),
],
),
const SizedBox(height: 12),
Wrap(
spacing: 10,
runSpacing: 10,
children: _searchHistory.take(6).map((term) {
return InkWell(
onTap: () {
_searchController.text = term;
_performSearch(term);
},
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 10,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
const Color(0xFF6366F1).withOpacity(0.1),
const Color(0xFF667EEA).withOpacity(0.1),
],
),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.history_rounded,
size: 16,
color: const Color(0xFF6366F1),
),
const SizedBox(width: 6),
Text(
term,
style: const TextStyle(
color: Color(0xFF6366F1),
fontSize: 14,
),
),
],
),
),
);
}).toList(),
),
],
),
);
}
// 词语标签组件
Widget _buildWordChip(String word, Color color, {bool isSmall = false}) {
return InkWell(
onTap: () {
_searchController.text = word;
_performSearch(word);
},
borderRadius: BorderRadius.circular(20),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: isSmall ? 12 : 16,
vertical: isSmall ? 6 : 10,
),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: color.withOpacity(0.3),
width: 1,
),
),
child: Text(
word,
style: TextStyle(
color: color,
fontSize: isSmall ? 12 : 14,
fontWeight: FontWeight.w600,
),
),
),
);
}
// 发音按钮组件
Widget _buildActionButton({
required IconData icon,
required VoidCallback onTap,
required Color color,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: color,
size: 20,
),
),
);
}
// 朗读词语
void _speakWord(String word) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.volume_up_rounded, color: Colors.white),
const SizedBox(width: 8),
Text('朗读: $word'),
],
),
backgroundColor: Colors.green,
duration: const Duration(seconds: 1),
),
);
}
// 收藏/取消收藏
void _toggleFavorite(DictionaryEntry entry) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Row(
children: [
const Icon(Icons.favorite_rounded, color: Colors.white),
const SizedBox(width: 8),
Text('已收藏: ${entry.word}'),
],
),
backgroundColor: entry.gradient[0],
duration: const Duration(seconds: 1),
),
);
}
}
// 词语详情页面
class DictionaryDetailScreen extends StatelessWidget {
final DictionaryEntry entry;
const DictionaryDetailScreen({super.key, required this.entry});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
entry.gradient[0],
entry.gradient[1],
],
),
),
child: SafeArea(
child: Column(
children: [
// 顶部导航
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
),
child: IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(
Icons.arrow_back_rounded,
color: Colors.white,
),
),
),
const Text(
'词语详情',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
),
child: IconButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('已添加到收藏'),
backgroundColor: Colors.green,
),
);
},
icon: const Icon(
Icons.favorite_border_rounded,
color: Colors.white,
),
),
),
],
),
),
// 词语卡片
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
children: [
// 主卡片
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(32),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 30,
offset: const Offset(0, 20),
),
],
),
child: Column(
children: [
Text(
entry.word,
style: const TextStyle(
fontSize: 56,
fontWeight: FontWeight.bold,
color: Color(0xFF2D3436),
),
),
const SizedBox(height: 12),
Text(
entry.pinyin,
style: TextStyle(
fontSize: 24,
color: Colors.grey[600],
letterSpacing: 2,
),
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: entry.gradient,
),
borderRadius: BorderRadius.circular(20),
),
child: Text(
entry.partOfSpeech,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
const SizedBox(height: 32),
// 释义
_buildDetailSection(
'释义',
Icons.info_rounded,
entry.gradient[0],
Text(
entry.definition,
style: const TextStyle(
fontSize: 18,
height: 1.8,
color: Color(0xFF2D3436),
),
),
),
const SizedBox(height: 24),
// 例句
_buildDetailSection(
'例句',
Icons.format_quote_rounded,
entry.gradient[0],
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: entry.gradient[0].withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: entry.gradient[0].withOpacity(0.3),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'"',
style: TextStyle(
fontSize: 48,
color: Color(0xFF6366F1),
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
entry.example,
style: const TextStyle(
fontSize: 18,
fontStyle: FontStyle.italic,
height: 1.6,
color: Color(0xFF2D3436),
),
),
),
],
),
),
),
const SizedBox(height: 24),
// 相关词语
_buildDetailSection(
'相关词语',
Icons.link_rounded,
entry.gradient[0],
Wrap(
spacing: 12,
runSpacing: 12,
children: entry.relatedWords.map((word) {
return GestureDetector(
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
DictionaryDetailScreen(
entry: DictionaryEntry(
word: word,
pinyin: '',
partOfSpeech: '相关词',
definition: '点击查看详情',
example: '',
relatedWords: [],
gradient: entry.gradient,
),
),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 12,
),
decoration: BoxDecoration(
color: entry.gradient[0].withOpacity(0.1),
borderRadius: BorderRadius.circular(24),
border: Border.all(
color: entry.gradient[0].withOpacity(0.3),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 12,
backgroundColor: entry.gradient[0],
child: Text(
word[0],
style: const TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 10),
Text(
word,
style: TextStyle(
color: entry.gradient[0],
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
],
),
),
);
}).toList(),
),
),
const SizedBox(height: 40),
// 操作按钮
Row(
children: [
Expanded(
child: _buildActionButton(
Icons.share_rounded,
'分享',
() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('分享成功'),
backgroundColor: Colors.green,
),
);
},
),
),
const SizedBox(width: 16),
Expanded(
child: _buildActionButton(
Icons.copy_rounded,
'复制',
() {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('已复制到剪贴板'),
backgroundColor: Colors.green,
),
);
},
),
),
],
),
],
),
),
),
],
),
),
),
);
}
Widget _buildDetailSection(
String title,
IconData icon,
Color color,
Widget content,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: color,
size: 24,
),
),
const SizedBox(width: 12),
Text(
title,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Color(0xFF2D3436),
),
),
],
),
const SizedBox(height: 16),
content,
],
);
}
Widget _buildActionButton(IconData icon, String label, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
color: const Color(0xFF6366F1),
),
const SizedBox(width: 8),
const Text(
'label',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF2D3436),
),
),
],
),
),
);
}
}
// 字典条目数据模型
class DictionaryEntry {
final String word;
final String pinyin;
final String partOfSpeech;
final String definition;
final String example;
final List<String> examples;
final List<String> relatedWords;
final List<String> similarWords;
final String type;
final List<Color> gradient;
DictionaryEntry({
required this.word,
required this.pinyin,
required this.partOfSpeech,
required this.definition,
this.example = '',
this.examples = const [],
this.relatedWords = const [],
this.similarWords = const [],
this.type = '',
required this.gradient,
});
}