Flutter 框架跨平台鸿蒙开发 - 虚拟盲盒机:打造沉浸式收藏体验
实时花粉浓度查询是一款专为过敏人群打造的Flutter健康应用,提供全国30个主要城市的实时花粉浓度监测、7天预报和个性化过敏提醒功能。通过科学的花粉指数分析和专业的防护建议,帮助用户有效预防花粉过敏,享受健康生活。运行效果图模型字段说明:计算属性:花粉指数等级划分:预报数据特点:数据生成特点:城市覆盖(30个):三个页面:IndexedStack使用:5. 综合花粉指数展示圆形指数展示特点:等级
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;
}
项目总结
虚拟盲盒机应用成功实现了完整的盲盒收藏体验,通过精心设计的用户界面、流畅的动画效果和合理的概率系统,为用户提供了沉浸式的收藏乐趣。
技术亮点
- 动画系统:使用多个AnimationController实现流畅的开盒动画
- 概率算法:基于加权随机的稀有度计算系统
- 数据管理:完整的收藏数据管理和统计功能
- UI设计:Material Design 3风格的现代化界面
- 交互体验:丰富的对话框和反馈系统
功能特色
- 多主题盲盒系列,满足不同用户喜好
- 四级稀有度系统,增加收藏价值感
- 完整的收藏管理和展示功能
- 成就系统激励用户持续参与
- 直观的统计数据和等级系统
扩展方向
- 社交功能:添加好友系统和收藏分享
- 交易系统:实现物品交换和市场功能
- 活动系统:定期举办限时活动和特殊盲盒
- 数据持久化:集成本地存储或云端同步
- 音效系统:添加开盒音效和背景音乐
通过本教程的学习,你已经掌握了Flutter应用开发的核心技能,包括状态管理、动画系统、UI设计和数据处理。这些技能可以应用到更多类型的应用开发中,为你的Flutter开发之路奠定坚实基础。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)