Flutter 框架跨平台鸿蒙开发 - 打造随机抽奖/点名器应用

Flutter实战:打造随机抽奖/点名器应用

前言

随机抽奖和点名是日常生活中常见的需求。本文将带你从零开始,使用Flutter开发一个功能完整的随机抽奖应用,支持权重设置、防重复、历史记录等功能。

应用特色

  • 🎰 加权随机:支持设置中奖概率权重
  • 🔄 旋转动画:炫酷的抽奖动画效果
  • 🚫 防重复抽取:可选是否允许重复中奖
  • 📊 批量抽取:一次抽取多个中奖者
  • 📝 批量导入:快速添加多个参与者
  • 📜 历史记录:保存所有抽奖记录
  • 🔄 重置功能:清除抽取状态
  • 💾 数据持久化:本地存储所有数据
  • 📈 实时统计:显示总人数、已抽取、剩余
  • 🌓 深色模式:自动适配系统主题

效果展示



随机抽奖
参与者管理
添加参与者
批量导入
编辑信息
设置权重
删除参与者
抽奖功能
开始抽奖
旋转动画
显示结果
多人抽取
抽奖设置
抽取数量
允许重复
重置记录
历史记录
时间记录
中奖名单
查看历史

数据模型设计

1. 参与者模型

dart 复制代码
class Participant {
  String id;
  String name;
  int weight;        // 权重(中奖概率)
  bool isSelected;   // 是否已被抽中

  Participant({
    required this.id,
    required this.name,
    this.weight = 1,
    this.isSelected = false,
  });
}

2. 抽奖记录模型

dart 复制代码
class LotteryRecord {
  DateTime timestamp;
  List<String> winners;

  LotteryRecord({
    required this.timestamp,
    required this.winners,
  });
}

核心功能实现

1. 加权随机算法

这是本应用的核心算法,根据权重随机选择参与者:

dart 复制代码
List<Participant> _weightedRandomSelection(
    List<Participant> participants, int count) {
  final random = Random();
  final selected = <Participant>[];
  final available = List<Participant>.from(participants);

  for (int i = 0; i < count && available.isNotEmpty; i++) {
    // 计算总权重
    final totalWeight = available.fold(0, (sum, p) => sum + p.weight);

    // 生成随机数
    var randomValue = random.nextInt(totalWeight);

    // 根据权重选择
    Participant? selectedParticipant;
    for (var participant in available) {
      randomValue -= participant.weight;
      if (randomValue < 0) {
        selectedParticipant = participant;
        break;
      }
    }

    if (selectedParticipant != null) {
      selected.add(selectedParticipant);
      available.remove(selectedParticipant);
    }
  }

  return selected;
}

算法原理

  1. 计算所有参与者的总权重
  2. 生成0到总权重之间的随机数
  3. 遍历参与者,依次减去权重
  4. 当随机数小于0时,选中当前参与者

示例

  • 张三权重3,李四权重2,王五权重1
  • 总权重 = 6
  • 随机数 = 4
  • 4 - 3 = 1(张三)
  • 1 - 2 = -1(李四被选中)

2. 抽奖动画

使用AnimationController实现旋转和缩放动画:

dart 复制代码
void _initAnimation() {
  _animationController = AnimationController(
    duration: const Duration(milliseconds: 2000),
    vsync: this,
  );

  // 旋转动画:旋转8圈
  _rotationAnimation = Tween<double>(begin: 0, end: 8 * pi).animate(
    CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
  );

  // 缩放动画:先放大后缩小
  _scaleAnimation = TweenSequence<double>([
    TweenSequenceItem(
      tween: Tween<double>(begin: 1.0, end: 1.3),
      weight: 1,
    ),
    TweenSequenceItem(
      tween: Tween<double>(begin: 1.3, end: 1.0),
      weight: 1,
    ),
  ]).animate(
    CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
  );

  // 动画完成后执行抽奖
  _animationController.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      _performDraw();
    }
  });
}

