Flutter虚拟盲盒机:打造沉浸式收藏体验

项目概述

虚拟盲盒机是一款基于Flutter开发的收藏类应用,模拟真实盲盒开启体验,为用户提供惊喜感十足的虚拟收藏乐趣。应用集成了盲盒开启、物品收藏、稀有度系统、成就系统等核心功能,通过精美的动画效果和直观的用户界面,为用户打造沉浸式的收藏体验。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

应用特色

  • 多样化盲盒系列:萌宠乐园、机甲战士、魔法世界等主题系列
  • 稀有度系统:普通、稀有、史诗、传说四个等级,不同概率获得
  • 开盒动画:流畅的开盒动画效果,增强用户体验
  • 收藏管理:完整的收藏系统,支持分类查看和详情展示
  • 成就系统:多种成就徽章,激励用户持续收藏
  • 统计分析:详细的收藏数据统计和可视化展示

技术架构

核心技术栈

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8

项目结构

lib/
├── main.dart              # 应用入口和主要逻辑
├── models/               # 数据模型(集成在main.dart中)
│   ├── blind_box_item.dart    # 盲盒物品模型
│   ├── blind_box_series.dart  # 盲盒系列模型
│   └── user_collection.dart   # 用户收藏模型
├── screens/              # 页面组件(集成在main.dart中)
│   ├── home_page.dart         # 首页
│   ├── collection_page.dart   # 收藏页面
│   ├── shop_page.dart         # 商店页面
│   └── profile_page.dart      # 个人页面
└── widgets/              # 自定义组件(集成在main.dart中)
    ├── series_card.dart       # 系列卡片
    ├── item_card.dart         # 物品卡片
    └── animation_widgets.dart # 动画组件

数据模型设计

盲盒物品模型(BlindBoxItem)

盲盒物品是应用的核心数据结构,包含物品的所有属性信息:

class BlindBoxItem {
  final String id;              // 物品唯一标识
  final String name;            // 物品名称
  final String description;     // 物品描述
  final String rarity;          // 稀有度等级
  final String imageUrl;        // 物品图标
  final int value;              // 物品价值
  final String category;        // 所属分类
  final DateTime obtainedAt;    // 获得时间

  BlindBoxItem({
    required this.id,
    required this.name,
    required this.description,
    required this.rarity,
    required this.imageUrl,
    required this.value,
    required this.category,
    required this.obtainedAt,
  });
}

物品模型还包含稀有度相关的计算属性:

Color get rarityColor {
  switch (rarity) {
    case 'common':
      return Colors.grey;      // 普通 - 灰色
    case 'rare':
      return Colors.blue;      // 稀有 - 蓝色
    case 'epic':
      return Colors.purple;    // 史诗 - 紫色
    case 'legendary':
      return Colors.orange;    // 传说 - 橙色
    default:
      return Colors.grey;
  }
}

String get rarityText {
  switch (rarity) {
    case 'common':
      return '普通';
    case 'rare':
      return '稀有';
    case 'epic':
      return '史诗';
    case 'legendary':
      return '传说';
    default:
      return '未知';
  }
}

盲盒系列模型(BlindBoxSeries)

盲盒系列定义了不同主题的盲盒集合:

class BlindBoxSeries {
  final String id;                        // 系列唯一标识
  final String name;                      // 系列名称
  final String description;               // 系列描述
  final String coverImage;               // 封面图标
  final int price;                        // 开盒价格
  final List<BlindBoxItem> items;         // 包含的物品列表
  final Map<String, double> rarityRates;  // 稀有度概率配置

  BlindBoxSeries({
    required this.id,
    required this.name,
    required this.description,
    required this.coverImage,
    required this.price,
    required this.items,
    required this.rarityRates,
  });
}

稀有度概率配置示例:

rarityRates: {
  'common': 0.5,      // 普通物品 50% 概率
  'rare': 0.3,        // 稀有物品 30% 概率
  'epic': 0.15,       // 史诗物品 15% 概率
  'legendary': 0.05,  // 传说物品 5% 概率
}

用户收藏模型(UserCollection)

用户收藏模型管理用户的收藏数据和统计信息:

