Flutter for OpenHarmony 实战:随机抽签应用设计与实现

欢迎加入开源鸿蒙跨平台社区:开源鸿蒙跨平台开发者社区


在这里插入图片描述

前言

随机抽签应用是一个实用的工具类应用,广泛应用于抽奖、分组、决策等场景。本文将详细介绍随机抽签算法、候选人管理、动画效果实现、结果历史记录以及UI交互设计。

一、抽签算法实现

1.1 基础随机选择

class LotterySystem {
  List<String> candidates = [];

  String drawWinner() {
    if (candidates.isEmpty) {
      return '';
    }

    final random = Random();
    final index = random.nextInt(candidates.length);
    return candidates[index];
  }
}

使用Random().nextInt()生成随机索引,从候选人列表中选择一个。

1.2 多次抽签

List<String> drawMultiple(int count) {
  if (count > candidates.length) {
    throw Exception('抽签数量超过候选人数量');
  }

  final selected = <String>[];
  final available = List<String>.from(candidates);

  for (int i = 0; i < count; i++) {
    final random = Random();
    final index = random.nextInt(available.length);
    selected.add(available[index]);
    available.removeAt(index);
  }

  return selected;
}

支持一次抽取多个不重复的候选人。

1.3 权重抽签

class WeightedCandidate {
  final String name;
  final int weight;

  WeightedCandidate({required this.name, required this.weight});
}

String drawWeighted(List<WeightedCandidate> candidates) {
  final totalWeight = candidates.fold(0, (sum, c) => sum + c.weight);
  final random = Random();
  final randomValue = random.nextInt(totalWeight);

  int currentWeight = 0;
  for (var candidate in candidates) {
    currentWeight += candidate.weight;
    if (randomValue < currentWeight) {
      return candidate.name;
    }
  }

  return candidates.last.name;
}

根据权重进行抽签,权重越高被抽中的概率越大。

二、候选人管理

2.1 添加候选人

在这里插入图片描述
在这里插入图片描述

void addCandidate(String name) {
  if (name.trim().isEmpty) {
    return;
  }

  if (candidates.contains(name)) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('该候选人已存在')),
    );
    return;
  }

  setState(() {
    candidates.add(name);
  });
}

添加候选人时检查重复和空名称。

2.2 批量导入

void importCandidates(String text) {
  final lines = text.split('\n');
  int added = 0;

  for (var line in lines) {
    final name = line.trim();
    if (name.isNotEmpty && !candidates.contains(name)) {
      candidates.add(name);
      added++;
    }
  }

  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('成功导入 $added 个候选人')),
  );
}

支持从文本批量导入候选人,每行一个。

2.3 删除候选人

在这里插入图片描述

void removeCandidate(String name) {
  setState(() {
    candidates.remove(name);
  });
}

void clearAll() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('清空候选人'),
      content: const Text('确定要清空所有候选人吗?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            setState(() {
              candidates.clear();
            });
            Navigator.pop(context);
          },
          child: const Text('确定'),
        ),
      ],
    ),
  );
}

提供单个删除和清空全部功能。

三、抽签动画

在这里插入图片描述

3.1 滚动动画

String currentDisplay = '';
bool isAnimating = false;
List<String> history = [];

void startDrawing() async {
  if (candidates.isEmpty || isAnimating) {
    return;
  }

  isAnimating = true;
  final duration = const Duration(milliseconds: 2000);
  final start = DateTime.now();

  while (DateTime.now().difference(start) < duration) {
    final random = Random();
    currentDisplay = candidates[random.nextInt(candidates.length)];
    setState(() {});
    await Future.delayed(const Duration(milliseconds: 50));
  }

  // 最终结果
  final winner = drawWinner();
  currentDisplay = winner;
  history.add(winner);
  isAnimating = false;
  setState(() {});

  showResultDialog(winner);
}

2秒内快速滚动显示候选人名称,制造悬念。

3.2 结果对话框

在这里插入图片描述

void showResultDialog(String winner) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('抽签结果'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Icon(Icons.celebration, size: 64, color: Colors.amber),
          const SizedBox(height: 16),
          Text(
            winner,
            style: const TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
          ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('确定'),
        ),
      ],
    ),
  );
}