3. 防重复抽取

dart 复制代码
void _startDraw() {
  // 获取可用参与者
  final availableParticipants = _allowRepeat
      ? _participants
      : _participants.where((p) => !p.isSelected).toList();

  // 检查是否有可用参与者
  if (availableParticipants.isEmpty) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('所有人都已被抽中,请重置或允许重复抽取')),
    );
    return;
  }

  // 检查抽取数量
  if (_drawCount > availableParticipants.length) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('抽取数量不能超过可用人数(${availableParticipants.length})')
      ),
    );
    return;
  }

  // 开始抽奖
  setState(() {
    _isDrawing = true;
    _currentWinners = [];
  });

  _animationController.reset();
  _animationController.forward();
}

4. 批量导入

dart 复制代码
void _batchAdd() {
  final controller = TextEditingController();
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('批量添加'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text('每行一个姓名'),
          TextField(
            controller: controller,
            decoration: const InputDecoration(
              hintText: '张三\n李四\n王五',
              border: OutlineInputBorder(),
            ),
            maxLines: 10,
          ),
        ],
      ),
      actions: [
        FilledButton(
          onPressed: () {
            // 按行分割,过滤空行
            final names = controller.text
                .split('\n')
                .map((s) => s.trim())
                .where((s) => s.isNotEmpty)
                .toList();

            if (names.isNotEmpty) {
              setState(() {
                for (var name in names) {
                  _participants.add(Participant(
                    id: DateTime.now().millisecondsSinceEpoch.toString() +
                        names.indexOf(name).toString(),
                    name: name,
                  ));
                }
              });
              Navigator.pop(context);
            }
          },
          child: const Text('添加'),
        ),
      ],
    ),
  );
}

UI组件设计

1. 抽奖按钮

dart 复制代码
Widget _buildDrawButton() {
  return AnimatedBuilder(
    animation: _animationController,
    builder: (context, child) {
      return Transform.rotate(
        angle: _rotationAnimation.value,
        child: Transform.scale(
          scale: _scaleAnimation.value,
          child: GestureDetector(
            onTap: _isDrawing ? null : _startDraw,
            child: Container(
              width: 200,
              height: 200,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                gradient: RadialGradient(
                  colors: [
                    Colors.deepOrange,
                    Colors.deepOrange.shade700,
                  ],
                ),
                boxShadow: [
                  BoxShadow(
                    color: Colors.deepOrange.withOpacity(0.5),
                    blurRadius: 30,
                    spreadRadius: 5,
                  ),
                ],
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(
                    _isDrawing ? Icons.hourglass_empty : Icons.casino,
                    size: 60,
                    color: Colors.white,
                  ),
                  Text(
                    _isDrawing ? '抽取中...' : '开始抽奖',
                    style: const TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                      color: Colors.white,
                    ),
                  ),
                ],
              ),
            ),
          ),
        ),
      );
    },
  );
}

2. 中奖名单显示

dart 复制代码
Widget _buildWinnersDisplay() {
  return Container(
    padding: const EdgeInsets.all(24),
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [
          Colors.amber.shade100,
          Colors.orange.shade100,
        ],
      ),
      borderRadius: BorderRadius.circular(20),
      border: Border.all(color: Colors.orange, width: 3),
    ),
    child: Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.emoji_events, color: Colors.amber.shade700, size: 32),
            const SizedBox(width: 12),
            const Text(
              '中奖名单',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
          ],
        ),
        const SizedBox(height: 16),
        ..._currentWinners.asMap().entries.map((entry) {
          return Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(12),
            ),
            child: Row(
              children: [
                // 排名
                Container(
                  width: 40,
                  height: 40,
                  decoration: const BoxDecoration(
                    color: Colors.orange,
                    shape: BoxShape.circle,
                  ),
                  child: Center(
                    child: Text(
                      '${entry.key + 1}',
                      style: const TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                        fontSize: 18,
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                // 姓名
                Expanded(
                  child: Text(
                    entry.value,
                    style: const TextStyle(
                      fontSize: 20,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                // 星星图标
                const Icon(Icons.star, color: Colors.amber, size: 28),
              ],
            ),
          );
        }),
      ],
    ),
  );
}

