引言

剧本库是用户浏览和选择剧本的核心页面,需要支持分类筛选和列表展示。本篇将详细讲解如何实现一个功能完善的剧本库列表页面,包括顶部类型筛选栏、剧本卡片列表、筛选功能等核心功能。通过这个页面,用户可以快速找到感兴趣的剧本,了解剧本的详细信息,并进行预订。

功能需求分析

剧本库页面的核心功能

  1. 类型筛选栏:支持按剧本类型(情感本、恐怖本、机制本等)进行筛选,使用横向滚动的ChoiceChip组件
  2. 剧本卡片列表:展示所有剧本的信息,包括名称、描述、类型、人数、时长、评分和价格
  3. 剧本信息展示:每个卡片包含剧本封面、名称、描述、标签和价格等关键信息
  4. 点击跳转详情:点击剧本卡片可以跳转到剧本详情页面查看更多信息
  5. 高级筛选: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可以实现横向滚动的列表。这种设计的优点包括:

  • 节省空间:不占用过多的垂直空间
  • 易于浏览:用户可以快速滑动查看所有选项
  • 响应式:在不同屏幕尺寸下都能正常显示

实现步骤:

  1. 创建ListView.builder
  2. 设置scrollDirection为Axis.horizontal
  3. 在itemBuilder中构建每个筛选项
  4. 使用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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