class UserCollection {
  final String userId;                      // 用户ID
  final List<BlindBoxItem> items;           // 收藏的物品列表
  final int totalValue;                     // 收藏总价值
  final Map<String, int> categoryCount;     // 分类统计
  final Map<String, int> rarityCount;       // 稀有度统计

  UserCollection({
    required this.userId,
    required this.items,
    required this.totalValue,
    required this.categoryCount,
    required this.rarityCount,
  });
}

应用主体结构

应用入口

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '虚拟盲盒机',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
        useMaterial3: true,
      ),
      home: const BlindBoxHomePage(),
    );
  }
}

应用采用紫色作为主题色,营造神秘而富有吸引力的视觉效果。

主页面结构

主页面使用底部导航栏实现四个核心功能模块:

class BlindBoxHomePage extends StatefulWidget {
  const BlindBoxHomePage({super.key});

  
  State<BlindBoxHomePage> createState() => _BlindBoxHomePageState();
}

class _BlindBoxHomePageState extends State<BlindBoxHomePage>
    with TickerProviderStateMixin {
  int _selectedIndex = 0;
  int _coins = 1000;                    // 用户金币
  List<BlindBoxItem> _collection = [];  // 用户收藏
  List<BlindBoxSeries> _series = [];    // 盲盒系列
  
  // 动画控制器
  late AnimationController _boxAnimationController;
  late AnimationController _itemRevealController;
  late Animation<double> _boxRotation;
  late Animation<double> _boxScale;
  late Animation<double> _itemScale;
}

动画系统设计

动画控制器初始化

应用使用多个动画控制器来实现流畅的开盒体验:

void _setupAnimations() {
  // 盒子动画控制器(2秒旋转动画)
  _boxAnimationController = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  );
  
  // 物品展示动画控制器(0.8秒弹性动画)
  _itemRevealController = AnimationController(
    duration: const Duration(milliseconds: 800),
    vsync: this,
  );

  // 盒子旋转动画
  _boxRotation = Tween<double>(
    begin: 0,
    end: 2 * pi,
  ).animate(CurvedAnimation(
    parent: _boxAnimationController,
    curve: Curves.easeInOut,
  ));

  // 盒子缩放动画
  _boxScale = Tween<double>(
    begin: 1.0,
    end: 1.2,
  ).animate(CurvedAnimation(
    parent: _boxAnimationController,
    curve: const Interval(0.0, 0.5, curve: Curves.easeOut),
  ));

  // 物品缩放动画(弹性效果)
  _itemScale = Tween<double>(
    begin: 0.0,
    end: 1.0,
  ).animate(CurvedAnimation(
    parent: _itemRevealController,
    curve: Curves.elasticOut,
  ));
}

开盒动画流程

开盒动画分为多个阶段,创造完整的开盒体验:

Future<void> _openBlindBox(BlindBoxSeries series) async {
  if (_coins < series.price) {
    _showMessage('金币不足!');
    return;
  }

  setState(() {
    _isOpening = true;
    _coins -= series.price;
  });

  // 第一阶段:盒子旋转动画
  _boxAnimationController.forward();
  await Future.delayed(const Duration(seconds: 2));

  // 第二阶段:随机获得物品
  final item = _getRandomItem(series);
  _currentItem = item;

  // 第三阶段:添加到收藏
  setState(() {
    _collection.insert(0, item);
  });

  // 第四阶段:物品展示动画
  _boxAnimationController.reset();
  _itemRevealController.forward();

  // 第五阶段:显示获得物品对话框
  _showItemObtainedDialog(item);

  setState(() {
    _isOpening = false;
  });

  // 重置动画状态
  await Future.delayed(const Duration(milliseconds: 500));
  _itemRevealController.reset();
}

核心功能实现

首页设计

首页展示用户的收藏概况和热门盲盒系列:

