Flutter for OpenHarmony《智慧字典》英语学习模块代码深度解析:从数据模型到交互体验
Flutter for OpenHarmony《智慧字典》英语学习模块代码深度解析:从数据模型到交互体验
·
Flutter for OpenHarmony《智慧字典》英语学习模块代码深度解析:从数据模型到交互体验
在移动应用开发中,一个功能的优秀与否,不仅体现在最终的UI上,更根植于其底层的代码架构和交互逻辑。本文将带您深入《智慧字典》App的英语学习模块源码,逐行剖析其如何通过精巧的设计,打造流畅、美观且富有潜力的学习体验。
完整效果展示

一、基石:结构化的数据模型 (EnglishWord)
一切功能的起点,是一个清晰、自解释的数据模型。EnglishWord 类完美地封装了一个英文单词所需的所有核心信息。
class EnglishWord {
final String word; // 单词本身,如 "apple"
final String phonetic; // 国际音标,如 "[ˈæpl]"
final String partOfSpeech; // 词性,如 "n." (名词)
final String meaning; // 中文释义,如 "苹果"
final String example; // 例句,如 "I eat an apple every day."
final Color color; // 主题色,用于统一视觉风格
final List<Color> gradient; // 渐变色数组,用于创建动态背景
const EnglishWord({
required this.word,
required this.phonetic,
required this.partOfSpeech,
required this.meaning,
required this.example,
required this.color,
required this.gradient,
});
}

代码解读:
final关键字:所有字段都被声明为final,意味着一旦对象创建,其属性就不可变。这保证了数据的一致性和安全性,是Flutter中推荐的不可变对象模式。required参数:在构造函数中,所有参数都是必需的,确保了每个EnglishWord实例都包含完整的信息,避免了空值异常。- 视觉属性 (
color,gradient):这两个字段非常关键。它们将数据与UI表现直接关联起来。每个单词都有自己的“个性色彩”,这为后续创建沉浸式的详情页(全屏渐变背景)奠定了基础。
二、入口:搜索驱动的单词列表 (EnglishTab)
EnglishTab 是用户接触英语功能的第一个界面,它采用了一个简洁高效的搜索列表模式。
2.1 状态管理与数据初始化
class _EnglishTabState extends State<EnglishTab> {
late List<EnglishWord> _allWords; // 存储所有单词的完整列表
List<EnglishWord> _filteredWords = []; // 存储根据搜索过滤后的单词列表
final TextEditingController _searchController = TextEditingController(); // 控制搜索框文本
void initState() {
super.initState();
// 在组件初始化时,加载所有预定义的单词数据
_allWords = WordData.getEnglishWords();
_filteredWords = _allWords; // 初始状态下,显示所有单词
// 监听搜索框的文本变化
_searchController.addListener(_onSearchChanged);
}
...
}

代码解读:
late关键字:_allWords被标记为late,表示它会在initState中被初始化,编译器无需担心它在声明时未赋值。- 数据分离:
_allWords和_filteredWords的分离是实现高效搜索的关键。原始数据 (_allWords) 保持不变,而_filteredWords则是根据用户输入动态生成的视图数据。TextEditingController:这是Flutter中控制TextField的标准方式。通过监听其变化 (addListener),可以实时响应用户的输入。
2.2 核心逻辑:实时搜索 (_onSearchChanged)
void _onSearchChanged() {
final query = _searchController.text.toLowerCase();
if (query.isEmpty) {
// 如果搜索框为空,则显示所有单词
setState(() {
_filteredWords = _allWords;
});
} else {
// 否则,过滤出单词或释义中包含查询关键词的单词
setState(() {
_filteredWords = _allWords.where((word) =>
word.word.toLowerCase().contains(query) ||
word.meaning.toLowerCase().contains(query)
).toList();
});
}
}

代码解读:
- 双向匹配:搜索逻辑同时检查
word(英文单词)和meaning(中文释义)。这意味着用户既可以输入英文查中文,也可以输入中文查英文,极大地提升了实用性。setState的精准调用:只有在_filteredWords列表发生变化后,才调用setState来触发UI重建。这是性能优化的关键,避免了不必要的重绘。
2.3 UI构建:单词卡片 (_buildWordCard)
Widget _buildWordCard(EnglishWord word) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// 左侧:垂直色带
Container(
width: 8,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: word.gradient,
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
const SizedBox(width: 12),
// 右侧:主要内容
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(word.word, style: ...), // 单词
Text(word.phonetic, style: ...), // 音标
// 词性标签
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(color: word.color.withOpacity(0.2)),
child: Text(word.partOfSpeech, style: TextStyle(color: word.color)),
),
const SizedBox(height: 8),
Text('释义: ${word.meaning}', style: ...), // 释义
],
),
),
],
),
),
);
}
代码解读:
- 视觉层次:通过
Row和Column的嵌套,清晰地划分了布局区域。左侧窄色带作为视觉锚点,右侧内容区信息层级分明。- 动态样式:色带 (
Container) 使用LinearGradient动态应用word.gradient。词性标签 (Container) 的背景色和文字色也直接来源于word.color,实现了数据驱动的UI。
三、深化:沉浸式详情页 (EnglishWordDetailScreen)
点击单词卡片后,会导航到 EnglishWordDetailScreen,这是一个全屏的、高度聚焦的学习页面。
3.1 全屏渐变背景
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent, // 让Scaffold背景透明
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: widget.word.gradient, // 应用该单词的专属渐变
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: SafeArea(...), // 内容区域
),
);
}
代码解读:
Scaffold透明化:将Scaffold的背景设为透明,是为了让其子Container的渐变背景能够完全铺满屏幕,创造出沉浸感。SafeArea:包裹内容,确保UI元素不会被手机的刘海屏或状态栏遮挡,是现代移动端开发的最佳实践。
3.2 功能按钮:发音与复制
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () {
// 播放发音的逻辑(当前为占位)
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('播放发音功能开发中...')),
);
},
icon: const Icon(Icons.volume_up_rounded),
label: const Text('播放'),
),
const SizedBox(width: 20),
OutlinedButton.icon(
onPressed: () {
// 复制单词到剪贴板
Clipboard.setData(ClipboardData(text: widget.word.word));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制单词')),
);
},
icon: const Icon(Icons.copy_rounded),
label: const Text('复制'),
),
],
)

