引言
剧本库是用户浏览和选择剧本的核心页面,需要支持分类筛选和列表展示。本篇将详细讲解如何实现一个功能完善的剧本库列表页面,包括顶部类型筛选栏、剧本卡片列表、筛选功能等核心功能。通过这个页面,用户可以快速找到感兴趣的剧本,了解剧本的详细信息,并进行预订。
功能需求分析
剧本库页面的核心功能
- 类型筛选栏:支持按剧本类型(情感本、恐怖本、机制本等)进行筛选,使用横向滚动的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算法中起着重要作用。
classScriptListPageextendsStatefulWidget{ScriptListPage({super.key});@overrideState<ScriptListPage>createState()=>_ScriptListPageState();}_ScriptListPageState类中定义了两个重要的状态变量。_selectedType用于记录当前选中的剧本类型,
初始值为"全部"表示显示所有剧本。_types列表定义了所有可用的剧本类型,包括"全部"、“情感本”、"恐怖本"等。
这些类型是剧本库的核心分类维度,用户可以通过选择不同的类型快速找到感兴趣的剧本。
这种设计让筛选功能简洁而高效。
class_ScriptListPageStateextendsState<ScriptListPage>{String_selectedType='全部';finalList<String>_types=['全部','情感本','恐怖本','机制本','欢乐本','硬核本'];_scripts列表定义了应用中的所有剧本数据。每个剧本使用Map<String, dynamic>类型存储,包含id、name、type、
players、duration、rating、price和desc等字段。这些字段涵盖了用户了解剧本所需的所有关键信息。
在实际项目中,这些数据应该从服务器获取,这里使用静态数据进行演示。数据结构的设计要考虑到UI展示的需求。
finalList<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组件垂直排列
筛选栏和剧本列表两个主要区域。
@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('剧本库'),actions:[IconButton(icon:constIcon(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(){returnContainer(height:50,color:Colors.white,child:ListView.builder(scrollDirection:Axis.horizontal,padding:constEdgeInsets.symmetric(horizontal:8),itemCount:_types.length,每个筛选项使用ChoiceChip组件实现。ChoiceChip是Material Design的选择芯片组件,适合单选筛选场景。
label属性显示筛选项的文字。selected属性根据_selectedType判断是否选中。selectedColor设置为主题紫色的20%透明度,
当选中时显示这个颜色。onSelected回调在用户点击时触发,通过setState更新_selectedType并重新构建页面。
Padding组件在每个芯片周围添加间距,保持视觉上的舒适感。
itemBuilder:(c,i)=>Padding(padding:constEdgeInsets.symmetric(horizontal:4,vertical:8),child:ChoiceChip(label:Text(_types[i]),selected:_selectedType==_types[i],selectedColor:constColor(0xFF6B4EFF).withOpacity(0.2),onSelected:(v)=>setState(()=>_selectedType=_types[i]),),),),);}第四部分:剧本列表构建
_buildScriptList方法构建剧本列表。首先根据_selectedType筛选剧本数据,如果选中"全部"则显示所有剧本,
否则只显示选中类型的剧本。使用where方法进行列表筛选是Dart中的常用技巧,能够快速过滤数据。
ListView.builder使用筛选后的列表构建UI,padding设置为12像素的内边距。itemBuilder回调调用_buildScriptCard
方法构建每个剧本卡片。这种设计让筛选功能简洁而高效。
Widget_buildScriptList(){varfiltered=_selectedType=='全部'?_scripts:_scripts.where((s)=>s['type']==_selectedType).toList();returnListView.builder(padding:constEdgeInsets.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){returnGestureDetector(onTap:()=>Get.to(()=>ScriptDetailPage(scriptId:script['id'])),child:Container(margin:constEdgeInsets.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:constBorderRadius.horizontal(left:Radius.circular(12)),),child:constCenter(child:Icon(Icons.auto_stories,size:40,color:Colors.purple)),),卡片右侧使用Expanded组件占据剩余空间,内部使用Padding添加12像素的内边距。Column组件垂直排列剧本信息,
crossAxisAlignment设为start使内容左对齐。第一行使用Row组件水平排列剧本名称和评分,Spacer组件将评分推到右侧。
剧本名称使用粗体和16像素字号突出显示。评分使用金色星星图标配合数字显示,这是评分的通用视觉符号。
// 右侧信息Expanded(child:Padding(padding:constEdgeInsets.all(12),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Row(children:[Text(script['name'],style:constTextStyle(fontWeight:FontWeight.bold,fontSize:16)),constSpacer(),Row(children:[constIcon(Icons.star,size:16,color:Colors.amber),Text(' ${script['rating']}',style:constTextStyle(fontWeight:FontWeight.bold)),]),],),剧本描述使用12像素的灰色小字显示,maxLines设为2限制显示行数,overflow设为ellipsis在超出时显示省略号。
这种设计既展示了剧本的基本信息,又避免了过长描述影响卡片布局。SizedBox(height: 8)在描述和标签之间添加间距。
下方的Row组件水平排列剧本类型、人数、时长等标签,以及右侧的价格信息。
constSizedBox(height:4),Text(script['desc'],maxLines:2,overflow:TextOverflow.ellipsis,style:TextStyle(color:Colors.grey[600],fontSize:12),),constSizedBox(height:8),Row(children:[_tag(script['type']),_tag(script['players']),_tag(script['duration']),constSpacer(),Text('¥${script['price']}',style:constTextStyle(color:Color(0xFF6B4EFF),fontWeight:FontWeight.bold)),],),第六部分:标签组件
_tag方法是一个辅助方法,用于构建剧本信息标签。Container使用灰色背景和4像素圆角营造出标签效果。
padding设置为水平6像素、垂直2像素的内边距,让标签看起来紧凑而精致。margin设置为右侧4像素的外边距,
在多个标签之间添加间距。Text组件显示标签文字,使用灰色和10像素字号。这种标签设计简洁而统一,
能够清晰地展示剧本的各种属性。
],),),),],),),);}Widget_tag(Stringtext)=>Container(margin:constEdgeInsets.only(right:4),padding:constEdgeInsets.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方法可以快速过滤列表数据。这种方式的优点包括:
- 简洁高效:一行代码完成筛选
- 函数式编程:符合现代编程范式
- 易于理解:代码意图清晰
筛选示例:
varfiltered=_selectedType=='全部'?_scripts:_scripts.where((s)=>s['type']==_selectedType).toList();4. 卡片布局的设计
剧本卡片使用Row组件水平排列左侧封面和右侧信息,这种设计的优点包括:
- 信息完整:在有限的空间内展示尽可能多的信息
- 视觉清晰:使用不同的区域展示不同类型的信息
- 操作便捷:整个卡片都可以点击进入详情
扩展功能建议
1. 搜索功能
添加搜索框让用户可以按名称搜索剧本。可以在AppBar下方添加搜索框,支持实时搜索和搜索历史。
2. 排序功能
支持按评分、价格、热度等条件排序剧本。可以在筛选栏右侧添加排序按钮,提供多种排序选项。
3. 收藏功能
在卡片右侧添加收藏按钮,让用户可以快速收藏感兴趣的剧本。收藏状态应该实时保存到本地或服务器。
4. 分页加载
当剧本数量很多时,实现分页加载可以提高性能。首次加载显示前20个剧本,滚动到底部时加载更多。
5. 图片缓存
如果使用网络图片作为剧本封面,应该实现图片缓存机制,避免重复下载。可以使用cached_network_image包。
6. 推荐算法
根据用户的浏览历史和收藏记录,推荐相似的剧本。这种个性化推荐能够提升用户体验。
数据结构设计
剧本模型
在实际项目中,建议定义剧本模型类而不是使用Map:
classScript{finalStringid;finalStringname;finalStringtype;finalStringplayers;finalStringduration;finaldouble rating;finalint price;finalStringdescription;finalStringcoverUrl;finalint playCount;finalDateTimecreatedAt;Script({requiredthis.id,requiredthis.name,requiredthis.type,requiredthis.players,requiredthis.duration,requiredthis.rating,requiredthis.price,requiredthis.description,requiredthis.coverUrl,requiredthis.playCount,requiredthis.createdAt,});factoryScript.fromJson(Map<String,dynamic>json){returnScript(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