Widget _buildHomePage() {
  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 欢迎卡片
        Container(
          width: double.infinity,
          padding: const EdgeInsets.all(20),
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.purple.shade400, Colors.pink.shade300],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
            borderRadius: BorderRadius.circular(16),
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                '欢迎来到虚拟盲盒机!',
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color: Colors.white,
                ),
              ),
              const SizedBox(height: 8),
              const Text(
                '收集你喜欢的物品,体验开盒的惊喜!',
                style: TextStyle(
                  fontSize: 16,
                  color: Colors.white70,
                ),
              ),
              const SizedBox(height: 16),
              // 统计信息展示
              Row(
                children: [
                  _buildStatCard('收藏数量', '${_collection.length}'),
                  const SizedBox(width: 16),
                  _buildStatCard('总价值', '${_collection.fold(0, (sum, item) => sum + item.value)}'),
                ],
              ),
            ],
          ),
        ),
        
        const SizedBox(height: 24),
        
        // 热门系列横向滚动展示
        const Text(
          '热门系列',
          style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 16),
        
        SizedBox(
          height: 200,
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: _series.length,
            itemBuilder: (context, index) {
              final series = _series[index];
              return Container(
                width: 160,
                margin: const EdgeInsets.only(right: 16),
                child: _buildSeriesCard(series),
              );
            },
          ),
        ),
      ],
    ),
  );
}

收藏页面设计

收藏页面按稀有度分组展示用户的收藏物品:

Widget _buildCollectionPage() {
  if (_collection.isEmpty) {
    return const Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.collections_outlined, size: 64, color: Colors.grey),
          SizedBox(height: 16),
          Text(
            '还没有收藏任何物品',
            style: TextStyle(fontSize: 18, color: Colors.grey),
          ),
          SizedBox(height: 8),
          Text(
            '去商店开启你的第一个盲盒吧!',
            style: TextStyle(fontSize: 14, color: Colors.grey),
          ),
        ],
      ),
    );
  }

  // 按稀有度分组
  final groupedItems = <String, List<BlindBoxItem>>{};
  for (final item in _collection) {
    groupedItems.putIfAbsent(item.rarity, () => []).add(item);
  }

  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 收藏统计卡片
        Container(
          width: double.infinity,
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.blue.shade400, Colors.purple.shade400],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
            borderRadius: BorderRadius.circular(16),
          ),
          child: _buildCollectionStats(),
        ),

        const SizedBox(height: 24),

        // 按稀有度展示收藏
        ...['legendary', 'epic', 'rare', 'common'].map((rarity) {
          final items = groupedItems[rarity] ?? [];
          if (items.isEmpty) return const SizedBox.shrink();

          return _buildRaritySection(rarity, items);
        }).toList(),
      ],
    ),
  );
}

商店页面设计

商店页面展示所有可购买的盲盒系列:

Widget _buildShopPage() {
  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 商店横幅
        Container(
          width: double.infinity,
          height: 120,
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.orange.shade400, Colors.red.shade400],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
            borderRadius: BorderRadius.circular(16),
          ),
          child: const Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  '🎁 盲盒商店 🎁',
                  style: TextStyle(
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                    color: Colors.white,
                  ),
                ),
                SizedBox(height: 8),
                Text(
                  '惊喜等你来发现!',
                  style: TextStyle(
                    fontSize: 16,
                    color: Colors.white70,
                  ),
                ),
              ],
            ),
          ),
        ),

        const SizedBox(height: 24),

        // 盲盒系列列表
        ListView.builder(
          shrinkWrap: true,
          physics: const NeverScrollableScrollPhysics(),
          itemCount: _series.length,
          itemBuilder: (context, index) {
            final series = _series[index];
            return Container(
              margin: const EdgeInsets.only(bottom: 16),
              child: _buildShopSeriesCard(series),
            );
          },
        ),
      ],
    ),
  );
}

随机算法实现

稀有度概率计算

应用使用加权随机算法来确定获得物品的稀有度:

