Flutter for OpenHarmony 剧本杀组队App实战14:剧本库列表实现
剧本库列表页面设计与实现摘要 本文详细介绍了剧本库列表页面的设计与实现方案。页面核心功能包括:顶部类型筛选栏(支持情感本、恐怖本等6类横向滚动筛选)、剧本卡片列表展示(含名称、类型、人数、时长等关键信息)、高级筛选入口及详情页跳转。页面采用Flutter框架开发,使用StatefulWidget管理筛选状态,ListView.builder实现懒加载优化性能。设计上遵循信息完整、视觉清晰原则,通过
引言
剧本库是用户浏览和选择剧本的核心页面,需要支持分类筛选和列表展示。本篇将详细讲解如何实现一个功能完善的剧本库列表页面,包括顶部类型筛选栏、剧本卡片列表、筛选功能等核心功能。通过这个页面,用户可以快速找到感兴趣的剧本,了解剧本的详细信息,并进行预订。
功能需求分析
剧本库页面的核心功能
- 类型筛选栏:支持按剧本类型(情感本、恐怖本、机制本等)进行筛选,使用横向滚动的ChoiceChip组件
- 剧本卡片列表:展示所有剧本的信息,包括名称、描述、类型、人数、时长、评分和价格
- 剧本信息展示:每个卡片包含剧本封面、名称、描述、标签和价格等关键信息
- 点击跳转详情:点击剧本卡片可以跳转到剧本详情页面查看更多信息
- 高级筛选:AppBar右侧提供高级筛选按钮,支持更复杂的筛选条件
用户交互需求
- 用户可以快速浏览所有剧本
- 用户可以按类型筛选剧本
- 用户可以查看剧本的基本信息和评分
- 用户可以了解剧本的价格和参与人数
- 用户可以点击进入剧本详情页面
剧本库设计的关键要素
1. 信息架构设计
剧本库的信息架构应该清晰合理,让用户能够快速找到想要的剧本。关键要素包括:
- 分类导航:按剧本类型分类,方便用户快速定位
- 搜索功能:支持按名称搜索剧本
- 排序功能:支持按评分、价格、热度等排序
- 筛选功能:支持多条件组合筛选
2. 卡片设计原则
剧本卡片是用户了解剧本的第一印象,设计应该遵循以下原则:
- 信息完整:包含用户做决策所需的所有关键信息
- 视觉清晰:使用合理的排版和颜色区分不同信息
- 操作便捷:支持快速点击进入详情或收藏
- 响应式设计:在不同屏幕尺寸下都能正常显示
3. 性能优化考虑
当剧本数量较多时,需要考虑性能优化:
- 使用ListView.builder实现懒加载
- 缓存剧本数据到本地
- 支持分页加载
- 优化图片加载
核心代码实现
第一部分:导入依赖与类定义
在开始编写剧本库列表页面之前,我们需要导入必要的依赖包。Flutter的Material库提供了基础的UI组件,
GetX框架提供了便捷的路由导航功能。我们还需要导入剧本详情页和筛选页面,以便用户进行相关操作。
这种模块化的导入方式让代码结构更加清晰,也方便后续的维护和扩展。合理的依赖管理是构建大型应用的基础。
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'script_detail_page.dart';
import '../filter/filter_page.dart';
ScriptListPage类继承自StatefulWidget,因为页面需要管理筛选条件的状态。当用户选择不同的类型时,
页面需要重新构建列表以显示筛选后的结果。StatefulWidget允许我们通过setState方法更新状态并触发UI重新构建。
这是处理用户交互和动态数据的标准方式。super.key参数用于Widget的唯一标识,在Widget树的diff算法中起着重要作用。
class ScriptListPage extends StatefulWidget {
ScriptListPage({super.key});
State<ScriptListPage> createState() => _ScriptListPageState();
}
_ScriptListPageState类中定义了两个重要的状态变量。_selectedType用于记录当前选中的剧本类型,
初始值为"全部"表示显示所有剧本。_types列表定义了所有可用的剧本类型,包括"全部"、“情感本”、"恐怖本"等。
这些类型是剧本库的核心分类维度,用户可以通过选择不同的类型快速找到感兴趣的剧本。
这种设计让筛选功能简洁而高效。
class _ScriptListPageState extends State<ScriptListPage> {
String _selectedType = '全部';
final List<String> _types = ['全部', '情感本', '恐怖本', '机制本', '欢乐本', '硬核本'];
_scripts列表定义了应用中的所有剧本数据。每个剧本使用Map<String, dynamic>类型存储,包含id、name、type、
players、duration、rating、price和desc等字段。这些字段涵盖了用户了解剧本所需的所有关键信息。
在实际项目中,这些数据应该从服务器获取,这里使用静态数据进行演示。数据结构的设计要考虑到UI展示的需求。
final List<Map<String, dynamic>> _scripts = [
{'id': '1', 'name': '年轮', 'type': '情感本', 'players': '6人', 'duration': '4-5h', 'rating': 9.2, 'price': 88, 'desc': '一段跨越时空的爱情故事'},
{'id': '2', 'name': '古木吟', 'type': '恐怖本', 'players': '7人', 'duration': '5-6h', 'rating': 9.5, 'price': 98, 'desc': '深山古宅的惊悚之夜'},
继续添加更多的剧本数据。"你好"是一个情感本,游戏时长较短只有3-4小时,适合时间有限的玩家。
"云使"是一个机制本,游戏时长最长达到6-7小时,参与人数也最多有8人。不同类型的剧本数据展示了
剧本库的多样性,能够满足不同玩家的需求。每个剧本都有独特的描述,帮助用户快速了解剧本的特点。
{'id': '3', 'name': '你好', 'type': '情感本', 'players': '5人', 'duration': '3-4h', 'rating': 9.0, 'price': 78, 'desc': '温暖治愈的青春故事'},
{'id': '4', 'name': '云使', 'type': '机制本', 'players': '8人', 'duration': '6-7h', 'rating': 9.3, 'price': 108, 'desc': '烧脑推理的巅峰之作'},
];
第二部分:页面主体结构
build方法是构建UI的核心方法,返回一个Scaffold脚手架组件作为页面的基础结构。AppBar的title设置为"剧本库",
简洁明了地表达了页面的功能。actions属性在AppBar右侧放置一个筛选按钮,点击后跳转到高级筛选页面。
这种设计让用户可以进行更复杂的筛选操作,如按价格范围、评分范围等条件筛选。body使用Column组件垂直排列
筛选栏和剧本列表两个主要区域。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('剧本库'),
actions: [
IconButton(
icon: const Icon(Icons.filter_list),
onPressed: () => Get.to(() => FilterPage()),
),
],
),
页面body使用Column组件垂直排列两个部分:顶部的类型筛选栏和下方的剧本列表。Column是Flutter中最常用的
布局组件之一,它将子组件按垂直方向依次排列。_buildTypeFilter()方法构建筛选栏,_buildScriptList()方法
构建剧本列表。Expanded组件让列表占据剩余的垂直空间,确保列表能够填满屏幕并支持滚动。这种布局结构
清晰合理,用户可以快速进行筛选和浏览。
body: Column(
children: [
_buildTypeFilter(),
Expanded(child: _buildScriptList()),
],
),
);
}
第三部分:类型筛选栏
_buildTypeFilter方法构建顶部的类型筛选栏。Container设置了50像素的高度和白色背景,与页面其他区域形成视觉区分。
ListView.builder配合scrollDirection: Axis.horizontal实现横向滚动的筛选栏。这种设计让用户可以快速浏览所有类型,
而不需要占用过多的垂直空间。padding设置为水平8像素的内边距,让筛选栏与屏幕边缘保持适当距离。
Widget _buildTypeFilter() {
return Container(
height: 50,
color: Colors.white,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: _types.length,
每个筛选项使用ChoiceChip组件实现。ChoiceChip是Material Design的选择芯片组件,适合单选筛选场景。
label属性显示筛选项的文字。selected属性根据_selectedType判断是否选中。selectedColor设置为主题紫色的20%透明度,
当选中时显示这个颜色。onSelected回调在用户点击时触发,通过setState更新_selectedType并重新构建页面。
Padding组件在每个芯片周围添加间距,保持视觉上的舒适感。
itemBuilder: (c, i) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: ChoiceChip(
label: Text(_types[i]),
selected: _selectedType == _types[i],
selectedColor: const Color(0xFF6B4EFF).withOpacity(0.2),
onSelected: (v) => setState(() => _selectedType = _types[i]),
),
),
),
);
}
第四部分:剧本列表构建
_buildScriptList方法构建剧本列表。首先根据_selectedType筛选剧本数据,如果选中"全部"则显示所有剧本,
否则只显示选中类型的剧本。使用where方法进行列表筛选是Dart中的常用技巧,能够快速过滤数据。
ListView.builder使用筛选后的列表构建UI,padding设置为12像素的内边距。itemBuilder回调调用_buildScriptCard
方法构建每个剧本卡片。这种设计让筛选功能简洁而高效。
Widget _buildScriptList() {
var filtered = _selectedType == '全部'
? _scripts
: _scripts.where((s) => s['type'] == _selectedType).toList();
return ListView.builder(
padding: const EdgeInsets.all(12),
itemCount: filtered.length,
itemBuilder: (c, i) => _buildScriptCard(filtered[i]),
);
}
第五部分:剧本卡片构建
_buildScriptCard方法构建单个剧本卡片。GestureDetector包裹整个卡片使其可以响应点击事件,点击时使用GetX的
Get.to方法导航到剧本详情页,并传递剧本ID。Container作为卡片容器,设置了底部12像素的外边距和12像素的圆角。
decoration使用BoxDecoration添加白色背景,营造出卡片效果。Row组件水平排列左侧的剧本封面和右侧的剧本信息。
Widget _buildScriptCard(Map<String, dynamic> script) {
return GestureDetector(
onTap: () => Get.to(() => ScriptDetailPage(scriptId: script['id'])),
child: Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
卡片左侧是剧本封面区域,使用100x120像素的Container显示。背景色使用紫色的浅色调,中间显示一个书籍图标。
在实际项目中,这里应该显示真实的剧本封面图片。borderRadius设置为左侧12像素的圆角,与卡片整体的圆角相匹配。
这种设计让卡片看起来更加整体和专业。
child: Row(
children: [
// 左侧封面
Container(
width: 100, height: 120,
decoration: BoxDecoration(
color: Colors.purple[100],
borderRadius: const BorderRadius.horizontal(left: Radius.circular(12)),
),
child: const Center(child: Icon(Icons.auto_stories, size: 40, color: Colors.purple)),
),
卡片右侧使用Expanded组件占据剩余空间,内部使用Padding添加12像素的内边距。Column组件垂直排列剧本信息,
crossAxisAlignment设为start使内容左对齐。第一行使用Row组件水平排列剧本名称和评分,Spacer组件将评分推到右侧。
剧本名称使用粗体和16像素字号突出显示。评分使用金色星星图标配合数字显示,这是评分的通用视觉符号。
// 右侧信息
Expanded(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(script['name'], style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
const Spacer(),
Row(children: [
const Icon(Icons.star, size: 16, color: Colors.amber),
Text(' ${script['rating']}', style: const TextStyle(fontWeight: FontWeight.bold)),
]),
],
),
剧本描述使用12像素的灰色小字显示,maxLines设为2限制显示行数,overflow设为ellipsis在超出时显示省略号。
这种设计既展示了剧本的基本信息,又避免了过长描述影响卡片布局。SizedBox(height: 8)在描述和标签之间添加间距。
下方的Row组件水平排列剧本类型、人数、时长等标签,以及右侧的价格信息。
const SizedBox(height: 4),
Text(
script['desc'],
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.grey[600], fontSize: 12),
),
const SizedBox(height: 8),
Row(
children: [
_tag(script['type']),
_tag(script['players']),
_tag(script['duration']),
const Spacer(),
Text('¥${script['price']}', style: const TextStyle(color: Color(0xFF6B4EFF), fontWeight: FontWeight.bold)),
],
),
第六部分:标签组件
_tag方法是一个辅助方法,用于构建剧本信息标签。Container使用灰色背景和4像素圆角营造出标签效果。
padding设置为水平6像素、垂直2像素的内边距,让标签看起来紧凑而精致。margin设置为右侧4像素的外边距,
在多个标签之间添加间距。Text组件显示标签文字,使用灰色和10像素字号。这种标签设计简洁而统一,
能够清晰地展示剧本的各种属性。
],
),
),
),
],
),
),
);
}
Widget _tag(String text) => Container(
margin: const EdgeInsets.only(right: 4),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(4)),
child: Text(text, style: TextStyle(color: Colors.grey[600], fontSize: 10)),
);
}
技术要点详解
1. ChoiceChip的使用
ChoiceChip是Material Design的选择芯片组件,适合单选筛选场景。主要特点包括:
- 视觉反馈:选中时显示不同的颜色和样式
- 紧凑设计:占用空间小,适合放在工具栏中
- 易于使用:提供简洁的API,易于集成
ChoiceChip的核心参数包括:
- label:显示的文字
- selected:是否选中
- selectedColor:选中时的背景颜色
- onSelected:选中时的回调函数
2. 横向滚动筛选栏的实现
使用ListView.builder配合scrollDirection: Axis.horizontal可以实现横向滚动的列表。这种设计的优点包括:
- 节省空间:不占用过多的垂直空间
- 易于浏览:用户可以快速滑动查看所有选项
- 响应式:在不同屏幕尺寸下都能正常显示
实现步骤:
- 创建ListView.builder
- 设置scrollDirection为Axis.horizontal
- 在itemBuilder中构建每个筛选项
- 使用padding控制间距
3. 列表筛选的实现
使用Dart的where方法可以快速过滤列表数据。这种方式的优点包括:
- 简洁高效:一行代码完成筛选
- 函数式编程:符合现代编程范式
- 易于理解:代码意图清晰
筛选示例:
var filtered = _selectedType == '全部'
? _scripts
: _scripts.where((s) => s['type'] == _selectedType).toList();
4. 卡片布局的设计
剧本卡片使用Row组件水平排列左侧封面和右侧信息,这种设计的优点包括:
- 信息完整:在有限的空间内展示尽可能多的信息
- 视觉清晰:使用不同的区域展示不同类型的信息
- 操作便捷:整个卡片都可以点击进入详情
扩展功能建议
1. 搜索功能
添加搜索框让用户可以按名称搜索剧本。可以在AppBar下方添加搜索框,支持实时搜索和搜索历史。
2. 排序功能
支持按评分、价格、热度等条件排序剧本。可以在筛选栏右侧添加排序按钮,提供多种排序选项。
3. 收藏功能
在卡片右侧添加收藏按钮,让用户可以快速收藏感兴趣的剧本。收藏状态应该实时保存到本地或服务器。
4. 分页加载
当剧本数量很多时,实现分页加载可以提高性能。首次加载显示前20个剧本,滚动到底部时加载更多。
5. 图片缓存
如果使用网络图片作为剧本封面,应该实现图片缓存机制,避免重复下载。可以使用cached_network_image包。
6. 推荐算法
根据用户的浏览历史和收藏记录,推荐相似的剧本。这种个性化推荐能够提升用户体验。
数据结构设计
剧本模型
在实际项目中,建议定义剧本模型类而不是使用Map:
class Script {
final String id;
final String name;
final String type;
final String players;
final String duration;
final double rating;
final int price;
final String description;
final String coverUrl;
final int playCount;
final DateTime createdAt;
Script({
required this.id,
required this.name,
required this.type,
required this.players,
required this.duration,
required this.rating,
required this.price,
required this.description,
required this.coverUrl,
required this.playCount,
required this.createdAt,
});
factory Script.fromJson(Map<String, dynamic> json) {
return Script(
id: json['id'],
name: json['name'],
type: json['type'],
players: json['players'],
duration: json['duration'],
rating: json['rating'].toDouble(),
price: json['price'],
description: json['description'],
coverUrl: json['coverUrl'],
playCount: json['playCount'],
createdAt: DateTime.parse(json['createdAt']),
);
}
}
API设计建议
获取剧本列表
GET /api/scripts?type=情感本&page=1&limit=20
Response: {
"scripts": [
{
"id": "1",
"name": "年轮",
"type": "情感本",
"players": "6人",
"duration": "4-5h",
"rating": 9.2,
"price": 88,
"description": "一段跨越时空的爱情故事",
"coverUrl": "...",
"playCount": 1234
}
],
"total": 100,
"page": 1,
"limit": 20
}
搜索剧本
GET /api/scripts/search?keyword=年轮
Response: {
"scripts": [...]
}
获取剧本类型列表
GET /api/scripts/types
Response: {
"types": ["全部", "情感本", "恐怖本", "机制本", "欢乐本", "硬核本"]
}
性能优化建议
1. 懒加载
使用ListView.builder实现懒加载,只渲染可见区域的列表项,大大提高性能。
2. 图片优化
- 使用合适的图片尺寸
- 实现图片缓存
- 使用占位图
- 支持图片压缩
3. 数据缓存
将剧本列表缓存到本地,减少网络请求。使用SharedPreferences或Hive存储。
4. 分页加载
实现分页加载,首次加载显示部分数据,滚动到底部时加载更多。
小结
本篇文章详细讲解了剧本库列表功能的实现过程,从功能需求分析到核心代码实现,再到技术要点和扩展建议。剧本库是用户浏览和选择剧本的核心页面,设计应该遵循信息完整、视觉清晰、操作便捷的原则。
页面使用ChoiceChip实现类型筛选,使用ListView.builder实现高效的列表展示,使用卡片布局展示剧本信息。整体设计简洁而高效,为用户提供了便捷的剧本浏览体验。
在实际项目中,可以根据需求添加搜索、排序、收藏等扩展功能,打造更加完善的剧本库系统。同时要注意性能优化,确保在剧本数量较多时依然保持流畅的用户体验。
下一篇文章我们将实现剧本详情页面,敬请期待!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)