鸿蒙原生开发——从零构建随机选择器

鸿蒙原生开发——从零构建随机选择器 一、引言我们每天都在无意识中做随机选择。午餐吃什么、今晚看哪部电影、周末去哪里玩——这些决策有一个共同特点选项太多但决策本身不重要“选哪个都行”。这时候把决策权交给随机数生成器反而能打破犹豫不决的心理僵局。随机选择器的技术核心是Math.random()——JavaScript 的内置伪随机数函数。它能生成 0 到 1 之间的一个浮点数所有后续的随机操作硬币正反面、骰子点数、范围内整数、列表中选取都是对这一随机源的变换和映射。但一个好的随机选择器不止于数字生成。它需要有一个旋转动画——用户在按下按钮后看到数字或图标快速变化经过约 0.6 秒后才定格在最终结果。这个动画不是多余的装饰而是一种心理暗示它让用户感受到随机过程正在发生增强了对随机性的信任感。如果按下按钮后结果立刻出现用户的大脑会怀疑是不是程序早就定好了。本文将用 ArkUI 从零构建一个随机选择器包含四种随机模式硬币抛掷正反面、骰子滚动1-6、数字范围任意区间、自定义列表抽取。每种模式都带有旋转动画和最近结果记录。阅读完本文你将能够使用Math.random()变换生成多种随机结果用setInterval实现旋转定格动画600ms 快速循环 定格管理动画状态防止重复点击构建四模式 tab 切换界面二、随机数变换2.1 Math.random() 的基础Math.random()返回 [0, 1) 区间的伪随机浮点数。这个看似简单的函数是所有随机操作的基础// 硬币二选一constfaceMath.random()0.5? 正面: 反面;// 骰子六选一constdiceMath.floor(Math.random()*6)1;// 范围内整数[min, max] 区间constrangemax-min1;constnumminMath.floor(Math.random()*range);// 列表中随机选取constitemitems[Math.floor(Math.random()*items.length)];每种变换的核心公式操作公式解释布尔二选一Math.random() 0.5随机数 0.5 的概率恰好是 50%1-6 骰子floor(random * 6) 10→0, 0.99→5.94→5, 1→[1,6]范围整数min floor(random * range)0→min, 0.99→minrange-1, →[min, max]数组选取arr[floor(random * len)]索引范围 [0, len-1]2.2 为什么不用 crypto.getRandomValues()密码生成器上一篇使用了Math.random()本文也使用Math.random()但对于不同的目的。Math.random()是伪随机数生成器PRNG——由确定性算法根据种子生成的数字序列。对于密码安全PRNG 不够攻击者可以通过已知输出推断下一个输出但对于硬币抛掷、骰子滚动、午饭吃什么这些场景PRNG 已经足够。如果需要密码学安全的随机数应使用crypto.getRandomValues()但随机选择器不在这个范畴内。三、旋转定格动画3.1 动画原理旋转动画的核心思路简单在约 600ms 的时间内快速连续显示随机结果然后定格在最终值。用户看到的是数字/图标快速变化的视觉效果产生随机正在发生的感觉。privatespinCount:number0;flipCoin():void{if(this.spinning)return;// 防止重复点击this.spinningtrue;this.spinCount0;this.spinTimersetInterval((){this.spinCount;this.coinFaceMath.random()0.5? 正面: 反面;// 快速变化if(this.spinCount12){// 12 × 50ms 600ms 后结束this.stopSpin();this.coinFaceMath.random()0.5? 正面: 反面;// 定格consth[this.coinFace,...this.coinHistory];if(h.length5)h.pop();this.coinHistoryh;}},50);}四个阶段防重复if (this.spinning) return。动画期间禁止再次点击防止多个动画同时运行。快速变化每 50ms 更新一次显示值20fps连续 12 次 600ms。用户在这 0.6 秒内看到硬币在正反面之间快速切换。定格12 次循环后清除定时器最后一次随机决定最终结果。记录历史最终结果加入历史列表最多保留 5 条。3.2 骰子的动画骰子使用相同的动画结构但随机值的变换不同rollDice():void{if(this.spinning)return;this.spinningtrue;this.spinCount0;this.spinTimersetInterval((){this.spinCount;this.diceResultMath.floor(Math.random()*6)1;if(this.spinCount12){this.stopSpin();this.diceResultMath.floor(Math.random()*6)1;consth[this.diceResult,...this.diceHistory];if(h.length5)h.pop();this.diceHistoryh;}},50);}骰子结果展示使用 Unicode 骰子符号⚀(1)、⚁(2)、⚂(3)、⚃(4)、⚄(5)、⚅(6)。这些是正式的 Unicode 字符在任何平台上都能正常显示diceEmoji(n:number):string{constfaces[,⚀,⚁,⚂,⚃,⚄,⚅];returnfaces[n];}80sp 大号骰子符号配合下方的数字文字既有视觉冲击力又清晰无误——光看符号可能把 ⚁ 和 ⚂ 弄混尤其是在动画快速闪过时但下方的阿拉伯数字消除了任何歧义。3.3 随机数的动画数字范围内的随机值是三段动画中视觉变化最剧烈的——因为数字可以从个位跳到百位视觉跨度大generateNum():void{if(this.spinning)return;this.minValparseInt(this.minText)||1;this.maxValparseInt(this.maxText)||100;if(this.minValthis.maxVal){consttmpthis.minVal;this.minValthis.maxVal;this.maxValtmp;// 自动修正最小值 最大值时交换}this.spinningtrue;this.spinCount0;constrangethis.maxVal-this.minVal1;this.spinTimersetInterval((){this.spinCount;this.numResultthis.minValMath.floor(Math.random()*range);if(this.spinCount12){this.stopSpin();this.numResultthis.minValMath.floor(Math.random()*range);consth[this.numResult,...this.numHistory];if(h.length5)h.pop();this.numHistoryh;}},50);}一个用户友好的设计如果用户输入 min max比如 min100, max1程序自动交换两个值。这避免了生成失败的错误提示因为用户的意图很明确——“我想要 1 到 100 之间的随机数”输入顺序不重要。3.4 列表抽取的动画列表抽取的动画比前三种更有趣——文本在变化时给人滑轮在旋转的感觉pickFromList():void{if(this.spinning||this.listItems.length0)return;this.spinningtrue;this.spinCount0;this.spinTimersetInterval((){this.spinCount;this.listResultthis.listItems[Math.floor(Math.random()*this.listItems.length)];if(this.spinCount16){// 16 × 50ms 800ms比其他模式稍长this.stopSpin();this.listResultthis.listItems[Math.floor(Math.random()*this.listItems.length)];consth[this.listResult,...this.listHistory];if(h.length5)h.pop();this.listHistoryh;}},50);}列表抽取使用 16 次循环800ms而非 12 次600ms动画稍长。原因是列表项的文本变化需要更多时间让用户感知名字在滚动——相较于数字人的大脑需要更长时间来处理文本识别。3.5 动画状态管理所有四种模式共享同一个动画锁——this.spinning。这意味着在硬币动画期间不能点击骰子或数字生成。这是一个有意的设计——同时运行多个动画会造成视觉混乱和状态冲突。动画期间按钮变为灰色#CCCCCC提供视觉上的暂时不可用反馈.backgroundColor(this.spinning?#CCCCCC:#1677FF)stopSpin()同时清理定时器和动画状态stopSpin():void{if(this.spinTimer!-1){clearInterval(this.spinTimer);this.spinTimer-1;}this.spinningfalse;}aboutToDisappear()生命周期中调用stopSpin()防止页面离开后定时器继续运行。四、UI 设计4.1 四模式 Tab 切换四种模式通过顶部 tab 栏切换[ 硬币] [ 骰子] [ 数字] [ 列表]每个 tab 显示图标 文字当前选中的为白字加粗未选中的为半透明白字#FFFFFF66。Tab 栏整合在深色标题栏下方形成视觉上的连续性。内容区使用if (this.activeTab N)条件渲染对应模式的内容互斥显示。4.2 硬币模式硬币模式内容极简——一个 64sp 的大号结果文字“ 正面或 反面” 一个品红色#EB2F96抛掷按钮 最近结果列表。品红色的选择是有意为之硬币抛掷带有轻微的游戏/娱乐属性品红比蓝色更活泼、更适合这个场景。而用户点击时动画赋予硬币一种命运正在被决定的仪式感。最近结果以小卡片形式展示显示前 5 次抛掷记录。4.3 骰子模式骰子模式展示 80sp 的骰子符号⚀⚁⚂⚃⚄⚅和下方的阿拉伯数字。用户在动画结束后能通过两个独立信息通道确认结果——图形和文字。最近结果以水平排列的小卡片展示每张卡片包含骰子符号32sp和数字。这个布局比垂直列表更紧凑——在 360dp 宽度下5 个 56vp 宽的小卡片恰好排成一行。4.4 数字模式数字模式有两个TextInput最小值 / 最大值键盘类型为InputType.Number 绿色生成随机数按钮 结果显示。56sp 等宽字体monospace显示结果数字与秒表的显示风格一致。结果下方是最近数字的横向排列52vp 宽的白色卡片。4.5 列表模式列表模式是四个中最复杂的添加输入TextInput 添加按钮蓝色输入后点添加或回车选项列表白色卡片展示所有选项右侧 × 按钮删除单个抽取按钮品红色 抽取一个按钮带旋转动画结果展示大号文字 图标展示抽中结果历史记录最近 5 次抽取结果添加按钮在输入为空时灰色#CCCCCC输入不为空时蓝色#1677FF这与待办清单和倒数日的表单验证模式一致。五、完整代码结构RandomPickerPage ├── 数据定义 │ └── TABS[] — 四个 tab 标签 ├── 状态变量 │ ├── State activeTab — 当前模式 │ ├── State spinning — 动画锁 │ ├── 硬币State coinFace coinHistory[] │ ├── 骰子State diceResult diceHistory[] │ ├── 数字State minVal/maxVal/minText/maxText numResult numHistory[] │ └── 列表State listInput listItems[] listResult listHistory[] ├── 动画引擎 │ ├── flipCoin() — 硬币动画12 帧 × 50ms │ ├── rollDice() — 骰子动画12 帧 × 50ms │ ├── generateNum() — 数字动画12 帧 × 50ms自动交换 min/max │ ├── pickFromList() — 列表抽取动画16 帧 × 50ms │ └── stopSpin() — 清理定时器 重置 spinning ├── 列表管理 │ ├── addListItem() — 添加选项 │ └── deleteListItem() — 删除选项 ├── 视图四个 if 分支 │ ├── Tab 0硬币 — 大号结果 抛掷按钮 历史列表 │ ├── Tab 1骰子 — 骰子符号 数字 掷骰子按钮 横向历史 │ ├── Tab 2数字 — 范围输入 结果 生成按钮 横向历史 │ └── Tab 3列表 — 输入添加 选项列表 抽取按钮 结果 历史 └── 生命周期 └── aboutToDisappear() — 清除动画定时器六、总结本文从零构建了一个随机选择器。与前十一篇的数据管理和工具类应用不同随机选择器的核心是随机数变换 旋转定格动画——没有持久化数据仅内存中的历史记录没有复杂列表只有四种随机模式和一个共享的动画引擎。核心要点回顾四种随机变换Math.random()映射到布尔二选一硬币、1-6 整数骰子 、[min, max] 范围数字、数组随机索引列表。每个变换都是对同一随机源的重新解释。旋转定格动画50ms 定时器每帧更新随机值12-16 帧后定格在最终结果。600-800ms 的动画时长让用户感知随机正在发生增强了结果的可信度。动画锁spinning防止重复点击。共享动画引擎四种模式共用spinning、spinCount、spinTimer三个私有变量和stopSpin()清理方法。这种共享设计减少了代码重复的同时保证了状态一致性——不可能出现两个动画同时运行的情况。骰子的 Unicode 符号⚀⚁⚂⚃⚄⚅ 是正式的 Unicode 字符在所有平台可正常显示。80sp 大号图形 下方阿拉伯数字双通道显示消除识别歧义。自动交换 min/max数字模式下用户输入 min max 时程序自动交换两值不做错误提示。这是一种用户友好的容错设计——用户的意图明确不需要被纠正。按钮颜色语义硬币按钮品红#EB2F96 游戏/娱乐骰子按钮蓝#1677FF 标准操作数字按钮绿#52C41A 生成/产出列表中抽取按钮品红#EB2F96 随机抽取。四种按钮使用三种颜色形成微妙的视觉层次——硬币和列表共用品红因为它们都是选择操作骰子是中性游戏数字是工具。随机选择器是一个小而有用的工具——四种模式、一个Math.random()、一段旋转动画。它是决策疲劳的解药也是随机数应用的一个完整示例。