BlindBoxItem _getRandomItem(BlindBoxSeries series) {
  final random = Random();
  final value = random.nextDouble();
  
  String selectedRarity = 'common';
  double cumulativeRate = 0.0;
  
  // 累积概率计算
  for (final entry in series.rarityRates.entries) {
    cumulativeRate += entry.value;
    if (value <= cumulativeRate) {
      selectedRarity = entry.key;
      break;
    }
  }

  // 从该稀有度的物品中随机选择
  final availableItems = series.items
      .where((item) => item.rarity == selectedRarity)
      .toList();
      
  if (availableItems.isEmpty) {
    // 如果没有该稀有度的物品,返回普通物品
    return series.items
        .where((item) => item.rarity == 'common')
        .first;
  }

  final selectedItem = availableItems[random.nextInt(availableItems.length)];
  
  // 创建新的物品实例(更新获得时间)
  return BlindBoxItem(
    id: '${selectedItem.id}_${DateTime.now().millisecondsSinceEpoch}',
    name: selectedItem.name,
    description: selectedItem.description,
    rarity: selectedItem.rarity,
    imageUrl: selectedItem.imageUrl,
    value: selectedItem.value,
    category: selectedItem.category,
    obtainedAt: DateTime.now(),
  );
}

物品价值计算

不同稀有度的物品具有不同的价值:

int _getRarityValue(String rarity) {
  switch (rarity) {
    case 'legendary':
      return 500;    // 传说物品价值最高
    case 'epic':
      return 200;    // 史诗物品价值较高
    case 'rare':
      return 80;     // 稀有物品价值中等
    case 'common':
      return 20;     // 普通物品价值较低
    default:
      return 10;
  }
}

用户界面设计

物品卡片组件

物品卡片是应用中的重要UI组件,用于展示盲盒物品:

Widget _buildItemCard(BlindBoxItem item) {
  return Card(
    elevation: 2,
    child: Container(
      padding: const EdgeInsets.all(8),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: item.rarityColor, width: 2),
      ),
      child: Column(
        children: [
          // 物品图标
          Text(
            item.imageUrl,
            style: const TextStyle(fontSize: 32),
          ),
          const SizedBox(height: 4),
          
          // 物品名称
          Text(
            item.name,
            style: const TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.bold,
            ),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 2),
          
          // 稀有度标签
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
            decoration: BoxDecoration(
              color: item.rarityColor,
              borderRadius: BorderRadius.circular(8),
            ),
            child: Text(
              item.rarityText,
              style: const TextStyle(
                fontSize: 10,
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ],
      ),
    ),
  );
}

系列卡片组件

系列卡片展示盲盒系列的基本信息和开盒按钮:

