
前言中午十二点办公室里总会响起那句魔咒“今天吃什么”外卖刷了二十分钟收藏的店吃了个遍最后还是点了楼下的黄焖鸡。这种选择困难本质上不是没得吃而是选项太多大脑宕机。有天我实在不想再纠结了打开 DevEco Studio 6.1.1 Beta1在 Pura X Max 模拟器上写了个抽签工具。它把午餐拆成主食、肉类、蔬菜三大类每类里面放着常吃的食物点一下按钮随机给你搭出一套组合还能手动排除不想要的食材——比如我今天就是不想吃香菜。这篇文章就是那次“自救”的产物里面会聊到随机数怎么保证公平、排除逻辑怎么写才不会让程序崩掉以及怎么用最简单的 ArkUI 组件搭出一个能用的午饭决策器。代码也给全你拷进模拟器明天中午就不用再纠结了。一、把午饭决策交给随机数——伪随机的那点事儿抽签这件事说到底就是从一个名单里随机挑一个。计算机里的“随机”和抛硬币不一样它其实是靠一个数学公式算出来的伪随机数。这个公式从一个叫做“种子”的初始值开始反复迭代出一个看似没有规律的序列。种子通常取自系统时间、硬件噪声这些难以预测的东西所以对选午餐来说它已经足够公平了。HarmonyOS 的 ArkTS 里Math.random()就是那个伪随机数生成器。它返回一个[0, 1)之间的浮点数比如0.37421。要从一个数组里随机取一个元素思路是把数组长度乘上这个随机数再用Math.floor向下取整得到一个索引。比如let items [米饭, 馒头, 面条]; let index Math.floor(Math.random() * items.length); let choice items[index];这个操作重复三次——分别从主食、肉类、蔬菜里各抽一个——一顿饭就搭配好了。要保证组合不重复只需要每次独立随机两次抽到同样的东西很正常因为我们用的Math.random在不同时间戳下种子不同。但如果今天特别不想吃某样东西呢这就是“排除项”的由来。我们在抽取之前先把用户排除掉的食物从对应类别的数组里过滤掉生成一个新的候选数组然后再抽。这样既保留了随机性又照顾了口味的个性化。二、食物库怎么设计——分门别类留好扩展口我们的食物数据不需要联网直接写死在代码里。为了方便管理和扩展我把数据按类别分开每一类用一个字符串数组存储。主食包括米饭、馒头、面条、饺子、包子等肉类包括猪肉、鸡肉、牛肉、鱼、虾蔬菜包括白菜、菠菜、西红柿、黄瓜、豆芽、香菜等等。这些食物都是日常外卖里常见的用户一看就有亲切感。排除项不能太多否则某个类别可能被用户全排除了导致无法抽取。为了避免程序崩溃我在抽取前会检查候选数组是否为空。如果某类别的食物全部被排除就显示一个提示比如“主食全部被排除了请至少保留一项”并让程序优雅降级不抽那个类别。这次搭配视为不完整但不会闪退。数据结构用State修饰的数组staples、meats、vegetables另外还有三个布尔数组来记录每个食物是否被排除比如excludeStaples: boolean[]长度和对应食物数组一致。为了界面简单我用Toggle组件切换每一项的排除状态。排除逻辑在抽取时执行根据excludeStaples数组过滤staples得到一个新的可选数组然后对新数组执行随机抽取。如果可选数组长度为 0就直接把结果设为“已全部排除”并在颜色上标红提示。三、界面设计——三列排开一目了然我想让这个工具界面像一张菜单卡左边是主食中间是肉右边是蔬菜。每个类别下面是食物列表每项前面有个Toggle默认打开。底部一个大的“抽签”按钮点一下三个类别的结果就显示在各自列表的顶部用大字体展示。这样用户可以同时调整排除项和查看结果。具体布局用三个Column并排放在Row里每个Column包含类别标题、抽签结果展示、食物列表带排除开关。结果用Text显示如果该类被全排除显示红色“无可用”。颜色上主食淡黄底、肉类淡红底、蔬菜淡绿底区分明显。按钮用蓝色圆角胶囊。整体色调柔和不刺眼适合午间刷手机用。为了代码清晰我把抽取逻辑写成一个randomPick函数传入原数组和排除数组返回结果字符串。三个类别分别调用三次一次性更新三个结果变量实现同步刷新。四、完整代码——所有食材和逻辑都在一个文件里以下代码适配 DevEco Studio 6.1.1 Beta1、SDK22 语法Pura X Max 模拟器。新建 Empty Ability 项目替换entry/src/main/ets/pages/Index.ets。无需任何权限纯本地逻辑。/* * 营养搭配抽签 —— 午餐搭配随机生成 * 环境DevEco Studio 6.1.1 Beta1Pura X Max 模拟器SDK22 */ Entry Component struct Index { // 主食数据 State staples: string[] [米饭, 馒头, 面条, 饺子, 包子, 烧饼]; State excludeStaples: boolean[] [false, false, false, false, false, false]; State stapleResult: string ?; // 肉类数据 State meats: string[] [猪肉, 鸡肉, 牛肉, 鱼肉, 虾仁, 排骨]; State excludeMeats: boolean[] [false, false, false, false, false, false]; State meatResult: string ?; // 蔬菜数据 State vegetables: string[] [白菜, 菠菜, 西红柿, 黄瓜, 豆芽, 香菜, 西兰花]; State excludeVegs: boolean[] [false, false, false, false, false, false, false]; State vegResult: string ?; // 随机抽取函数 private randomPick(items: string[], excludes: boolean[]): string { let available: string[] []; for (let i 0; i items.length; i) { if (!excludes[i]) { available.push(items[i]); } } if (available.length 0) { return 已全部排除; } let idx Math.floor(Math.random() * available.length); return available[idx]; } // 执行抽签 private draw(): void { this.stapleResult this.randomPick(this.staples, this.excludeStaples); this.meatResult this.randomPick(this.meats, this.excludeMeats); this.vegResult this.randomPick(this.vegetables, this.excludeVegs); } // 重置所有排除 private resetExcludes(): void { this.excludeStaples new Array(this.staples.length).fill(false); this.excludeMeats new Array(this.meats.length).fill(false); this.excludeVegs new Array(this.vegetables.length).fill(false); this.stapleResult ?; this.meatResult ?; this.vegResult ?; } build() { Column() { Text(午餐搭配抽签) .fontSize(28) .fontWeight(FontWeight.Bold) .margin({ top: 20, bottom: 8 }) Text(排除不想吃的再点抽签) .fontSize(15) .fontColor(#888) .margin({ bottom: 15 }) // 三类食材 Row() { // 主食 Column() { Text(主食) .fontSize(18) .fontWeight(FontWeight.Bold) .margin({ bottom: 8 }) Text(this.stapleResult) .fontSize(22) .fontColor(this.stapleResult 已全部排除 ? #F44336 : #333) .height(36) .margin({ bottom: 6 }) ForEach(this.staples, (item: string, idx: number) { Row() { Toggle({ type: ToggleType.Switch, isOn: !this.excludeStaples[idx] }) .onChange((value: boolean) { this.excludeStaples[idx] !value; }) .width(40) Text(item).fontSize(14).margin({ left: 6 }) } .margin({ bottom: 4 }) }) } .layoutWeight(1) .padding(10) .backgroundColor(#FFF8E1) .borderRadius(8) // 肉类 Column() { Text(肉类) .fontSize(18) .fontWeight(FontWeight.Bold) .margin({ bottom: 8 }) Text(this.meatResult) .fontSize(22) .fontColor(this.meatResult 已全部排除 ? #F44336 : #333) .height(36) .margin({ bottom: 6 }) ForEach(this.meats, (item: string, idx: number) { Row() { Toggle({ type: ToggleType.Switch, isOn: !this.excludeMeats[idx] }) .onChange((value: boolean) { this.excludeMeats[idx] !value; }) .width(40) Text(item).fontSize(14).margin({ left: 6 }) } .margin({ bottom: 4 }) }) } .layoutWeight(1) .padding(10) .backgroundColor(#FFEBEE) .borderRadius(8) .margin({ left: 6 }) // 蔬菜 Column() { Text(蔬菜) .fontSize(18) .fontWeight(FontWeight.Bold) .margin({ bottom: 8 }) Text(this.vegResult) .fontSize(22) .fontColor(this.vegResult 已全部排除 ? #F44336 : #333) .height(36) .margin({ bottom: 6 }) ForEach(this.vegetables, (item: string, idx: number) { Row() { Toggle({ type: ToggleType.Switch, isOn: !this.excludeVegs[idx] }) .onChange((value: boolean) { this.excludeVegs[idx] !value; }) .width(40) Text(item).fontSize(14).margin({ left: 6 }) } .margin({ bottom: 4 }) }) } .layoutWeight(1) .padding(10) .backgroundColor(#E8F5E9) .borderRadius(8) .margin({ left: 6 }) } .width(96%) .margin({ bottom: 15 }) // 操作按钮 Row() { Button(抽签) .type(ButtonType.Capsule) .backgroundColor(#1976D2) .fontColor(Color.White) .fontSize(20) .layoutWeight(1) .onClick(() { this.draw(); }) Button(重置排除) .type(ButtonType.Capsule) .backgroundColor(#EEEEEE) .fontColor(#333) .fontSize(16) .layoutWeight(1) .margin({ left: 10 }) .onClick(() { this.resetExcludes(); }) } .width(90%) .margin({ bottom: 15 }) Text( 使用 Math.random 随机生成搭配排除项即时生效) .fontSize(12) .fontColor(#AAA) .width(90%) .textAlign(TextAlign.Center) } .width(100%) .height(100%) .backgroundColor(#FAFAFA) } }代码把食物数据、排除状态和抽取逻辑全部放在一个组件里。randomPick函数处理过滤和随机挑选如果某类全部被排除就返回带颜色的提示文本。界面使用三列并排每列内用ForEach渲染开关和食物名称。抽签和重置按钮放在底部操作路径极短。运行效果代码粘贴进 DevEco StudioRun 到 Pura X Max 模拟器。屏幕上方出现三列颜色不同的卡片黄底主食、红底肉类、绿底蔬菜。每列顶部显示一个问号下面是带开关的食物列表全部默认开启。点“抽签”按钮三个问号同时变成具体食物比如“馒头”“鸡肉”“菠菜”。不想吃香菜把蔬菜列里“香菜”的开关关掉再抽签香菜永远不会出现。如果不小心把所有蔬菜都排除了蔬菜结果会显示红色“已全部排除”提醒你至少留一样。点“重置排除”所有开关恢复开启问号复位。整个过程交互流畅抽签瞬间完成是那种“纠结了就点一下”的轻松工具。总结这个小小的午餐抽签器里面其实揉进了几个写应用时经常要用到的技能随机数与组合决策Math.random配合数组索引实现等概率随机选择是抽奖、摇号、推荐算法的基础。动态过滤与容错处理通过排除数组动态生成候选列表并在列表为空时给出优雅提示避免程序报错。ArkUI 的列表与开关组件Toggle的开关状态与State数组联动ForEach循环渲染可交互列表实现了多选项的独立控制。组件化布局思维把功能拆成可复用的randomPick函数把 UI 拆成三列卡片代码清晰易维护。如果以后想升级可以把食物库换成从 Preferences 读取让用户自己添加爱吃的东西或者给每道菜配上图片。但就解决“今天中午吃什么”这个难题来说这个小工具已经足够了——点一下命运帮你决定你只管去吃。