使用对话框展示最终结果,带有庆祝图标。

四、UI界面设计

4.1 主界面布局

在这里插入图片描述


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('随机抽签'),
      actions: [
        IconButton(icon: const Icon(Icons.history), onPressed: showHistory),
        IconButton(icon: const Icon(Icons.settings), onPressed: showSettings),
      ],
    ),
    body: Column(
      children: [
        Expanded(
          child: Center(
            child: Text(
              currentDisplay,
              style: Theme.of(context).textTheme.displayLarge,
            ),
          ),
        ),
        _buildCandidateList(),
        _buildControlButtons(),
      ],
    ),
  );
}

中心显示当前抽签结果,下方是候选人列表和控制按钮。

4.2 候选人列表

Widget _buildCandidateList() {
  return Container(
    height: 200,
    padding: const EdgeInsets.all(16),
    child: ListView.builder(
      itemCount: candidates.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(candidates[index]),
          trailing: IconButton(
            icon: const Icon(Icons.remove_circle),
            onPressed: () => removeCandidate(candidates[index]),
          ),
        );
      },
    ),
  );
}

可滚动的候选人列表,每个项带删除按钮。

4.3 控制按钮

Widget _buildControlButtons() {
  return Padding(
    padding: const EdgeInsets.all(16),
    child: Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        ElevatedButton.icon(
          onPressed: isAnimating ? null : startDrawing,
          icon: const Icon(Icons.play_arrow),
          label: const Text('开始抽签'),
        ),
        ElevatedButton.icon(
          onPressed: () => addCandidateDialog(),
          icon: const Icon(Icons.add),
          label: const Text('添加候选人'),
        ),
      ],
    ),
  );
}

开始抽签和添加候选人两个主要按钮。

五、历史记录

5.1 记录存储

List<String> history = [];
List<DateTime> timestamps = [];

void addToHistory(String winner) {
  history.add(winner);
  timestamps.add(DateTime.now());
}

记录抽签结果和时间戳。

5.2 历史显示

void showHistory() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('抽签历史'),
      content: SizedBox(
        width: double.maxFinite,
        height: 400,
        child: ListView.builder(
          itemCount: history.length,
          itemBuilder: (context, index) {
            final reversedIndex = history.length - 1 - index;
            return ListTile(
              title: Text(history[reversedIndex]),
              subtitle: Text(_formatTime(timestamps[reversedIndex])),
              leading: Text('${reversedIndex + 1}'),
            );
          },
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('关闭'),
        ),
        TextButton(
          onPressed: () {
            setState(() {
              history.clear();
              timestamps.clear();
            });
            Navigator.pop(context);
          },
          child: const Text('清空历史'),
        ),
      ],
    ),
  );
}

显示所有历史记录,最新记录在前。

六、设置功能

6.1 动画时长

int animationDuration = 2000; // 毫秒

void updateDuration(int duration) {
  setState(() {
    animationDuration = duration;
  });
}

允许用户调整抽签动画时长。

6.2 音效开关

bool soundEnabled = true;

void toggleSound() {
  setState(() {
    soundEnabled = !soundEnabled;
  });
}

控制抽签时是否播放音效。

七、数据持久化

7.1 保存数据

Future<void> saveData() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setStringList('candidates', candidates);
  await prefs.setStringList('history', history);
}

使用SharedPreferences保存候选人和历史记录。

7.2 加载数据

Future<void> loadData() async {
  final prefs = await SharedPreferences.getInstance();
  final savedCandidates = prefs.getStringList('candidates');
  final savedHistory = prefs.getStringList('history');

  if (savedCandidates != null) {
    setState(() {
      candidates = savedCandidates;
    });
  }

  if (savedHistory != null) {
    history = savedHistory;
  }
}

应用启动时自动加载数据。

总结

本文详细介绍了随机抽签应用的设计与实现。从随机算法到候选人管理,从动画效果到UI设计,每个技术点都直接影响应用的功能性和用户体验。通过这些技术的综合应用,实现了功能完整且实用的随机抽签应用。

Logo

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

更多推荐