Widget _buildSeriesCard(BlindBoxSeries series) {
  return Card(
    elevation: 4,
    child: InkWell(
      onTap: () => _showSeriesDetail(series),
      borderRadius: BorderRadius.circular(12),
      child: Container(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 系列封面
            Center(
              child: Text(
                series.coverImage,
                style: const TextStyle(fontSize: 48),
              ),
            ),
            const SizedBox(height: 8),
            
            // 系列名称
            Text(
              series.name,
              style: const TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.bold,
              ),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
            const SizedBox(height: 4),
            
            // 系列描述
            Text(
              series.description,
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey.shade600,
              ),
              maxLines: 2,
              overflow: TextOverflow.ellipsis,
            ),
            const Spacer(),
            
            // 价格和开盒按钮
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Row(
                  children: [
                    const Icon(Icons.monetization_on, size: 16, color: Colors.amber),
                    const SizedBox(width: 4),
                    Text('${series.price}'),
                  ],
                ),
                ElevatedButton(
                  onPressed: () => _openBlindBox(series),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.purple,
                    foregroundColor: Colors.white,
                    minimumSize: const Size(60, 32),
                  ),
                  child: const Text('开盒', style: TextStyle(fontSize: 12)),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

对话框系统

物品获得对话框

当用户开启盲盒获得物品时,显示精美的获得物品对话框:

void _showItemObtainedDialog(BlindBoxItem item) {
  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (context) => Dialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
      child: Container(
        padding: const EdgeInsets.all(24),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(20),
          gradient: LinearGradient(
            colors: [
              item.rarityColor.withValues(alpha: 0.1),
              item.rarityColor.withValues(alpha: 0.05),
            ],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
          ),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // 恭喜文字
            Text(
              '🎉 恭喜获得 🎉',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
                color: item.rarityColor,
              ),
            ),
            
            const SizedBox(height: 20),
            
            // 物品展示(带动画)
            AnimatedBuilder(
              animation: _itemRevealController,
              builder: (context, child) {
                return Transform.scale(
                  scale: _itemScale.value,
                  child: Container(
                    width: 120,
                    height: 120,
                    decoration: BoxDecoration(
                      color: item.rarityColor.withValues(alpha: 0.1),
                      borderRadius: BorderRadius.circular(20),
                      border: Border.all(color: item.rarityColor, width: 3),
                      boxShadow: [
                        BoxShadow(
                          color: item.rarityColor.withValues(alpha: 0.3),
                          blurRadius: 20,
                          spreadRadius: 5,
                        ),
                      ],
                    ),
                    child: Center(
                      child: Text(
                        item.imageUrl,
                        style: const TextStyle(fontSize: 60),
                      ),
                    ),
                  ),
                );
              },
            ),
            
            const SizedBox(height: 20),
            
            // 物品信息
            Text(
              item.name,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            
            const SizedBox(height: 8),
            
            // 稀有度标签
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
              decoration: BoxDecoration(
                color: item.rarityColor,
                borderRadius: BorderRadius.circular(12),
              ),
              child: Text(
                item.rarityText,
                style: const TextStyle(
                  color: Colors.white,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
            
            const SizedBox(height: 8),
            
            // 物品价值
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Icon(Icons.monetization_on, color: Colors.amber, size: 16),
                const SizedBox(width: 4),
                Text(
                  '价值:${item.value}',
                  style: const TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            
            const SizedBox(height: 20),
            
            // 确认按钮
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () => Navigator.of(context).pop(),
                style: ElevatedButton.styleFrom(
                  backgroundColor: item.rarityColor,
                  foregroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(vertical: 12),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
                child: const Text(
                  '太棒了!',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

系列详情对话框

系列详情对话框展示盲盒系列包含的所有物品:

void _showSeriesDetail(BlindBoxSeries series) {
  showDialog(
    context: context,
    builder: (context) => Dialog(
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
      child: Container(
        padding: const EdgeInsets.all(20),
        constraints: const BoxConstraints(maxHeight: 600),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 系列标题
            Row(
              children: [
                Text(
                  series.coverImage,
                  style: const TextStyle(fontSize: 32),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        series.name,
                        style: const TextStyle(
                          fontSize: 20,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        series.description,
                        style: TextStyle(
                          fontSize: 14,
                          color: Colors.grey.shade600,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            
            const SizedBox(height: 20),
            
            const Text(
              '可能获得的物品',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            
            const SizedBox(height: 12),
            
            // 物品预览网格
            Expanded(
              child: GridView.builder(
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  childAspectRatio: 0.8,
                  crossAxisSpacing: 8,
                  mainAxisSpacing: 8,
                ),
                itemCount: series.items.length,
                itemBuilder: (context, index) {
                  final item = series.items[index];
                  return Container(
                    padding: const EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      border: Border.all(color: item.rarityColor),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Column(
                      children: [
                        Expanded(
                          child: Center(
                            child: Text(
                              item.imageUrl,
                              style: const TextStyle(fontSize: 24),
                            ),
                          ),
                        ),
                        Text(
                          item.name,
                          style: const TextStyle(fontSize: 10),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                        Container(
                          padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
                          decoration: BoxDecoration(
                            color: item.rarityColor,
                            borderRadius: BorderRadius.circular(4),
                          ),
                          child: Text(
                            item.rarityText,
                            style: const TextStyle(
                              fontSize: 8,
                              color: Colors.white,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                      ],
                    ),
                  );
                },
              ),
            ),
            
            const SizedBox(height: 16),
            
            // 关闭按钮
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () => Navigator.of(context).pop(),
                child: const Text('关闭'),
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

个人中心功能

统计数据展示

个人中心展示用户的收藏统计和成就系统:

Widget _buildProfilePage() {
  final totalValue = _collection.fold(0, (sum, item) => sum + item.value);
  final rarityCount = <String, int>{};
  
  // 统计各稀有度物品数量
  for (final item in _collection) {
    rarityCount[item.rarity] = (rarityCount[item.rarity] ?? 0) + 1;
  }

  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 用户信息卡片
        Container(
          width: double.infinity,
          padding: const EdgeInsets.all(20),
          decoration: BoxDecoration(
            gradient: LinearGradient(
              colors: [Colors.indigo.shade400, Colors.purple.shade400],
              begin: Alignment.topLeft,
              end: Alignment.bottomRight,
            ),
            borderRadius: BorderRadius.circular(16),
          ),
          child: Column(
            children: [
              const CircleAvatar(
                radius: 40,
                backgroundColor: Colors.white,
                child: Icon(Icons.person, size: 40, color: Colors.indigo),
              ),
              const SizedBox(height: 12),
              const Text(
                '盲盒收藏家',
                style: TextStyle(
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  color: Colors.white,
                ),
              ),
              const SizedBox(height: 8),
              Text(
                '收藏等级:${_getCollectorLevel()}',
                style: const TextStyle(
                  fontSize: 14,
                  color: Colors.white70,
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 24),

        // 统计信息网格
        const Text(
          '收藏统计',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 12),

        Row(
          children: [
            Expanded(
              child: _buildStatisticCard(
                '总收藏',
                '${_collection.length}',
                Icons.collections,
                Colors.blue,
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: _buildStatisticCard(
                '总价值',
                '$totalValue',
                Icons.monetization_on,
                Colors.amber,
              ),
            ),
          ],
        ),

        const SizedBox(height: 12),

        Row(
          children: [
            Expanded(
              child: _buildStatisticCard(
                '传说物品',
                '${rarityCount['legendary'] ?? 0}',
                Icons.star,
                Colors.orange,
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: _buildStatisticCard(
                '史诗物品',
                '${rarityCount['epic'] ?? 0}',
                Icons.diamond,
                Colors.purple,
              ),
            ),
          ],
        ),
      ],
    ),
  );
}

收藏等级系统

根据用户收藏数量计算收藏等级:

String _getCollectorLevel() {
  final count = _collection.length;
  if (count >= 50) return '大师级';
  if (count >= 30) return '专家级';
  if (count >= 15) return '熟练级';
  if (count >= 5) return '初级';
  return '新手';
}

成就系统

成就系统激励用户持续收藏:

List<Widget> _buildAchievements() {
  final achievements = [
    {
      'title': '初次开盒',
      'desc': '开启第一个盲盒',
      'achieved': _collection.isNotEmpty
    },
    {
      'title': '收藏家',
      'desc': '收集10个物品',
      'achieved': _collection.length >= 10
    },
    {
      'title': '传说猎人',
      'desc': '获得传说物品',
      'achieved': _collection.any((item) => item.rarity == 'legendary')
    },
    {
      'title': '全系列',
      'desc': '收集所有系列',
      'achieved': false
    },
  ];

  return achievements.map((achievement) {
    final achieved = achievement['achieved'] as bool;
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: achieved 
            ? Colors.green.withValues(alpha: 0.1) 
            : Colors.grey.withValues(alpha: 0.1),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(
          color: achieved ? Colors.green : Colors.grey,
          width: 1,
        ),
      ),
      child: Row(
        children: [
          Icon(
            achieved ? Icons.check_circle : Icons.radio_button_unchecked,
            color: achieved ? Colors.green : Colors.grey,
            size: 20,
          ),
          const SizedBox(width: 8),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  achievement['title'] as String,
                  style: TextStyle(
                    fontSize: 12,
                    fontWeight: FontWeight.bold,
                    color: achieved ? Colors.green : Colors.grey,
                  ),
                ),
                Text(
                  achievement['desc'] as String,
                  style: TextStyle(
                    fontSize: 10,
                    color: achieved 
                        ? Colors.green.shade700 
                        : Colors.grey.shade600,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }).toList();
}

数据初始化

盲盒系列数据

应用初始化时创建三个不同主题的盲盒系列:

void _initializeData() {
  _series = [
    // 萌宠乐园系列
    BlindBoxSeries(
      id: '1',
      name: '萌宠乐园',
      description: '可爱的小动物们等你收集',
      coverImage: '🐱',
      price: 50,
      items: _generateItems('pets'),
      rarityRates: {
        'common': 0.5,
        'rare': 0.3,
        'epic': 0.15,
        'legendary': 0.05,
      },
    ),
    
    // 机甲战士系列
    BlindBoxSeries(
      id: '2',
      name: '机甲战士',
      description: '未来科技机甲收藏',
      coverImage: '🤖',
      price: 80,
      items: _generateItems('mechs'),
      rarityRates: {
        'common': 0.4,
        'rare': 0.35,
        'epic': 0.2,
        'legendary': 0.05,
      },
    ),
    
    // 魔法世界系列
    BlindBoxSeries(
      id: '3',
      name: '魔法世界',
      description: '神秘的魔法道具收集',
      coverImage: '🔮',
      price: 100,
      items: _generateItems('magic'),
      rarityRates: {
        'common': 0.3,
        'rare': 0.4,
        'epic': 0.25,
        'legendary': 0.05,
      },
    ),
  ];
}

物品数据生成

根据不同分类生成对应的物品数据:

List<BlindBoxItem> _generateItems(String category) {
  final items = <BlindBoxItem>[];
  
  final categoryData = {
    'pets': {
      'names': ['小橘猫', '柴犬', '仓鼠', '兔子', '小鸟', '金鱼', '乌龟', '小猪'],
      'emoji': ['🐱', '🐶', '🐹', '🐰', '🐦', '🐠', '🐢', '🐷'],
    },
    'mechs': {
      'names': ['雷神机甲', '风暴战士', '钢铁巨人', '光速侠', '火焰王', '冰霜守护', '雷电法王', '暗影刺客'],
      'emoji': ['🤖', '⚡', '🔥', '❄️', '💨', '🌟', '⚔️', '🛡️'],
    },
    'magic': {
      'names': ['魔法水晶', '法师帽', '魔法书', '药水瓶', '魔法杖', '占卜球', '魔法戒指', '传送门'],
      'emoji': ['🔮', '🎩', '📚', '🧪', '🪄', '🌙', '💍', '🌀'],
    },
  };

  final data = categoryData[category]!;
  final names = data['names'] as List<String>;
  final emojis = data['emoji'] as List<String>;

  for (int i = 0; i < names.length; i++) {
    final rarity = _getRandomRarity();
    items.add(BlindBoxItem(
      id: '${category}_$i',
      name: names[i],
      description: '来自${category == 'pets' ? '萌宠乐园' : category == 'mechs' ? '机甲战士' : '魔法世界'}系列的${names[i]}',
      rarity: rarity,
      imageUrl: emojis[i],
      value: _getRarityValue(rarity),
      category: category,
      obtainedAt: DateTime.now(),
    ));
  }

  return items;
}

项目总结

虚拟盲盒机应用成功实现了完整的盲盒收藏体验,通过精心设计的用户界面、流畅的动画效果和合理的概率系统,为用户提供了沉浸式的收藏乐趣。

技术亮点

  1. 动画系统:使用多个AnimationController实现流畅的开盒动画
  2. 概率算法:基于加权随机的稀有度计算系统
  3. 数据管理:完整的收藏数据管理和统计功能
  4. UI设计:Material Design 3风格的现代化界面
  5. 交互体验:丰富的对话框和反馈系统

功能特色

  • 多主题盲盒系列,满足不同用户喜好
  • 四级稀有度系统,增加收藏价值感
  • 完整的收藏管理和展示功能
  • 成就系统激励用户持续参与
  • 直观的统计数据和等级系统

扩展方向

  1. 社交功能:添加好友系统和收藏分享
  2. 交易系统:实现物品交换和市场功能
  3. 活动系统:定期举办限时活动和特殊盲盒
  4. 数据持久化:集成本地存储或云端同步
  5. 音效系统:添加开盒音效和背景音乐

通过本教程的学习,你已经掌握了Flutter应用开发的核心技能,包括状态管理、动画系统、UI设计和数据处理。这些技能可以应用到更多类型的应用开发中,为你的Flutter开发之路奠定坚实基础。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