Flutter 框架跨平台鸿蒙开发 - 观影记录账本应用开发教程
这是一款功能全面的观影记录账本应用,为电影爱好者提供专业的观影管理和统计分析功能。应用采用Material Design 3设计风格,支持电影信息记录、观影状态管理、评分系统、统计分析、搜索筛选等功能,界面简洁美观,操作流畅便捷,是管理个人观影历程的理想工具。运行效果图MovieRecordHomePageMovieListPageStatisticsPageWishlistPageSetting
Flutter观影记录账本应用开发教程
项目简介
这是一款功能全面的观影记录账本应用,为电影爱好者提供专业的观影管理和统计分析功能。应用采用Material Design 3设计风格,支持电影信息记录、观影状态管理、评分系统、统计分析、搜索筛选等功能,界面简洁美观,操作流畅便捷,是管理个人观影历程的理想工具。
运行效果图


核心特性
- 完整电影信息:标题、导演、演员、年份、时长、类型等详细信息
- 观影状态管理:已观看、正在看、想看、弃坑四种状态
- 评分系统:1-10分精确评分和观影感受记录
- 智能搜索:支持标题、导演、演员的模糊搜索
- 多维筛选:按类型、状态、评分等条件筛选
- 统计分析:观影数据的可视化统计和趋势分析
- 观影清单:想看和在看电影的专门管理
- 数据管理:支持数据备份、恢复和导入导出
- 个性化设置:灵活的排序和显示选项
- 精美界面:渐变设计和流畅的用户体验
技术栈
- Flutter 3.x
- Material Design 3
- 数据持久化
- 动画控制器(AnimationController)
- 状态管理
- 图表统计
项目架构
数据模型设计
MovieRecord(电影记录模型)
class MovieRecord {
final String id; // 唯一标识
final String title; // 电影标题
final String director; // 导演
final List<String> genres; // 类型列表
final int year; // 年份
final int duration; // 时长(分钟)
final double rating; // 评分(1-10)
final String review; // 观影感受
final DateTime watchDate; // 观看日期
final String poster; // 海报URL
final List<String> actors; // 演员列表
final String country; // 国家/地区
final String language; // 语言
final WatchStatus status; // 观看状态
final List<String> tags; // 标签列表
const MovieRecord({
required this.id,
required this.title,
required this.director,
required this.genres,
required this.year,
required this.duration,
required this.rating,
required this.review,
required this.watchDate,
this.poster = '',
this.actors = const [],
this.country = '',
this.language = '',
this.status = WatchStatus.watched,
this.tags = const [],
});
}
设计要点:
- id使用时间戳确保唯一性
- genres和actors使用列表支持多个值
- rating使用double支持精确评分
- watchDate记录具体观看时间
- status使用枚举确保数据一致性
WatchStatus(观看状态枚举)
enum WatchStatus {
watched, // 已观看
watching, // 正在观看
planned, // 计划观看
dropped, // 弃坑
}
SortBy(排序方式枚举)
enum SortBy {
watchDate, // 观看日期
rating, // 评分
title, // 标题
year, // 年份
duration, // 时长
}
数据关系图
统计数据模型
// 统计信息
int _totalMovies = 0; // 总观影数
int _totalHours = 0; // 总观影时长
double _averageRating = 0.0; // 平均评分
Map<String, int> _genreStats = {}; // 类型统计
// 筛选条件
String _searchQuery = ''; // 搜索关键词
List<String> _selectedGenres = []; // 选中的类型
WatchStatus? _selectedStatus; // 选中的状态
SortBy _sortBy = SortBy.watchDate; // 排序方式
bool _sortAscending = false; // 排序方向
核心功能实现
1. 电影记录管理
实现完整的电影信息录入和管理功能。
void _showAddMovieDialog() {
showDialog(
context: context,
builder: (context) => _AddMovieDialog(
onSave: (movie) {
setState(() {
_movieRecords.add(movie);
});
_updateStatistics();
},
),
);
}
class _AddMovieDialog extends StatefulWidget {
final Function(MovieRecord) onSave;
const _AddMovieDialog({required this.onSave});
State<_AddMovieDialog> createState() => _AddMovieDialogState();
}
class _AddMovieDialogState extends State<_AddMovieDialog> {
final _formKey = GlobalKey<FormState>();
final _titleController = TextEditingController();
final _directorController = TextEditingController();
final _yearController = TextEditingController();
final _durationController = TextEditingController();
final _reviewController = TextEditingController();
double _rating = 8.0;
List<String> _selectedGenres = [];
WatchStatus _status = WatchStatus.watched;
DateTime _watchDate = DateTime.now();
void _saveMovie() {
if (_formKey.currentState!.validate()) {
final movie = MovieRecord(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: _titleController.text,
director: _directorController.text,
genres: _selectedGenres,
year: int.tryParse(_yearController.text) ?? DateTime.now().year,
duration: int.tryParse(_durationController.text) ?? 120,
rating: _rating,
review: _reviewController.text,
watchDate: _watchDate,
status: _status,
);
widget.onSave(movie);
Navigator.pop(context);
}
}
}
功能特点:
- 表单验证:必填字段的完整性检查
- 类型选择:多选FilterChip支持多种类型
- 评分滑块:直观的1-10分评分选择
- 日期选择:DatePicker选择观看日期
- 状态管理:下拉选择观看状态
2. 搜索和筛选系统
实现强大的搜索和多维度筛选功能。
List<MovieRecord> _getFilteredMovies() {
var filtered = _movieRecords.where((movie) {
// 搜索过滤
if (_searchQuery.isNotEmpty) {
final query = _searchQuery.toLowerCase();
if (!movie.title.toLowerCase().contains(query) &&
!movie.director.toLowerCase().contains(query) &&
!movie.actors.any((actor) => actor.toLowerCase().contains(query))) {
return false;
}
}
// 类型过滤
if (_selectedGenres.isNotEmpty) {
if (!movie.genres.any((genre) => _selectedGenres.contains(genre))) {
return false;
}
}
// 状态过滤
if (_selectedStatus != null && movie.status != _selectedStatus) {
return false;
}
return true;
}).toList();
// 排序
filtered.sort((a, b) {
switch (_sortBy) {
case SortBy.watchDate:
return _sortAscending
? a.watchDate.compareTo(b.watchDate)
: b.watchDate.compareTo(a.watchDate);
case SortBy.rating:
return _sortAscending
? a.rating.compareTo(b.rating)
: b.rating.compareTo(a.rating);
case SortBy.title:
return _sortAscending
? a.title.compareTo(b.title)
: b.title.compareTo(a.title);
case SortBy.year:
return _sortAscending
? a.year.compareTo(b.year)
: b.year.compareTo(a.year);
case SortBy.duration:
return _sortAscending
? a.duration.compareTo(b.duration)
: b.duration.compareTo(a.duration);
}
});
return filtered;
}
Widget _buildSearchAndFilter() {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
children: [
TextField(
decoration: const InputDecoration(
hintText: '搜索电影标题、导演或演员...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildFilterChip('筛选类型',
_selectedGenres.isEmpty ? '全部' : _selectedGenres.join(', '),
() => _showGenreFilter()),
),
const SizedBox(width: 8),
Expanded(
child: _buildFilterChip('观看状态',
_getStatusName(_selectedStatus),
() => _showStatusFilter()),
),
const SizedBox(width: 8),
Expanded(
child: _buildFilterChip('排序',
_getSortName(_sortBy),
() => _showSortOptions()),
),
],
),
],
),
);
}
搜索筛选特点:
- 模糊搜索:支持标题、导演、演员的模糊匹配
- 多维筛选:类型、状态的组合筛选
- 灵活排序:多种排序方式和升降序切换
- 实时更新:输入即时更新搜索结果
3. 统计分析系统
提供详细的观影数据统计和可视化分析。
void _updateStatistics() {
setState(() {
_totalMovies = _movieRecords.where((m) => m.status == WatchStatus.watched).length;
_totalHours = _movieRecords
.where((m) => m.status == WatchStatus.watched)
.fold(0, (sum, movie) => sum + movie.duration) ~/ 60;
final watchedMovies = _movieRecords.where((m) => m.status == WatchStatus.watched).toList();
if (watchedMovies.isNotEmpty) {
_averageRating = watchedMovies
.map((m) => m.rating)
.reduce((a, b) => a + b) / watchedMovies.length;
}
_genreStats.clear();
for (final movie in watchedMovies) {
for (final genre in movie.genres) {
_genreStats[genre] = (_genreStats[genre] ?? 0) + 1;
}
}
});
}
Widget _buildGenreStats() {
if (_genreStats.isEmpty) {
return const SizedBox.shrink();
}
final sortedGenres = _genreStats.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'类型偏好',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
...sortedGenres.take(5).map((entry) {
final percentage = (entry.value / _totalMovies * 100).round();
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(entry.key),
Text('${entry.value}部 ($percentage%)'),
],
),
const SizedBox(height: 4),
LinearProgressIndicator(
value: entry.value / _totalMovies,
backgroundColor: Colors.grey.shade200,
valueColor: AlwaysStoppedAnimation<Color>(Colors.indigo.shade400),
),
],
),
);
}),
],
),
),
);
}
Widget _buildRatingDistribution() {
final watchedMovies = _movieRecords.where((m) => m.status == WatchStatus.watched).toList();
final ratingRanges = <String, int>{
'9-10分': 0,
'8-9分': 0,
'7-8分': 0,
'6-7分': 0,
'6分以下': 0,
};
for (final movie in watchedMovies) {
if (movie.rating >= 9) {
ratingRanges['9-10分'] = ratingRanges['9-10分']! + 1;
} else if (movie.rating >= 8) {
ratingRanges['8-9分'] = ratingRanges['8-9分']! + 1;
} else if (movie.rating >= 7) {
ratingRanges['7-8分'] = ratingRanges['7-8分']! + 1;
} else if (movie.rating >= 6) {
ratingRanges['6-7分'] = ratingRanges['6-7分']! + 1;
} else {
ratingRanges['6分以下'] = ratingRanges['6分以下']! + 1;
}
}
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'评分分布',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
...ratingRanges.entries.map((entry) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
SizedBox(
width: 60,
child: Text(entry.key),
),
Expanded(
child: LinearProgressIndicator(
value: _totalMovies > 0 ? entry.value / _totalMovies : 0,
backgroundColor: Colors.grey.shade200,
valueColor: AlwaysStoppedAnimation<Color>(Colors.amber.shade600),
),
),
const SizedBox(width: 8),
Text('${entry.value}部'),
],
),
);
}),
],
),
),
);
}
统计分析特点:
- 多维统计:观影数量、时长、评分等多维度统计
- 类型偏好:可视化显示最喜欢的电影类型
- 评分分布:直观展示评分区间分布
- 年度统计:按年份统计观影数量趋势
4. 电影卡片展示
设计美观的电影信息卡片展示。
Widget _buildMovieCard(MovieRecord movie) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: InkWell(
onTap: () => _showMovieDetails(movie),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 海报占位符
Container(
width: 60,
height: 90,
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(8),
),
child: movie.poster.isEmpty
? const Icon(Icons.movie, size: 30, color: Colors.grey)
: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
movie.poster,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.movie, size: 30, color: Colors.grey);
},
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
movie.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _getStatusColor(movie.status),
borderRadius: BorderRadius.circular(12),
),
child: Text(
_getStatusName(movie.status),
style: const TextStyle(
fontSize: 10,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
],
),
const SizedBox(height: 4),
Text(
'导演: ${movie.director}',
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade600,
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
'${movie.year}',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
const SizedBox(width: 8),
Text(
'${movie.duration}分钟',
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
const Spacer(),
Row(
children: [
const Icon(Icons.star, size: 16, color: Colors.amber),
const SizedBox(width: 4),
Text(
movie.rating.toStringAsFixed(1),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
],
),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 4,
runSpacing: 4,
children: movie.genres.map((genre) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: Colors.indigo.shade50,
borderRadius: BorderRadius.circular(12),
),
child: Text(
genre,
style: TextStyle(
fontSize: 10,
color: Colors.indigo.shade700,
),
),
);
}).toList(),
),
if (movie.review.isNotEmpty) ...[
const SizedBox(height: 8),
Text(
movie.review,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade700,
fontStyle: FontStyle.italic,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
],
),
),
),
);
}
卡片设计特点:
- 信息层次:清晰的信息层次和视觉重点
- 状态标识:彩色标签显示观看状态
- 评分展示:星级图标和数字评分
- 类型标签:彩色标签展示电影类型
- 交互反馈:点击效果和详情跳转
5. 观影清单管理
专门的观影计划和进度管理功能。
Widget _buildWishlistContent() {
final plannedMovies = _movieRecords.where((m) => m.status == WatchStatus.planned).toList();
final watchingMovies = _movieRecords.where((m) => m.status == WatchStatus.watching).toList();
return DefaultTabController(
length: 2,
child: Column(
children: [
const TabBar(
tabs: [
Tab(text: '想看'),
Tab(text: '在看'),
],
),
Expanded(
child: TabBarView(
children: [
_buildMovieStatusList(plannedMovies, '暂无想看的电影'),
_buildMovieStatusList(watchingMovies, '暂无正在观看的电影'),
],
),
),
],
),
);
}
Widget _buildMovieStatusList(List<MovieRecord> movies, String emptyMessage) {
if (movies.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.bookmark_outline, size: 64, color: Colors.grey.shade400),
const SizedBox(height: 16),
Text(emptyMessage, style: TextStyle(fontSize: 16, color: Colors.grey.shade600)),
],
),
);
}
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: movies.length,
itemBuilder: (context, index) {
return _buildMovieCard(movies[index]);
},
);
}
清单管理特点:
- 分类管理:想看和在看的分别管理
- 状态切换:便捷的状态更新功能
- 进度跟踪:观影进度的可视化展示
- 空状态处理:友好的空列表提示
6. 电影详情展示
详细的电影信息展示和操作功能。
void _showMovieDetails(MovieRecord movie) {
showDialog(
context: context,
builder: (context) => _MovieDetailsDialog(
movie: movie,
onEdit: (editedMovie) {
setState(() {
final index = _movieRecords.indexWhere((m) => m.id == movie.id);
if (index != -1) {
_movieRecords[index] = editedMovie;
}
});
_updateStatistics();
},
onDelete: () {
setState(() {
_movieRecords.removeWhere((m) => m.id == movie.id);
});
_updateStatistics();
},
),
);
}
class _MovieDetailsDialog extends StatelessWidget {
final MovieRecord movie;
final Function(MovieRecord) onEdit;
final VoidCallback onDelete;
const _MovieDetailsDialog({
required this.movie,
required this.onEdit,
required this.onDelete,
});
Widget build(BuildContext context) {
return AlertDialog(
title: Text(movie.title),
content: SizedBox(
width: 400,
height: 500,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDetailRow('导演', movie.director),
_buildDetailRow('年份', '${movie.year}'),
_buildDetailRow('时长', '${movie.duration}分钟'),
_buildDetailRow('评分', '${movie.rating}/10'),
_buildDetailRow('状态', _getStatusName(movie.status)),
_buildDetailRow('观看日期', _formatDate(movie.watchDate)),
if (movie.genres.isNotEmpty)
_buildDetailRow('类型', movie.genres.join(', ')),
if (movie.actors.isNotEmpty)
_buildDetailRow('演员', movie.actors.join(', ')),
if (movie.country.isNotEmpty)
_buildDetailRow('国家', movie.country),
if (movie.language.isNotEmpty)
_buildDetailRow('语言', movie.language),
if (movie.review.isNotEmpty) ...[
const SizedBox(height: 16),
const Text('观影感受:', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text(movie.review),
],
],
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('关闭'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
_showEditDialog(context);
},
child: const Text('编辑'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
_showDeleteConfirmation(context);
},
child: const Text('删除', style: TextStyle(color: Colors.red)),
),
],
);
}
Widget _buildDetailRow(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 80,
child: Text(
'$label:',
style: const TextStyle(fontWeight: FontWeight.w500),
),
),
Expanded(child: Text(value)),
],
),
);
}
}
详情展示特点:
- 完整信息:展示所有电影相关信息
- 操作按钮:编辑、删除等操作功能
- 格式化显示:清晰的标签-值格式
- 滚动支持:长内容的滚动查看
UI组件设计
1. 导航栏设计
采用Material Design 3的NavigationBar组件,提供清晰的页面导航。
NavigationBar(
selectedIndex: _selectedIndex,
onDestinationSelected: (index) {
setState(() {
_selectedIndex = index;
});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.movie_outlined),
selectedIcon: Icon(Icons.movie),
label: '观影记录',
),
NavigationDestination(
icon: Icon(Icons.analytics_outlined),
selectedIcon: Icon(Icons.analytics),
label: '统计分析',
),
NavigationDestination(
icon: Icon(Icons.bookmark_outlined),
selectedIcon: Icon(Icons.bookmark),
label: '观影清单',
),
NavigationDestination(
icon: Icon(Icons.settings_outlined),
selectedIcon: Icon(Icons.settings),
label: '设置',
),
],
)
设计特点:
- 图标状态:选中和未选中状态的不同图标
- 标签清晰:简洁明了的功能标签
- 视觉反馈:选中状态的视觉突出
- 一致性:统一的设计语言
2. 页面头部设计
每个页面都有独特的渐变色头部,增强视觉层次。
Widget _buildMovieListHeader() {
return Container(
padding: const EdgeInsets.fromLTRB(16, 48, 16, 16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.indigo.shade600, Colors.indigo.shade400],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
children: [
// 标题和图标
Row(
children: [
const Icon(Icons.movie, color: Colors.white, size: 32),
const SizedBox(width: 12),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'观影记录账本',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
Text(
'记录每一部精彩的电影',
style: TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
],
),
),
],
),
const SizedBox(height: 16),
// 统计卡片
Row(
children: [
Expanded(
child: _buildHeaderCard(
'观影总数',
'$_totalMovies',
Icons.movie_creation,
),
),
// 更多统计卡片...
],
),
],
),
);
}
头部特色:
- 渐变背景:美观的渐变色彩
- 统计展示:关键数据的快速预览
- 层次分明:清晰的信息层次结构
- 响应式:适配不同屏幕尺寸
3. 卡片组件设计
统一的卡片设计语言,提供一致的用户体验。
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color.withValues(alpha: 0.3)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color, size: 24),
const SizedBox(height: 8),
Text(
value,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
title,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade600,
),
),
],
),
);
}
卡片特点:
- 圆角设计:现代化的圆角边框
- 颜色主题:统一的颜色体系
- 阴影效果:适度的阴影增强层次
- 内容布局:合理的内容间距和对齐
4. 表单组件设计
用户友好的表单输入体验。
TextFormField(
controller: _titleController,
decoration: const InputDecoration(
labelText: '电影标题 *',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.movie),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入电影标题';
}
return null;
},
)
表单特色:
- 清晰标签:明确的字段标识
- 输入验证:实时的输入验证反馈
- 图标提示:直观的功能图标
- 错误处理:友好的错误提示信息
功能扩展建议
1. 数据同步功能
实现云端数据同步,支持多设备数据共享。
class CloudSyncService {
// 上传数据到云端
Future<void> uploadData(List<MovieRecord> records) async {
// 实现云端上传逻辑
}
// 从云端下载数据
Future<List<MovieRecord>> downloadData() async {
// 实现云端下载逻辑
return [];
}
// 数据同步
Future<void> syncData() async {
// 实现增量同步逻辑
}
}
扩展价值:
- 多设备同步:手机、平板、电脑数据一致
- 数据安全:云端备份防止数据丢失
- 离线支持:离线操作,联网时自动同步
2. 社交分享功能
添加观影记录的社交分享能力。
class SocialShareService {
// 分享到社交平台
Future<void> shareMovie(MovieRecord movie) async {
final text = '我刚看了《${movie.title}》,评分${movie.rating}/10分!${movie.review}';
// 调用分享插件
}
// 生成观影海报
Future<Uint8List> generateMoviePoster(MovieRecord movie) async {
// 生成精美的观影分享海报
return Uint8List(0);
}
}
3. 推荐系统
基于观影历史的智能推荐功能。
class RecommendationEngine {
// 基于类型偏好推荐
List<String> recommendByGenre(List<MovieRecord> history) {
// 分析用户类型偏好,推荐相似电影
return [];
}
// 基于评分推荐
List<String> recommendByRating(List<MovieRecord> history) {
// 分析高评分电影特征,推荐类似作品
return [];
}
}
4. 观影计划提醒
智能的观影计划和提醒功能。
class WatchReminderService {
// 设置观影提醒
Future<void> setWatchReminder(MovieRecord movie, DateTime reminderTime) async {
// 设置本地通知提醒
}
// 智能推荐观影时间
DateTime suggestWatchTime(MovieRecord movie) {
// 基于电影时长和用户习惯推荐观影时间
return DateTime.now();
}
}
5. 数据导入导出
支持多种格式的数据导入导出。
class DataImportExport {
// 导出为CSV格式
Future<String> exportToCSV(List<MovieRecord> records) async {
// 生成CSV格式数据
return '';
}
// 导出为JSON格式
Future<String> exportToJSON(List<MovieRecord> records) async {
// 生成JSON格式数据
return '';
}
// 从豆瓣导入
Future<List<MovieRecord>> importFromDouban(String userId) async {
// 从豆瓣API导入观影记录
return [];
}
}
6. 高级统计分析
更丰富的数据分析和可视化功能。
class AdvancedAnalytics {
// 观影趋势分析
Map<String, dynamic> analyzeTrends(List<MovieRecord> records) {
// 分析观影频率、类型变化等趋势
return {};
}
// 导演偏好分析
Map<String, int> analyzeDirectorPreference(List<MovieRecord> records) {
// 统计最喜欢的导演
return {};
}
// 观影时间分析
Map<String, dynamic> analyzeWatchingTime(List<MovieRecord> records) {
// 分析观影时间分布
return {};
}
}
7. 电影信息自动获取
集成电影数据库API,自动获取电影信息。
class MovieInfoService {
// 搜索电影信息
Future<List<MovieInfo>> searchMovie(String title) async {
// 调用TMDB或豆瓣API搜索电影
return [];
}
// 获取详细信息
Future<MovieInfo> getMovieDetails(String movieId) async {
// 获取电影详细信息
return MovieInfo();
}
}
8. 个性化主题
支持多种主题和个性化设置。
class ThemeService {
// 切换主题
void switchTheme(ThemeMode mode) {
// 切换亮色/暗色主题
}
// 自定义颜色
void setCustomColor(Color primaryColor) {
// 设置自定义主色调
}
}
性能优化建议
1. 列表性能优化
对于大量电影记录的高效渲染。
// 使用ListView.builder进行懒加载
ListView.builder(
itemCount: filteredMovies.length,
itemBuilder: (context, index) {
return _buildMovieCard(filteredMovies[index]);
},
// 添加缓存范围
cacheExtent: 1000,
)
// 实现虚拟滚动
class VirtualizedMovieList extends StatelessWidget {
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => _buildMovieCard(movies[index]),
childCount: movies.length,
),
),
],
);
}
}
2. 图片加载优化
优化海报图片的加载和缓存。
// 使用cached_network_image插件
CachedNetworkImage(
imageUrl: movie.poster,
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.movie),
fit: BoxFit.cover,
memCacheWidth: 200, // 限制内存中的图片尺寸
memCacheHeight: 300,
)
// 预加载关键图片
void preloadImages() {
for (final movie in _movieRecords.take(10)) {
if (movie.poster.isNotEmpty) {
precacheImage(NetworkImage(movie.poster), context);
}
}
}
3. 数据存储优化
高效的本地数据存储方案。
// 使用Hive进行高性能本地存储
class MovieStorage {
static late Box<MovieRecord> _movieBox;
static Future<void> init() async {
Hive.registerAdapter(MovieRecordAdapter());
_movieBox = await Hive.openBox<MovieRecord>('movies');
}
static Future<void> saveMovie(MovieRecord movie) async {
await _movieBox.put(movie.id, movie);
}
static List<MovieRecord> getAllMovies() {
return _movieBox.values.toList();
}
}
// 实现增量更新
class IncrementalUpdate {
static void updateStatistics(MovieRecord newMovie) {
// 只更新受影响的统计数据,避免全量重计算
}
}
4. 搜索性能优化
实现高效的搜索算法。
class SearchOptimizer {
// 建立搜索索引
static Map<String, List<MovieRecord>> _searchIndex = {};
static void buildSearchIndex(List<MovieRecord> movies) {
_searchIndex.clear();
for (final movie in movies) {
// 为标题、导演、演员建立索引
_addToIndex(movie.title.toLowerCase(), movie);
_addToIndex(movie.director.toLowerCase(), movie);
for (final actor in movie.actors) {
_addToIndex(actor.toLowerCase(), movie);
}
}
}
static List<MovieRecord> search(String query) {
final results = <MovieRecord>{};
final keywords = query.toLowerCase().split(' ');
for (final keyword in keywords) {
final matches = _searchIndex[keyword] ?? [];
results.addAll(matches);
}
return results.toList();
}
}
5. 内存管理优化
避免内存泄漏和过度使用。
class MemoryManager {
// 及时释放资源
void dispose() {
_fadeController.dispose();
_scaleController.dispose();
_titleController.dispose();
// 释放所有控制器和监听器
super.dispose();
}
// 使用弱引用避免循环引用
WeakReference<MovieRecord>? _currentMovie;
// 限制缓存大小
static const int maxCacheSize = 100;
static final LRUCache<String, MovieRecord> _cache =
LRUCache<String, MovieRecord>(maxCacheSize);
}
测试建议
1. 单元测试
测试核心业务逻辑和数据处理。
// test/movie_record_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:movie_record_app/models/movie_record.dart';
void main() {
group('MovieRecord Tests', () {
test('should create movie record with required fields', () {
final movie = MovieRecord(
id: '1',
title: '测试电影',
director: '测试导演',
genres: ['剧情'],
year: 2024,
duration: 120,
rating: 8.5,
review: '很好看',
watchDate: DateTime.now(),
);
expect(movie.title, '测试电影');
expect(movie.rating, 8.5);
expect(movie.status, WatchStatus.watched);
});
test('should validate rating range', () {
expect(() => MovieRecord(
id: '1',
title: '测试电影',
director: '测试导演',
genres: ['剧情'],
year: 2024,
duration: 120,
rating: 11.0, // 超出范围
review: '很好看',
watchDate: DateTime.now(),
), throwsArgumentError);
});
});
}
2. Widget测试
测试UI组件的渲染和交互。
// test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:movie_record_app/main.dart';
void main() {
testWidgets('should display movie list', (WidgetTester tester) async {
await tester.pumpWidget(const MovieRecordApp());
// 验证导航栏存在
expect(find.text('观影记录'), findsOneWidget);
expect(find.text('统计分析'), findsOneWidget);
// 验证添加按钮存在
expect(find.byType(FloatingActionButton), findsOneWidget);
// 点击添加按钮
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
// 验证对话框打开
expect(find.text('添加观影记录'), findsOneWidget);
});
testWidgets('should filter movies by search query', (WidgetTester tester) async {
await tester.pumpWidget(const MovieRecordApp());
// 输入搜索关键词
await tester.enterText(find.byType(TextField), '肖申克');
await tester.pumpAndSettle();
// 验证搜索结果
expect(find.text('肖申克的救赎'), findsOneWidget);
});
}
3. 集成测试
测试完整的用户流程。
// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:movie_record_app/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Movie Record App Integration Tests', () {
testWidgets('complete movie adding flow', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
// 点击添加按钮
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
// 填写电影信息
await tester.enterText(find.byKey(const Key('title_field')), '新电影');
await tester.enterText(find.byKey(const Key('director_field')), '新导演');
// 选择类型
await tester.tap(find.text('剧情'));
// 保存电影
await tester.tap(find.text('保存'));
await tester.pumpAndSettle();
// 验证电影已添加
expect(find.text('新电影'), findsOneWidget);
});
});
}
4. 性能测试
测试应用性能和响应速度。
// test/performance_test.dart
import 'package:flutter_test/flutter_test.dart';
void main() {
group('Performance Tests', () {
test('should handle large dataset efficiently', () {
final stopwatch = Stopwatch()..start();
// 创建大量测试数据
final movies = List.generate(1000, (index) => MovieRecord(
id: '$index',
title: '电影$index',
director: '导演$index',
genres: ['剧情'],
year: 2024,
duration: 120,
rating: 8.0,
review: '测试评论',
watchDate: DateTime.now(),
));
// 测试搜索性能
final filteredMovies = movies.where((movie) =>
movie.title.contains('100')).toList();
stopwatch.stop();
// 验证性能要求(例如:1000条记录搜索应在100ms内完成)
expect(stopwatch.elapsedMilliseconds, lessThan(100));
expect(filteredMovies.length, greaterThan(0));
});
});
}
部署指南
1. Android部署
配置Android应用的构建和发布。
# android/app/build.gradle
android {
compileSdkVersion 34
defaultConfig {
applicationId "com.example.movie_record_app"
minSdkVersion 21
targetSdkVersion 34
versionCode 1
versionName "1.0.0"
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
构建命令:
# 构建APK
flutter build apk --release
# 构建App Bundle
flutter build appbundle --release
# 安装到设备
flutter install --release
2. iOS部署
配置iOS应用的构建和发布。
<!-- ios/Runner/Info.plist -->
<dict>
<key>CFBundleName</key>
<string>观影记录账本</string>
<key>CFBundleDisplayName</key>
<string>观影记录账本</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
</dict>
构建命令:
# 构建iOS应用
flutter build ios --release
# 使用Xcode构建
open ios/Runner.xcworkspace
3. Web部署
配置Web应用的构建和部署。
# web/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>观影记录账本</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<script src="main.dart.js" type="application/javascript"></script>
</body>
</html>
构建和部署:
# 构建Web应用
flutter build web --release
# 部署到Firebase Hosting
firebase deploy --only hosting
# 部署到GitHub Pages
gh-pages -d build/web
4. 桌面应用部署
配置Windows、macOS、Linux桌面应用。
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
window_manager: ^0.3.7 # 窗口管理
dev_dependencies:
msix: ^3.16.7 # Windows打包
构建命令:
# Windows
flutter build windows --release
# macOS
flutter build macos --release
# Linux
flutter build linux --release
5. 持续集成/持续部署 (CI/CD)
使用GitHub Actions自动化构建和部署。
# .github/workflows/build.yml
name: Build and Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.16.0'
- name: Install dependencies
run: flutter pub get
- name: Run tests
run: flutter test
- name: Build APK
run: flutter build apk --release
- name: Build Web
run: flutter build web --release
- name: Deploy to Firebase
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
projectId: movie-record-app
项目总结
技术成果
本项目成功实现了一个功能完整的观影记录账本应用,具备以下技术特点:
- 架构设计:采用清晰的分层架构,数据模型、业务逻辑、UI组件分离明确
- 状态管理:使用StatefulWidget进行本地状态管理,响应式更新UI
- 数据处理:实现了完整的CRUD操作,支持复杂的搜索筛选和统计分析
- 用户体验:Material Design 3设计风格,流畅的动画和交互效果
- 性能优化:列表懒加载、图片缓存、搜索优化等性能提升措施
功能亮点
- 全面的电影信息管理:支持详细的电影信息录入和管理
- 智能搜索筛选:多维度的搜索和筛选功能
- 丰富的统计分析:可视化的观影数据统计和趋势分析
- 灵活的状态管理:支持多种观影状态的管理和切换
- 优雅的界面设计:现代化的UI设计和良好的用户体验
学习价值
通过本项目的开发,可以学习到:
- Flutter基础:Widget组件、状态管理、导航路由等核心概念
- 数据建模:复杂数据结构的设计和管理
- UI设计:Material Design设计规范的实际应用
- 性能优化:Flutter应用性能优化的实践方法
- 项目架构:中等复杂度应用的架构设计思路
扩展方向
本应用具有良好的扩展性,可以在以下方向继续发展:
- 云端同步:实现多设备数据同步功能
- 社交功能:添加观影记录分享和社区交流
- AI推荐:基于机器学习的个性化推荐系统
- 数据分析:更深入的观影习惯分析和洞察
- 跨平台:扩展到更多平台和设备
开发建议
对于想要学习或改进本项目的开发者:
- 循序渐进:从基础功能开始,逐步添加复杂特性
- 代码规范:保持良好的代码风格和注释习惯
- 测试驱动:编写充分的单元测试和集成测试
- 用户反馈:重视用户体验,持续优化界面和交互
- 技术更新:关注Flutter生态的最新发展,及时更新技术栈
本项目展示了Flutter在移动应用开发中的强大能力,通过合理的架构设计和精心的功能实现,创造了一个实用且美观的观影记录管理工具。希望这个项目能够为Flutter学习者提供有价值的参考和启发。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)