3. 统计信息

dart 复制代码
Widget _buildStatistics(int selectedCount) {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          _buildStatItem(
            '总人数',
            _participants.length.toString(),
            Icons.people,
            Colors.blue,
          ),
          _buildStatItem(
            '已抽取',
            selectedCount.toString(),
            Icons.check_circle,
            Colors.green,
          ),
          _buildStatItem(
            '剩余',
            (_participants.length - selectedCount).toString(),
            Icons.pending,
            Colors.orange,
          ),
        ],
      ),
    ),
  );
}

Widget _buildStatItem(
    String label, String value, IconData icon, Color color) {
  return Column(
    children: [
      Icon(icon, color: color, size: 32),
      const SizedBox(height: 8),
      Text(
        value,
        style: TextStyle(
          fontSize: 24,
          fontWeight: FontWeight.bold,
          color: color,
        ),
      ),
      Text(label, style: TextStyle(fontSize: 14, color: Colors.grey.shade600)),
    ],
  );
}

技术要点详解

1. 加权随机算法

权重越大,被选中的概率越高:

参与者 权重 概率
张三 3 50% (3/6)
李四 2 33.3% (2/6)
王五 1 16.7% (1/6)

2. TweenSequence动画

实现先放大后缩小的效果:

dart 复制代码
TweenSequence<double>([
  TweenSequenceItem(
    tween: Tween<double>(begin: 1.0, end: 1.3),
    weight: 1,  // 前50%时间
  ),
  TweenSequenceItem(
    tween: Tween<double>(begin: 1.3, end: 1.0),
    weight: 1,  // 后50%时间
  ),
])

3. AnimationStatusListener

监听动画完成事件:

dart 复制代码
_animationController.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    // 动画完成后执行抽奖
    _performDraw();
  }
});

4. Slider滑块

调整抽取数量:

dart 复制代码
Slider(
  value: _drawCount.toDouble(),
  min: 1,
  max: min(_participants.length, 10).toDouble(),
  divisions: min(_participants.length, 10) - 1,
  label: _drawCount.toString(),
  onChanged: (value) {
    setState(() {
      _drawCount = value.toInt();
    });
  },
)

功能扩展建议

1. 音效和震动

dart 复制代码
import 'package:audioplayers/audioplayers.dart';
import 'package:vibration/vibration.dart';

Future<void> _playDrawSound() async {
  final player = AudioPlayer();
  await player.play(AssetSource('sounds/lottery.mp3'));
}

Future<void> _vibrate() async {
  if (await Vibration.hasVibrator() ?? false) {
    Vibration.vibrate(duration: 500);
  }
}

2. 导出结果

dart 复制代码
import 'package:share_plus/share_plus.dart';

void _shareResults() {
  final text = '''
抽奖结果
时间:${DateTime.now()}

中奖名单:
${_currentWinners.asMap().entries.map((e) => '${e.key + 1}. ${e.value}').join('\n')}
  ''';
  
  Share.share(text);
}

3. 自定义动画

dart 复制代码
class LotteryAnimation extends StatelessWidget {
  final Animation<double> animation;
  final List<String> names;

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animation,
      builder: (context, child) {
        final index = (animation.value * names.length).floor() % names.length;
        return Text(
          names[index],
          style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
        );
      },
    );
  }
}

4. 权重可视化

dart 复制代码
Widget _buildWeightBar(Participant participant, int totalWeight) {
  final percentage = (participant.weight / totalWeight * 100);
  
  return Column(
    children: [
      Text(participant.name),
      LinearProgressIndicator(
        value: participant.weight / totalWeight,
        minHeight: 20,
      ),
      Text('${percentage.toStringAsFixed(1)}%'),
    ],
  );
}