代码解读:
ClipboardAPI:Clipboard.setData()是Flutter提供的标准API,用于与系统剪贴板交互。这是实现“复制”功能的核心。ScaffoldMessenger:用于显示SnackBar,向用户提供操作成功的即时反馈,是提升用户体验的重要细节。- 占位逻辑:播放发音功能目前是占位状态,通过
SnackBar告知用户,这是一种优雅处理未完成功能的方式。
四、巩固:游戏化词汇测试 (EnglishQuizScreen)
EnglishQuizScreen 通过问答形式帮助用户主动回忆和巩固所学单词。
4.1 题目模型与状态
class QuizQuestion {
final EnglishWord word; // 题目对应的单词
final List<String> options; // 四个干扰项(包括正确答案)
final int correctAnswer; // 正确答案在options中的索引
}
// 在State中
int _currentQuestionIndex = 0; // 当前题目索引
int _selectedOption = -1; // 用户选择的选项索引
bool _answered = false; // 是否已作答
int _score = 0; // 总得分
代码解读:
QuizQuestion模型:将题目、选项和答案封装在一起,使得题库管理和逻辑处理变得简单清晰。- 状态变量:
_answered是控制交互流程的关键。它确保用户只能作答一次,并在作答后锁定选项,提供清晰的交互反馈。
4.2 答案提交与反馈
void _submitAnswer() {
if (_selectedOption == -1) return; // 未选择任何选项
final isCorrect = _selectedOption == _questions[_currentQuestionIndex].correctAnswer;
if (isCorrect) {
_score += 10; // 答对加分
}
setState(() {
_answered = true; // 标记为已作答,触发UI更新以显示对错
});
// 延迟1秒后自动进入下一题
Future.delayed(const Duration(seconds: 1), () {
if (_currentQuestionIndex < _questions.length - 1) {
setState(() {
_currentQuestionIndex++;
_selectedOption = -1;
_answered = false;
});
} else {
_showResults(); // 显示最终结果
}
});
}
代码解读:
Future.delayed:这个方法用于创建一个延迟任务。在这里,它在用户看到答案反馈后,自动平滑地过渡到下一题,无需用户手动点击“下一题”,使测试流程更加流畅。- 状态重置:进入新题目时,会重置
_selectedOption和_answered,为下一轮交互做好准备。
🌐 加入社区
欢迎加入 开源鸿蒙跨平台开发者社区,获取最新资源与技术支持:
👉 开源鸿蒙跨平台开发者社区
技术因分享而进步,生态因共建而繁荣。
—— 晚霞的不甘 · 与您共赴鸿蒙跨平台开发之旅
完整代码
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 Scaffold(
body: Column(
children: [
// 功能选择卡片
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF667EEA),
const Color(0xFF764BA2),
],
),
),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'成语学习',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildFunctionCard(
icon: Icons.search_rounded,
title: '成语词典',
subtitle: '查找成语释义',
color: const Color(0xFF667EEA),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const DictionaryHomeScreen(),
),
);
},
),
),
const SizedBox(width: 16),
Expanded(
child: _buildFunctionCard(
icon: Icons.quiz_rounded,
title: '成语测试',
subtitle: '测试成语知识',
color: const Color(0xFF30CFD0),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const IdiomQuizScreen(),
),
);
},
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildFunctionCard(
icon: Icons.edit_note_rounded,
title: '填空练习',
subtitle: '完成成语填空',
color: const Color(0xFFFA709A),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const FillBlankScreen(),
),
);
},
),
),
const SizedBox(width: 16),
Expanded(
child: _buildFunctionCard(
icon: Icons.auto_stories_rounded,
title: '每日成语',
subtitle: '学习新成语',
color: const Color(0xFFA8FF78),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DailyIdiomScreen(),
),
);
},
),
),
],
),
],
),
),
),
// 成语统计
Flexible(
child: Container(
padding: const EdgeInsets.all(24),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'学习统计',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
_buildStatCard(
icon: Icons.menu_book_rounded,
title: '成语数量',
value: '50',
subtitle: '个成语',
color: const Color(0xFF667EEA),
),
const SizedBox(height: 12),
_buildStatCard(
icon: Icons.check_circle_rounded,
title: '已掌握',
value: '12',
subtitle: '个成语',
color: const Color(0xFF30CFD0),
),
const SizedBox(height: 12),
_buildStatCard(
icon: Icons.school_rounded,
title: '测试次数',
value: '5',
subtitle: '次测试',
color: const Color(0xFFFA709A),
),
const SizedBox(height: 12),
_buildStatCard(
icon: Icons.emoji_events_rounded,
title: '正确率',
value: '85',
suffix: '%',
subtitle: '平均正确率',
color: const Color(0xFFA8FF78),
),
],
),
),
),
),
],
),
);
}
Widget _buildFunctionCard({
required IconData icon,
required String title,
required String subtitle,
required Color color,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: Colors.white.withOpacity(0.3),
width: 2,
),
),
child: Column(
children: [
Icon(
icon,
color: Colors.white,
size: 32,
),
const SizedBox(height: 12),
Text(
title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.8),
),
),
],
),
),
);
}
Widget _buildStatCard({
required IconData icon,
required String title,
required String value,
required String subtitle,
required Color color,
String suffix = '',
}) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: color.withOpacity(0.3),
width: 1,
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(12),
),
child: Icon(
icon,
color: Colors.white,
size: 24,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF636E72),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Row(
children: [
Flexible(
child: Text(
value + suffix,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: color,
),
),
),
const SizedBox(width: 8),
Flexible(
child: Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 14,
color: Color(0xFF636E72),
),
),
),
],
),
],
),
),
],
),
);
}
}
// 英语标签页
class EnglishTab extends StatefulWidget {
const EnglishTab({super.key});
@override
State<EnglishTab> createState() => _EnglishTabState();
}
class _EnglishTabState extends State<EnglishTab> {
final TextEditingController _searchController = TextEditingController();
final List<EnglishWord> _vocabulary = _getVocabulary();
List<EnglishWord> _searchResults = [];
bool _isSearching = false;
@override
void initState() {
super.initState();
_searchController.addListener(_onSearchChanged);
}
@override
void dispose() {
_searchController.removeListener(_onSearchChanged);
_searchController.dispose();
super.dispose();
}
void _onSearchChanged() {
setState(() {
if (_searchController.text.isEmpty) {
_searchResults = [];
_isSearching = false;
} else {
_isSearching = true;
_searchResults = _vocabulary
.where((word) =>
word.word
.toLowerCase()
.contains(_searchController.text.toLowerCase()) ||
word.meaning
.toLowerCase()
.contains(_searchController.text.toLowerCase()))
.toList();
}
});
}
@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: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'英语学习',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
'探索英语的世界',
maxLines: 2,
overflow: TextOverflow.ellipsis,
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,
),
suffixIcon: _searchController.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
},
)
: null,
border: InputBorder.none,
contentPadding: const EdgeInsets.all(20),
),
),
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: _buildFunctionCard(
icon: Icons.quiz_rounded,
title: '单词测试',
subtitle: '测试词汇量',
color: const Color(0xFF30CFD0),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const EnglishQuizScreen(),
),
);
},
),
),
const SizedBox(width: 16),
Expanded(
child: _buildFunctionCard(
icon: Icons.text_fields_rounded,
title: '句子测试',
subtitle: '翻译练习',
color: const Color(0xFF667EEA),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const EnglishSentenceQuizScreen(),
),
);
},
),
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildFunctionCard(
icon: Icons.hearing_rounded,
title: '听力练习',
subtitle: '提升听力',
color: const Color(0xFFFA709A),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('听力练习功能开发中')),
);
},
),
),
const SizedBox(width: 16),
Expanded(
child: _buildFunctionCard(
icon: Icons.history_rounded,
title: '学习记录',
subtitle: '查看历史',
color: const Color(0xFFA8FF78),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('学习记录功能开发中')),
);
},
),
),
],
),
],
),
),
const SizedBox(height: 16),
Flexible(
child: Container(
margin: const EdgeInsets.fromLTRB(20, 0, 20, 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
),
child:
_isSearching ? _buildSearchResults() : _buildDailyWords(),
),
),
],
),
),
),
);
}
Widget _buildFunctionCard({
required IconData icon,
required String title,
required String subtitle,
required Color color,
required VoidCallback onTap,
}) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: color.withOpacity(0.2),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: color.withOpacity(0.3),
width: 2,
),
),
child: Column(
children: [
Icon(
icon,
color: Colors.white,
size: 32,
),
const SizedBox(height: 12),
Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
subtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 12,
color: Colors.white,
),
),
],
),
),
);
}
Widget _buildSearchResults() {
if (_searchResults.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off_rounded,
size: 64,
color: Colors.grey[300],
),
const SizedBox(height: 16),
Text(
'未找到相关单词',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 18,
color: Colors.grey[600],
),
),
],
),
);
}
return SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: _searchResults.map((word) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: _buildWordCard(word),
);
}).toList(),
),
);
}
Widget _buildDailyWords() {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text(
'每日推荐',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF2D3436),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
children: _vocabulary.take(10).map((word) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: _buildWordCard(word),
);
}).toList(),
),
),
const SizedBox(height: 20),
],
),
);
}
Widget _buildWordCard(EnglishWord word) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => EnglishWordDetailScreen(word: word),
),
);
},
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: word.color.withOpacity(0.2),
width: 2,
),
boxShadow: [
BoxShadow(
color: word.color.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 6,
height: 60,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: word.gradient,
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
borderRadius: BorderRadius.circular(3),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
word.word,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: word.color,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: word.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
word.partOfSpeech,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: word.color,
fontWeight: FontWeight.w500,
),
),
),
],
),
const SizedBox(height: 8),
Text(
word.phonetic,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
fontStyle: FontStyle.italic,
),
),
const SizedBox(height: 6),
Text(
word.meaning,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 15,
color: Colors.grey[700],
height: 1.4,
),
),
],
),
),
const SizedBox(width: 8),
Icon(
Icons.chevron_right_rounded,
color: Colors.grey[400],
),
],
),
),
);
}
}
// 英语单词数据模型
class EnglishWord {
final String word;
final String phonetic;
final String partOfSpeech;
final String meaning;
final String example;
final Color color;
final List<Color> gradient;
EnglishWord({
required this.word,
required this.phonetic,
required this.partOfSpeech,
required this.meaning,
required this.example,
required this.color,
required this.gradient,
});
}
List<EnglishWord> _getVocabulary() {
return [
EnglishWord(
word: 'ephemeral',
phonetic: '/ɪˈfem.ər.əl/',
partOfSpeech: 'adj.',
meaning: '短暂的,转瞬即逝的',
example: 'Fame is often ephemeral.',
color: const Color(0xFF667EEA),
gradient: [const Color(0xFF667EEA), const Color(0xFF764BA2)],
),
EnglishWord(
word: 'serendipity',
phonetic: '/ˌser.ənˈdɪp.ə.ti/',
partOfSpeech: 'n.',
meaning: '意外发现珍奇事物的本领',
example: 'Finding this book was pure serendipity.',
color: const Color(0xFF30CFD0),
gradient: [const Color(0xFF30CFD0), const Color(0xFF30CFD0)],
),
EnglishWord(
word: 'eloquent',
phonetic: '/ˈel.ə.kwənt/',
partOfSpeech: 'adj.',
meaning: '雄辩的,有说服力的',
example: 'She gave an eloquent speech.',
color: const Color(0xFFFA709A),
gradient: [const Color(0xFFFA709A), const Color(0xFFFEE140)],
),
EnglishWord(
word: 'resilient',
phonetic: '/rɪˈzɪl.jənt/',
partOfSpeech: 'adj.',
meaning: '有弹性的,能恢复的',
example: 'Children are often resilient.',
color: const Color(0xFFA8FF78),
gradient: [const Color(0xFF78FFD6), const Color(0xFFA8FF78)],
),
EnglishWord(
word: 'meticulous',
phonetic: '/məˈtɪk.jə.ləs/',
partOfSpeech: 'adj.',
meaning: '一丝不苟的,细致的',
example: 'He is meticulous in his work.',
color: const Color(0xFFF58EA8),
gradient: [const Color(0xFFF58EA8), const Color(0xFFFED6E3)],
),
EnglishWord(
word: 'ubiquitous',
phonetic: '/juːˈbɪk.wɪ.təs/',
partOfSpeech: 'adj.',
meaning: '无处不在的',
example: 'Smartphones are now ubiquitous.',
color: const Color(0xFF9D50BB),
gradient: [const Color(0xFF6E48AA), const Color(0xFF9D50BB)],
),
EnglishWord(
word: 'pragmatic',
phonetic: '/præɡˈmæt̬.ɪk/',
partOfSpeech: 'adj.',
meaning: '务实的,实用主义的',
example: 'We need a pragmatic approach.',
color: const Color(0xFF56AB2F),
gradient: [const Color(0xFFA8E063), const Color(0xFF56AB2F)],
),
EnglishWord(
word: 'ambiguous',
phonetic: '/æmˈbɪɡ.ju.əs/',
partOfSpeech: 'adj.',
meaning: '模棱两可的',
example: 'The message was ambiguous.',
color: const Color(0xFFFC5C7D),
gradient: [const Color(0xFFFC5C7D), const Color(0xFF6A82FB)],
),
EnglishWord(
word: 'conundrum',
phonetic: '/kəˈnʌn.drəm/',
partOfSpeech: 'n.',
meaning: '令人费解的难题',
example: 'This presents quite a conundrum.',
color: const Color(0xFF1CB5E0),
gradient: [const Color(0xFF000046), const Color(0xFF1CB5E0)],
),
EnglishWord(
word: 'epitome',
phonetic: '/ɪˈpɪt.ə.mi/',
partOfSpeech: 'n.',
meaning: '典型,缩影',
example: 'She is the epitome of elegance.',
color: const Color(0xFFDA22FF),
gradient: [const Color(0xFF9733EE), const Color(0xFFDA22FF)],
),
];
}
// 英语单词详情页面
class EnglishWordDetailScreen extends StatelessWidget {
final EnglishWord word;
const EnglishWordDetailScreen({super.key, required this.word});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: word.gradient,
),
),
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,
),
),
),
Text(
'单词详情',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const 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('已收藏')),
);
},
icon: const Icon(
Icons.favorite_border_rounded,
color: Colors.white,
),
),
),
],
),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
),
),
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Column(
children: [
Text(
word.word,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: word.color,
),
),
const SizedBox(height: 8),
Text(
word.phonetic,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 20,
color: Colors.grey[600],
fontStyle: FontStyle.italic,
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
decoration: BoxDecoration(
color: word.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
word.partOfSpeech,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: word.color,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
const SizedBox(height: 32),
_buildDetailSection(
'中文释义',
Icons.translate_rounded,
word.color,
Text(
word.meaning,
maxLines: 5,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 20,
height: 1.6,
color: Color(0xFF2D3436),
),
),
),
const SizedBox(height: 24),
_buildDetailSection(
'例句',
Icons.format_quote_rounded,
word.color,
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: word.color.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'"',
style: TextStyle(
fontSize: 40,
color: Color(0xFF30CFD0),
fontWeight: FontWeight.bold,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
word.example,
maxLines: 4,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 18,
fontStyle: FontStyle.italic,
height: 1.6,
color: Color(0xFF2D3436),
),
),
),
],
),
),
),
const SizedBox(height: 32),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('正在播放发音...')),
);
},
icon: const Icon(Icons.volume_up_rounded),
label: const Text('播放发音'),
style: ElevatedButton.styleFrom(
backgroundColor: word.color,
foregroundColor: Colors.white,
padding:
const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: ElevatedButton.icon(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已复制到剪贴板')),
);
},
icon: const Icon(Icons.copy_rounded),
label: const Text('复制单词'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[200],
foregroundColor: const Color(0xFF2D3436),
padding:
const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
),
],
),
],
),
),
),
),
],
),
),
),
);
}
Widget _buildDetailSection(
String title,
IconData icon,
Color color,
Widget content,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 20),
),
const SizedBox(width: 12),
Text(
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
const SizedBox(height: 16),
content,
],
);
}
}
// 英语单词测试页面
class EnglishQuizScreen extends StatefulWidget {
const EnglishQuizScreen({super.key});
@override
State<EnglishQuizScreen> createState() => _EnglishQuizScreenState();
}
class _EnglishQuizScreenState extends State<EnglishQuizScreen> {
int _currentQuestionIndex = 0;
int _score = 0;
bool _answered = false;
int? _selectedAnswer;
final List<EnglishQuizQuestion> _questions = _getQuizQuestions();
void _selectAnswer(int index) {
if (_answered) return;
setState(() {
_selectedAnswer = index;
_answered = true;
if (index == _questions[_currentQuestionIndex].correctAnswer) {
_score += 10;
}
});
}
void _nextQuestion() {
if (_currentQuestionIndex < _questions.length - 1) {
setState(() {
_currentQuestionIndex++;
_answered = false;
_selectedAnswer = null;
});
} else {
_showResults();
}
}
void _showResults() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
title: const Text('测试完成'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.emoji_events_rounded,
size: 80,
color: Color(0xFF30CFD0),
),
const SizedBox(height: 16),
Text(
'得分: $_score',
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Color(0xFF30CFD0),
),
),
const SizedBox(height: 8),
Text(
'正确率: ${(_score / (_questions.length * 10) * 100).toInt()}%',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
},
child: const Text('返回'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
setState(() {
_currentQuestionIndex = 0;
_score = 0;
_answered = false;
_selectedAnswer = null;
});
},
child: const Text('重新测试'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final currentQuestion = _questions[_currentQuestionIndex];
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF30CFD0),
const Color(0xFF330867),
],
),
),
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,
),
),
),
Text(
'单词测试',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'得分: $_score',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
),
Expanded(
child: Container(
margin: const EdgeInsets.fromLTRB(20, 0, 20, 20),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
),
child: Column(
children: [
Container(
margin: const EdgeInsets.only(bottom: 24),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'题目 ${_currentQuestionIndex + 1}/${_questions.length}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
if (_answered)
Text(
_selectedAnswer ==
currentQuestion.correctAnswer
? '回答正确!'
: '回答错误',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _selectedAnswer ==
currentQuestion.correctAnswer
? Colors.green
: Colors.red,
),
),
],
),
const SizedBox(height: 8),
LinearProgressIndicator(
value: (_currentQuestionIndex + 1) /
_questions.length,
backgroundColor: Colors.grey[300],
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF30CFD0),
),
),
],
),
),
const SizedBox(height: 24),
Text(
'选择正确的中文释义',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: const Color(0xFF30CFD0).withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
currentQuestion.word,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Color(0xFF30CFD0),
),
),
),
const SizedBox(height: 32),
...currentQuestion.options.asMap().entries.map((entry) {
final index = entry.key;
final option = entry.value;
final isSelected = _selectedAnswer == index;
final isCorrect =
index == currentQuestion.correctAnswer;
Color? backgroundColor;
Color? textColor;
if (_answered) {
if (isCorrect) {
backgroundColor = Colors.green.withOpacity(0.2);
textColor = Colors.green;
} else if (isSelected) {
backgroundColor = Colors.red.withOpacity(0.2);
textColor = Colors.red;
} else {
backgroundColor = null;
textColor = Colors.grey[700];
}
} else if (isSelected) {
backgroundColor =
const Color(0xFF30CFD0).withOpacity(0.2);
textColor = const Color(0xFF30CFD0);
} else {
backgroundColor = null;
textColor = Colors.grey[700];
}
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: () => _selectAnswer(index),
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: backgroundColor ?? Colors.grey[50],
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected
? const Color(0xFF30CFD0)
: Colors.grey[200]!,
width: 2,
),
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: isSelected
? const Color(0xFF30CFD0)
: Colors.grey[200],
shape: BoxShape.circle,
),
child: Center(
child: Text(
String.fromCharCode(65 + index),
style: TextStyle(
color: isSelected
? Colors.white
: Colors.grey[600],
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
option,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: textColor,
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
),
],
),
),
),
);
}),
const SizedBox(height: 24),
if (_answered)
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _nextQuestion,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF30CFD0),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 18),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: Text(
_currentQuestionIndex < _questions.length - 1
? '下一题'
: '查看结果',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
],
),
),
),
);
}
}
class EnglishQuizQuestion {
final String word;
final List<String> options;
final int correctAnswer;
EnglishQuizQuestion({
required this.word,
required this.options,
required this.correctAnswer,
});
}
List<EnglishQuizQuestion> _getQuizQuestions() {
return [
EnglishQuizQuestion(
word: 'ephemeral',
options: ['短暂的', '永恒的', '美丽的', '危险的'],
correctAnswer: 0,
),
EnglishQuizQuestion(
word: 'serendipity',
options: ['机遇', '意外发现', '悲伤', '快乐'],
correctAnswer: 1,
),
EnglishQuizQuestion(
word: 'eloquent',
options: ['沉默的', '雄辩的', '愚蠢的', '勇敢的'],
correctAnswer: 1,
),
EnglishQuizQuestion(
word: 'resilient',
options: ['脆弱的', '懒惰的', '有弹性的', '固执的'],
correctAnswer: 2,
),
EnglishQuizQuestion(
word: 'meticulous',
options: ['粗心的', '一丝不苟的', '快速的', '慢的'],
correctAnswer: 1,
),
];
}
// 英语句子测试页面
class EnglishSentenceQuizScreen extends StatefulWidget {
const EnglishSentenceQuizScreen({super.key});
@override
State<EnglishSentenceQuizScreen> createState() =>
_EnglishSentenceQuizScreenState();
}
class _EnglishSentenceQuizScreenState extends State<EnglishSentenceQuizScreen> {
int _currentQuestionIndex = 0;
int _score = 0;
bool _answered = false;
int? _selectedAnswer;
final List<EnglishSentenceQuestion> _questions = _getSentenceQuizQuestions();
void _selectAnswer(int index) {
if (_answered) return;
setState(() {
_selectedAnswer = index;
_answered = true;
if (index == _questions[_currentQuestionIndex].correctAnswer) {
_score += 10;
}
});
}
void _nextQuestion() {
if (_currentQuestionIndex < _questions.length - 1) {
setState(() {
_currentQuestionIndex++;
_answered = false;
_selectedAnswer = null;
});
} else {
_showResults();
}
}
void _showResults() {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
title: const Text('测试完成'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.emoji_events_rounded,
size: 80,
color: Color(0xFF667EEA),
),
const SizedBox(height: 16),
Text(
'得分: $_score',
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Color(0xFF667EEA),
),
),
const SizedBox(height: 8),
Text(
'正确率: ${(_score / (_questions.length * 10) * 100).toInt()}%',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
},
child: const Text('返回'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
setState(() {
_currentQuestionIndex = 0;
_score = 0;
_answered = false;
_selectedAnswer = null;
});
},
child: const Text('重新测试'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final currentQuestion = _questions[_currentQuestionIndex];
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF667EEA),
const Color(0xFF764BA2),
],
),
),
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,
),
),
),
Text(
'句子测试',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(16),
),
child: Text(
'得分: $_score',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
),
Expanded(
child: Container(
margin: const EdgeInsets.fromLTRB(20, 0, 20, 20),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(24),
),
child: SingleChildScrollView(
child: Column(
children: [
Container(
margin: const EdgeInsets.only(bottom: 24),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'题目 ${_currentQuestionIndex + 1}/${_questions.length}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
if (_answered)
Text(
_selectedAnswer ==
currentQuestion.correctAnswer
? '回答正确!'
: '回答错误',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: _selectedAnswer ==
currentQuestion.correctAnswer
? Colors.green
: Colors.red,
),
),
],
),
const SizedBox(height: 8),
LinearProgressIndicator(
value: (_currentQuestionIndex + 1) /
_questions.length,
backgroundColor: Colors.grey[300],
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF667EEA),
),
),
],
),
),
const SizedBox(height: 24),
Text(
'选择正确的中文翻译',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0xFF667EEA).withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Text(
currentQuestion.english,
maxLines: 4,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Color(0xFF2D3436),
height: 1.6,
),
),
),
const SizedBox(height: 24),
...currentQuestion.options.asMap().entries.map((entry) {
final index = entry.key;
final option = entry.value;
final isSelected = _selectedAnswer == index;
final isCorrect =
index == currentQuestion.correctAnswer;
Color? backgroundColor;
Color? textColor;
if (_answered) {
if (isCorrect) {
backgroundColor = Colors.green.withOpacity(0.2);
textColor = Colors.green;
} else if (isSelected) {
backgroundColor = Colors.red.withOpacity(0.2);
textColor = Colors.red;
} else {
backgroundColor = null;
textColor = Colors.grey[700];
}
} else if (isSelected) {
backgroundColor =
const Color(0xFF667EEA).withOpacity(0.2);
textColor = const Color(0xFF667EEA);
} else {
backgroundColor = null;
textColor = Colors.grey[700];
}
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: () => _selectAnswer(index),
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: backgroundColor ?? Colors.grey[50],
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected
? const Color(0xFF667EEA)
: Colors.grey[200]!,
width: 2,
),
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: isSelected
? const Color(0xFF667EEA)
: Colors.grey[200],
shape: BoxShape.circle,
),
child: Center(
child: Text(
String.fromCharCode(65 + index),
style: TextStyle(
color: isSelected
? Colors.white
: Colors.grey[600],
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
option,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: textColor,
fontWeight: isSelected
? FontWeight.bold
: FontWeight.normal,
),
),
),
],
),
),
),
);
}),
const SizedBox(height: 24),
if (_answered)
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _nextQuestion,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF667EEA),
foregroundColor: Colors.white,
padding:
const EdgeInsets.symmetric(vertical: 18),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: Text(
_currentQuestionIndex < _questions.length - 1
? '下一题'
: '查看结果',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
),
],
),
),
),
);
}
}
class EnglishSentenceQuestion {
final String english;
final List<String> options;
final int correctAnswer;
EnglishSentenceQuestion({
required this.english,
required this.options,
required this.correctAnswer,
});
}
List<EnglishSentenceQuestion> _getSentenceQuizQuestions() {
return [
EnglishSentenceQuestion(
english: 'Actions speak louder than words.',
options: ['行动胜于雄辩', '话多没用', '言出必行', '沉默是金'],
correctAnswer: 0,
),
EnglishSentenceQuestion(
english: 'Rome was not built in a day.',
options: ['罗马一天就建好了', '罗马没有一天', '罗马不是一天建成的', '建罗马需要时间'],
correctAnswer: 2,
),
EnglishSentenceQuestion(
english: 'Practice makes perfect.',
options: ['完美需要练习', '熟能生巧', '练习很重要', '完美源于实践'],
correctAnswer: 1,
),
EnglishSentenceQuestion(
english: 'Time flies like an arrow.',
options: ['时间像箭一样飞', '光阴似箭', '箭飞得很快', '时间流逝'],
correctAnswer: 1,
),
EnglishSentenceQuestion(
english: 'Knowledge is power.',
options: ['知识就是力量', '力量来自知识', '学习很重要', '知识很强大'],
correctAnswer: 0,
),
];
}
// 我的标签页
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(
'智慧字典',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 8),
Text(
'探索词语的奥秘',
maxLines: 2,
overflow: TextOverflow.ellipsis,
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,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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)],
),
// 更多成语
DictionaryEntry(
word: '脚踏实地',
pinyin: 'jiǎo tà shídì',
partOfSpeech: '成语',
definition: '比喻做事踏实,认真。',
examples: [
'我们要脚踏实地,一步一个脚印。',
'脚踏实地的工作作风很重要。',
'学习要脚踏实地,不能好高骛远。',
'脚踏实地是成功的基石。'
],
relatedWords: ['勤勤恳恳', '兢兢业业', '认真踏实'],
similarWords: ['好高骛远', '眼高手低', '虚浮不实'],
type: '成语',
gradient: [const Color(0xFF667EEA), const Color(0xFF764BA2)],
),
DictionaryEntry(
word: '熟能生巧',
pinyin: 'shú néng shēng qiǎo',
partOfSpeech: '成语',
definition: '熟练了就能产生巧办法。',
examples: [
'熟能生巧,多练习就能掌握。',
'熟能生巧是学习的秘诀。',
'熟能生巧,不断重复就能提高。',
'熟能生巧,实践出真知。'
],
relatedWords: ['勤学苦练', '学以致用', '循序渐进'],
similarWords: ['一曝十寒', '眼高手低', '纸上谈兵'],
type: '成语',
gradient: [const Color(0xFFF093FB), const Color(0xFFF5576C)],
),
DictionaryEntry(
word: '持之以恒',
pinyin: 'chí zhī yǐ héng',
partOfSpeech: '成语',
definition: '长久地坚持下去。',
examples: [
'学习要持之以恒,不能三天打鱼两天晒网。',
'持之以恒的努力终将获得回报。',
'只有持之以恒,才能取得成功。',
'持之以恒是成功的关键。'
],
relatedWords: ['坚持不懈', '锲而不舍', '坚持到底'],
similarWords: ['半途而废', '浅尝辄止', '虎头蛇尾'],
type: '成语',
gradient: [const Color(0xFF4FACFE), const Color(0xFF00F2FE)],
),
DictionaryEntry(
word: '循序渐进',
pinyin: 'xún xù jiàn jìn',
partOfSpeech: '成语',
definition: '按一定的顺序、步骤逐渐进步。',
examples: [
'学习要循序渐进,不能急于求成。',
'循序渐进是学习的正确方法。',
'任何事情都要循序渐进,不能好高骛远。',
'循序渐进才能达到目标。'
],
relatedWords: ['一步一个脚印', '按部就班', '逐步推进'],
similarWords: ['急于求成', '好高骛远', '一蹴而就'],
type: '成语',
gradient: [const Color(0xFFFA709A), const Color(0xFFFEE140)],
),
DictionaryEntry(
word: '举一反三',
pinyin: 'jǔ yī fǎn sān',
partOfSpeech: '成语',
definition: '从一件事情类推而知道其他许多事情。',
examples: [
'学习要举一反三,触类旁通。',
'举一反三是学习的重要方法。',
'学会举一反三,事半功倍。',
'举一反三,融会贯通。'
],
relatedWords: ['触类旁通', '融会贯通', '灵活变通'],
similarWords: ['囫囫吞枣', '死记硬背', '生搬硬套'],
type: '成语',
gradient: [const Color(0xFF30CFD0), const Color(0xFF330867)],
),
DictionaryEntry(
word: '温故知新',
pinyin: 'wēn gù zhī xīn',
partOfSpeech: '成语',
definition: '温习旧的,从而得知新的。',
examples: [
'温故知新是学习的有效方法。',
'经常温故知新,知识会更牢固。',
'温故知新,不断进步。',
'温故知新,学而时习之。'
],
relatedWords: ['学而时习', '反复巩固', '温习巩固'],
similarWords: ['学了就忘', '囫囫吞枣', '一曝十寒'],
type: '成语',
gradient: [const Color(0xFFA8FF78), const Color(0xFF78FFA8)],
),
DictionaryEntry(
word: '勤能补拙',
pinyin: 'qín néng bǔ zhuō',
partOfSpeech: '成语',
definition: '勤奋可以弥补天资的不足。',
examples: [
'勤能补拙,笨鸟先飞。',
'勤能补拙,努力就有回报。',
'勤能补拙,成功的天平是平等的。',
'勤能补拙,努力创造奇迹。'
],
relatedWords: ['笨鸟先飞', '勤学苦练', '努力奋斗'],
similarWords: ['自暴自弃', '听天由命', '坐享其成'],
type: '成语',
gradient: [const Color(0xFFFF9A9E), const Color(0xFFFECFEF)],
),
DictionaryEntry(
word: '博览群书',
pinyin: 'bó lǎn qún shū',
partOfSpeech: '成语',
definition: '广泛阅读各种书籍。',
examples: ['要博览群书,开阔眼界。', '博览群书是学习的基础。', '博览群书,知识渊博。', '博览群书,增长见识。'],
relatedWords: ['学富五车', '见多识广', '学识渊博'],
similarWords: ['孤陋寡闻', '井底之蛙', '坐井观天'],
type: '成语',
gradient: [const Color(0xFFFFF9C), const Color(0xFFFFB199)],
),
DictionaryEntry(
word: '知行合一',
pinyin: 'zhī xíng hé yī',
partOfSpeech: '成语',
definition: '认识事物的道理与在现实中运用此道理,是密不可分的一回事。',
examples: ['要知行合一,理论联系实际。', '知行合一是学习的最高境界。', '知行合一,学以致用。', '知行合一,实践出真知。'],
relatedWords: ['学以致用', '理论联系实际', '脚踏实地'],
similarWords: ['纸上谈兵', '空谈理论', '脱离实际'],
type: '成语',
gradient: [const Color(0xFF89F7FE), const Color(0xFF66A6FF)],
),
DictionaryEntry(
word: '博学多才',
pinyin: 'bó xué duō cái',
partOfSpeech: '成语',
definition: '学识广博,有多方面的才能。',
examples: ['我们要博学多才,全面发展。', '博学多才是时代的要求。', '博学多才,适应社会。', '博学多才,成就事业。'],
relatedWords: ['学富五车', '多才多艺', '见多识广'],
similarWords: ['孤陋寡闻', '才疏学浅', '不学无术'],
type: '成语',
gradient: [const Color(0xFFE0C3FC), const Color(0xFF8EC5FC)],
),
];
@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(
'智慧字典',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 1,
),
),
const SizedBox(height: 8),
Text(
'探索词语的奥秘',
maxLines: 2,
overflow: TextOverflow.ellipsis,
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,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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})',
maxLines: 1,
overflow: TextOverflow.ellipsis,
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(
'每日推荐',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
Text(
'发现更多',
maxLines: 1,
overflow: TextOverflow.ellipsis,
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: [
Expanded(
child: Text(
entry.word,
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
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,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 8),
Text(
entry.pinyin,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
// 操作按钮组
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,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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)}热度',
maxLines: 1,
overflow: TextOverflow.ellipsis,
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}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
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,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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(
'词语详情',
maxLines: 1,
overflow: TextOverflow.ellipsis,
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,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 56,
fontWeight: FontWeight.bold,
color: Color(0xFF2D3436),
),
),
const SizedBox(height: 12),
Text(
entry.pinyin,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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,
maxLines: 6,
overflow: TextOverflow.ellipsis,
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,
maxLines: 3,
overflow: TextOverflow.ellipsis,
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,
maxLines: 1,
overflow: TextOverflow.ellipsis,
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),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF2D3436),
),
),
],
),
),
);
}
}
// 成语测试页面
class IdiomQuizScreen extends StatefulWidget {
const IdiomQuizScreen({super.key});
@override
State<IdiomQuizScreen> createState() => _IdiomQuizScreenState();
}
class _IdiomQuizScreenState extends State<IdiomQuizScreen> {
final List<QuizQuestion> _questions = [
QuizQuestion(
idiom: '一丝不苟',
options: ['办事认真', '马马虎虎', '粗心大意', '敷衍了事'],
correctAnswer: 0,
),
QuizQuestion(
idiom: '画蛇添足',
options: ['恰到好处', '画龙点睛', '做多余的事', '精益求精'],
correctAnswer: 2,
),
QuizQuestion(
idiom: '守株待兔',
options: ['主动出击', '侥幸心理', '努力奋斗', '积极进取'],
correctAnswer: 1,
),
QuizQuestion(
idiom: '亡羊补牢',
options: ['及时补救', '放弃治疗', '听天由命', '无动于衷'],
correctAnswer: 0,
),
QuizQuestion(
idiom: '掩耳盗铃',
options: ['光明磊落', '实事求是', '自欺欺人', '坦诚相待'],
correctAnswer: 2,
),
];
int _currentQuestionIndex = 0;
int _score = 0;
bool _answered = false;
int? _selectedAnswer;
@override
Widget build(BuildContext context) {
final currentQuestion = _questions[_currentQuestionIndex];
return Scaffold(
appBar: AppBar(
title: const Text('成语测试'),
backgroundColor: const Color(0xFF667EEA),
foregroundColor: Colors.white,
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF667EEA).withOpacity(0.1),
const Color(0xFF764BA2).withOpacity(0.1),
],
),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
// 进度条
Container(
margin: const EdgeInsets.only(bottom: 24),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'题目 ${_currentQuestionIndex + 1}/${_questions.length}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
'得分: $_score',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF667EEA),
),
),
],
),
const SizedBox(height: 8),
LinearProgressIndicator(
value: (_currentQuestionIndex + 1) / _questions.length,
backgroundColor: Colors.grey[300],
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF667EEA),
),
),
],
),
),
// 问题卡片
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Column(
children: [
Text(
currentQuestion.idiom,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Color(0xFF667EEA),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 16),
Text(
'这个成语的意思是什么?',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 18,
color: Colors.grey[600],
),
),
],
),
),
const SizedBox(height: 32),
// 选项
...currentQuestion.options.asMap().entries.map((entry) {
final index = entry.key;
final option = entry.value;
final isSelected = _selectedAnswer == index;
final isCorrect = index == currentQuestion.correctAnswer;
Color? backgroundColor;
Color? textColor;
if (_answered) {
if (isCorrect) {
backgroundColor = Colors.green.withOpacity(0.2);
textColor = Colors.green;
} else if (isSelected) {
backgroundColor = Colors.red.withOpacity(0.2);
textColor = Colors.red;
}
} else if (isSelected) {
backgroundColor = const Color(0xFF667EEA).withOpacity(0.2);
textColor = const Color(0xFF667EEA);
}
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: _answered ? null : () => _selectAnswer(index),
borderRadius: BorderRadius.circular(16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: backgroundColor ?? Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: isSelected
? const Color(0xFF667EEA)
: Colors.grey[300]!,
width: isSelected ? 2 : 1,
),
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: backgroundColor ?? Colors.grey[200],
shape: BoxShape.circle,
),
child: Center(
child: Text(
String.fromCharCode(65 + index),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: textColor ?? Colors.grey[600],
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Text(
option,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: textColor ?? Colors.black87,
fontWeight: FontWeight.w500,
),
),
),
if (_answered && isCorrect)
const Icon(
Icons.check_circle_rounded,
color: Colors.green,
size: 24,
),
if (_answered && isSelected && !isCorrect)
const Icon(
Icons.cancel_rounded,
color: Colors.red,
size: 24,
),
],
),
),
),
);
}).toList(),
const SizedBox(height: 24),
// 下一题/完成按钮
if (_answered)
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: () => _nextQuestion(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF667EEA),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: Text(
_currentQuestionIndex < _questions.length - 1
? '下一题'
: '完成测试',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
),
);
}
void _selectAnswer(int index) {
setState(() {
_selectedAnswer = index;
_answered = true;
if (index == _questions[_currentQuestionIndex].correctAnswer) {
_score += 10;
}
});
}
void _nextQuestion() {
if (_currentQuestionIndex < _questions.length - 1) {
setState(() {
_currentQuestionIndex++;
_answered = false;
_selectedAnswer = null;
});
} else {
_showResult();
}
}
void _showResult() {
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: const Text(
'测试完成',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF667EEA),
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_score >= 40
? Icons.emoji_events_rounded
: Icons.sentiment_satisfied_rounded,
size: 80,
color: _score >= 40
? const Color(0xFFA8FF78)
: const Color(0xFF667EEA),
),
const SizedBox(height: 16),
Text(
'你的得分',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
Text(
'$_score',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: Color(0xFF667EEA),
),
),
const SizedBox(height: 8),
Text(
_getScoreMessage(),
style: TextStyle(
fontSize: 18,
color: Colors.grey[700],
fontWeight: FontWeight.w500,
),
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
},
child: const Text('返回'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
setState(() {
_currentQuestionIndex = 0;
_score = 0;
_answered = false;
_selectedAnswer = null;
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF667EEA),
),
child: const Text('再测一次'),
),
],
),
);
}
String _getScoreMessage() {
if (_score >= 40) return '太棒了!成语知识很扎实!';
if (_score >= 30) return '不错!继续加油!';
if (_score >= 20) return '还可以,多学习会更好!';
return '需要加强学习,多积累成语!';
}
}
// 填空题页面
class FillBlankScreen extends StatefulWidget {
const FillBlankScreen({super.key});
@override
State<FillBlankScreen> createState() => _FillBlankScreenState();
}
class _FillBlankScreenState extends State<FillBlankScreen> {
final List<FillBlankQuestion> _questions = [
FillBlankQuestion(
question: '他工作__,从不马虎。',
answer: '一丝不苟',
hint: '形容办事认真',
),
FillBlankQuestion(
question: '这篇作文写得很好,最后几句是__。',
answer: '画蛇添足',
hint: '做多余的事',
),
FillBlankQuestion(
question: '虽然失败了,但要__,吸取教训。',
answer: '亡羊补牢',
hint: '出了问题及时补救',
),
FillBlankQuestion(
question: '不要做__的事情,要学会适可而止。',
answer: '掩耳盗铃',
hint: '自己欺骗自己',
),
FillBlankQuestion(
question: '我们要__,追求卓越。',
answer: '精益求精',
hint: '追求完美',
),
];
int _currentQuestionIndex = 0;
int _score = 0;
final TextEditingController _answerController = TextEditingController();
@override
void dispose() {
_answerController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final currentQuestion = _questions[_currentQuestionIndex];
return Scaffold(
appBar: AppBar(
title: const Text('填空练习'),
backgroundColor: const Color(0xFF30CFD0),
foregroundColor: Colors.white,
),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF30CFD0).withOpacity(0.1),
const Color(0xFF330867).withOpacity(0.1),
],
),
),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
// 进度信息
Container(
margin: const EdgeInsets.only(bottom: 24),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'题目 ${_currentQuestionIndex + 1}/${_questions.length}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
'得分: $_score',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF30CFD0),
),
),
],
),
const SizedBox(height: 8),
LinearProgressIndicator(
value: (_currentQuestionIndex + 1) / _questions.length,
backgroundColor: Colors.grey[300],
valueColor: const AlwaysStoppedAnimation<Color>(
Color(0xFF30CFD0),
),
),
],
),
),
// 问题卡片
Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: Column(
children: [
Text(
'完成下列成语填空:',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
const SizedBox(height: 16),
Text(
currentQuestion.question,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
height: 1.5,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 16),
if (currentQuestion.hint.isNotEmpty)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: const Color(0xFF30CFD0).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'💡 提示: ${currentQuestion.hint}',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
color: const Color(0xFF30CFD0),
),
),
),
],
),
),
const SizedBox(height: 32),
// 答案输入
TextField(
controller: _answerController,
style: const TextStyle(fontSize: 18),
decoration: InputDecoration(
hintText: '输入成语答案...',
hintStyle: TextStyle(
color: Colors.grey[400],
fontSize: 16,
),
prefixIcon: const Icon(
Icons.edit_rounded,
color: Color(0xFF30CFD0),
),
filled: true,
fillColor: Colors.grey[50],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.all(20),
),
),
const SizedBox(height: 24),
// 提交按钮
SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: () => _checkAnswer(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF30CFD0),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: const Text(
'提交答案',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
),
),
);
}
void _checkAnswer() {
final userAnswer = _answerController.text.trim();
final correctAnswer = _questions[_currentQuestionIndex].answer;
if (userAnswer == correctAnswer) {
setState(() {
_score += 10;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Row(
children: [
Icon(Icons.check_circle_rounded, color: Colors.white),
SizedBox(width: 8),
Text('回答正确!+10分'),
],
),
backgroundColor: Colors.green,
duration: const Duration(seconds: 2),
),
);
Future.delayed(const Duration(milliseconds: 1500), () {
if (_currentQuestionIndex < _questions.length - 1) {
setState(() {
_currentQuestionIndex++;
_answerController.clear();
});
} else {
_showResult();
}
});
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Row(
children: [
Icon(Icons.cancel_rounded, color: Colors.white),
SizedBox(width: 8),
Text('回答错误,再试一次'),
],
),
backgroundColor: Colors.red,
duration: const Duration(seconds: 2),
),
);
_answerController.clear();
}
}
void _showResult() {
showDialog(
context: context,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: const Text(
'练习完成',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF30CFD0),
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
_score >= 40
? Icons.emoji_events_rounded
: Icons.sentiment_satisfied_rounded,
size: 80,
color: _score >= 40
? const Color(0xFFA8FF78)
: const Color(0xFF30CFD0),
),
const SizedBox(height: 16),
Text(
'你的得分',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
Text(
'$_score',
style: const TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: Color(0xFF30CFD0),
),
),
const SizedBox(height: 8),
Text(
_getScoreMessage(),
style: TextStyle(
fontSize: 18,
color: Colors.grey[700],
fontWeight: FontWeight.w500,
),
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
},
child: const Text('返回'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
setState(() {
_currentQuestionIndex = 0;
_score = 0;
_answerController.clear();
});
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF30CFD0),
),
child: const Text('再练一次'),
),
],
),
);
}
String _getScoreMessage() {
if (_score >= 40) return '太棒了!成语掌握得很好!';
if (_score >= 30) return '不错!继续加油!';
if (_score >= 20) return '还可以,多练习会更好!';
return '需要加强学习,多积累成语!';
}
}
// 每日成语页面
class DailyIdiomScreen extends StatefulWidget {
const DailyIdiomScreen({super.key});
@override
State<DailyIdiomScreen> createState() => _DailyIdiomScreenState();
}
class _DailyIdiomScreenState extends State<DailyIdiomScreen> {
final List<DictionaryEntry> _dailyIdioms = [
DictionaryEntry(
word: '持之以恒',
pinyin: 'chí zhī yǐ héng',
partOfSpeech: '成语',
definition: '长久地坚持下去。',
examples: [
'学习要持之以恒,不能三天打鱼两天晒网。',
'持之以恒的努力终将获得回报。',
'只有持之以恒,才能取得成功。',
'持之以恒是成功的关键。'
],
relatedWords: ['坚持不懈', '锲而不舍', '坚持到底'],
similarWords: ['半途而废', '浅尝辄止', '虎头蛇尾'],
type: '成语',
gradient: [const Color(0xFFA8FF78), const Color(0xFF78FFA8)],
),
DictionaryEntry(
word: '脚踏实地',
pinyin: 'jiǎo tà shí dì',
partOfSpeech: '成语',
definition: '比喻做事踏实,认真。',
examples: [
'我们要脚踏实地,一步一个脚印。',
'脚踏实地的工作作风很重要。',
'学习要脚踏实地,不能好高骛远。',
'脚踏实地是成功的基石。'
],
relatedWords: ['勤勤恳恳', '兢兢业业', '认真踏实'],
similarWords: ['好高骛远', '眼高手低', '虚浮不实'],
type: '成语',
gradient: [const Color(0xFFFF9A9E), const Color(0xFFFECFEF)],
),
DictionaryEntry(
word: '熟能生巧',
pinyin: 'shú néng shēng qiǎo',
partOfSpeech: '成语',
definition: '熟练了就能产生巧办法。',
examples: [
'熟能生巧,多练习就能掌握。',
'熟能生巧是学习的秘诀。',
'熟能生巧,不断重复就能提高。',
'熟能生巧,实践出真知。'
],
relatedWords: ['勤学苦练', '学以致用', '循序渐进'],
similarWords: ['一曝十寒', '眼高手低', '纸上谈兵'],
type: '成语',
gradient: [const Color(0xFF89F7FE), const Color(0xFF66A6FF)],
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('每日成语'),
backgroundColor: const Color(0xFFA8FF78),
foregroundColor: Colors.white,
),
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _dailyIdioms.length,
itemBuilder: (context, index) {
final idiom = _dailyIdioms[index];
return Container(
margin: const EdgeInsets.only(bottom: 16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: idiom.gradient,
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: idiom.gradient[0].withOpacity(0.3),
blurRadius: 10,
offset: const Offset(0, 5),
),
],
),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DictionaryDetailScreen(entry: idiom),
),
);
},
borderRadius: BorderRadius.circular(20),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
idiom.word,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
Flexible(
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: BorderRadius.circular(12),
),
child: Text(
idiom.pinyin,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
),
),
],
),
const SizedBox(height: 12),
Text(
idiom.definition,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: Colors.white.withOpacity(0.9),
height: 1.5,
),
),
],
),
),
),
);
},
),
);
}
}
// 数据模型类
class QuizQuestion {
final String idiom;
final List<String> options;
final int correctAnswer;
QuizQuestion({
required this.idiom,
required this.options,
required this.correctAnswer,
});
}
class FillBlankQuestion {
final String question;
final String answer;
final String hint;
FillBlankQuestion({
required this.question,
required this.answer,
required this.hint,
});
}
// 字典条目数据模型
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,
});
}
更多推荐



所有评论(0)