5. 分组抽奖

dart 复制代码
class Group {
  String name;
  List<Participant> participants;
  
  Group({required this.name, required this.participants});
}

List<Group> _groups = [];

void _drawByGroup(Group group) {
  final winners = _weightedRandomSelection(group.participants, _drawCount);
  // ...
}

使用场景

  • 🎓 课堂点名:随机提问学生
  • 🎁 抽奖活动:年会、活动抽奖
  • 🎯 任务分配:随机分配任务
  • 🎮 游戏选人:游戏中随机选择玩家
  • 🏆 评选投票:随机抽取评委
  • 👥 分组活动:随机分组
  • 🎪 互动游戏:现场互动抽奖

常见问题解答

Q1: 如何确保抽奖的公平性?

A: 使用Dart的Random类,它基于伪随机数生成器,对于一般应用足够公平。如需更高安全性,可使用Random.secure()。

Q2: 权重如何影响中奖概率?

A: 权重为3的人中奖概率是权重为1的人的3倍。总概率 = 个人权重 / 所有人权重之和。

Q3: 如何实现必中功能?

A: 设置某人权重为极大值(如1000),或直接在代码中指定必中人员。

项目结构

复制代码
lib/
├── main.dart                      # 主程序入口
├── models/
│   ├── participant.dart          # 参与者模型
│   └── lottery_record.dart       # 抽奖记录模型
├── screens/
│   ├── lottery_page.dart         # 主页面
│   ├── participants_page.dart    # 参与者管理页
│   └── history_page.dart         # 历史记录页
├── widgets/
│   ├── draw_button.dart          # 抽奖按钮
│   ├── winners_display.dart      # 中奖名单
│   └── statistics_card.dart      # 统计卡片
└── utils/
    ├── lottery_algorithm.dart    # 抽奖算法
    └── storage_helper.dart       # 存储助手

总结

本文实现了一个功能完整的随机抽奖应用,涵盖了以下核心技术:

  1. 加权随机算法:根据权重计算中奖概率
  2. 动画组合:旋转 + 缩放动画
  3. 批量操作:批量导入参与者
  4. 防重复机制:可选是否允许重复中奖
  5. 数据持久化:SharedPreferences存储

通过本项目,你不仅学会了如何实现抽奖应用,还掌握了Flutter中随机算法、动画设计、数据管理的核心技术。这些知识可以应用到更多场景,如游戏开发、互动应用等领域。

公平、公正、公开,让抽奖更有趣!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
弓.长.2 小时前
React Native 鸿蒙跨平台开发:实现商品列表组件
react native·react.js·harmonyos
以太浮标2 小时前
华为eNSP模拟器综合实验- ospf区域的作用和报文类型的关系解析
网络·华为·智能路由器
时光慢煮2 小时前
Flutter 编译开发 OpenHarmony 全流程实战教程-基于开源仓库GitCode 搜索工具 v1.0.3 的跨平台实践
flutter·开源·gitcode
小白阿龙2 小时前
鸿蒙+flutter 跨平台开发——Placeholder 控件的基础使用场景
flutter·华为·harmonyos·鸿蒙
时光慢煮2 小时前
基于 Flutter × OpenHarmony 图书馆管理系统之构建书籍管理模块
flutter·华为·开源·openharmony
IT陈图图2 小时前
智慧图书馆的数字名片:基于 Flutter × OpenHarmony 的读者卡片构建实践
flutter·鸿蒙·openharmony
弓.长.2 小时前
React Native 鸿蒙跨平台开发:SafeAreaView 安全区域
安全·react native·harmonyos
弓.长.2 小时前
React Native 鸿蒙跨平台开发:i18n 国际化方案代码指南
react native·react.js·harmonyos
南村群童欺我老无力.3 小时前
Flutter 框架跨平台鸿蒙开发 - 打造专业级单位换算器,支持8大类50+单位互转
flutter·华为·harmonyos