鸿蒙 ArkTS 实战:随机点名 RandomPick

📅 2026/8/7 11:52:23
鸿蒙 ArkTS 实战:随机点名 RandomPick
随机点名是课堂提问、会议抽签、活动抽奖等场景里最高频的小工具之一。一、应用概述与功能Index24 是一个「随机点名」页面整体数据流是维护一个名单数组 → 点名时随机选中一项 → 停止后展示结果 → 名单支持增删。页面的具体功能点包括内置名单names 数组预置了 12 个中文姓名——张伟、王芳、李娜、刘洋、陈静、杨磊、赵敏、黄强、周婷、吴杰、徐丽、孙浩页面一进来就能直接点名。预置名单的好处是演示零成本打开页面立刻可用真实使用时这份名单通常会来自后端接口或本地存储届时只需替换 names 的初始化来源其余逻辑完全不用改动。滚动点名点击「开始点名」按钮running 置为 true同时启动一个 100ms 的 setInterval每个周期内用 Math.random() 随机算出一个下标更新 highlight 与 current。此时顶部大字以蓝色高速跳动列表对应行以浅蓝背景加粗高亮。停止定格点击「停止」按钮clearInterval 停掉定时器running 置 false随后弹出 toast「选中xxx」当前名字颜色变为绿色表示点名结束。添加姓名底部输入框输入姓名后点「添加」先 trim 去空格空串提示「请输入姓名」重复时提示「该姓名已存在」合法则通过 concat 追加并清空输入框。删除姓名点击任意名单行即可删除该行若正在点名删除前会先自动调用 stop()避免删除后定时器仍引用无效数据。状态展示页面右侧显示「剩余人数」实时数值并提示「点击名单行可删除」交互引导完整。返回导航顶部「返回」按钮调用 router.back() 回到上一页。这个页面把「定时器、随机数、数组增删、条件样式」全部串在一个小应用里非常适合作为 setInterval 在 ArkTS 中标准用法的学习范本。从布局结构上看页面采用「固定头部 可滚动内容」的主框架外层 Column 铺满全屏顶部是返回栏其余内容放进 ScrollScroll 内部用 Column({ space: 16 }) 依次排列「显示卡片、操作按钮、剩余人数行、添加行、名单列表」五个区块。显示卡片与名单列表都是白底圆角卡片在 #f2f3f5 的浅灰底上层次分明。这样的结构让名单很长时依然可以上下滑动查看同时顶部返回栏始终固定可见。二、核心知识点State 响应式状态Index24 声明了五个 State 变量namesstring[] 名单数组、currentstring 当前显示的名字初始为「待点名」、highlightnumber 当前高亮的下标、runningboolean 是否正在点名、inputstring 添加输入的文本。它们的变化都会驱动 UI 更新highlight 变化时列表高亮跟着移动current 变化时顶部大字跟着切换running 变化时按钮状态、标题文案「点名中…」/「点名结果」与颜色一起联动。timer 则是普通成员变量只是定时器句柄不参与渲染。其中 names 是引用类型修改它时必须整体赋新数组才能被框架感知这也是第 4 点「数组操作技巧」反复强调 concat 与 slice 的原因。另外 highlight 与 current 虽然分属两个状态但每次回调总是成对更新保证「高亮行」与「显示的名字」始终指向同一个人避免出现显示与高亮错位的怪异现象。setInterval / clearInterval这是本页的灵魂。start() 中用 setInterval(() {…}, 100) 每 100ms 执行一次回调stop() 中用 clearInterval(this.timer) 终止。ArkTS 中定时器回调会在时间到后进入事件循环执行因此回调里直接读写 this 状态即可无需额外线程同步。注意定时器必须妥善清理否则页面销毁后回调仍会执行造成内存泄漏甚至异常。setInterval 与 setTimeout 的差别要分清setInterval 会按固定间隔无限次执行直到被 clearInterval 停止setTimeout 只执行一次。本页面要的是「持续滚动」所以用 setInterval而后面扩展章节提到的「先快后慢」减速效果则需要改用 setTimeout 递归调度。理解二者的差别是玩转定时器的第一步。随机数生成const idx: number Math.floor(Math.random() * this.names.length) 生成 0 到 length - 1 之间的整数下标与 names 长度联动。即使运行中名单被删除随机范围也始终与数组长度一致不会越界。从概率上说Math.random() 均匀分布在 [0, 1) 区间乘以长度再向下取整后每个下标被选中的概率近似相等不存在偏向某一个人的问题。唯一要注意的是每次点名之间是独立事件同一人连续被点中是完全正常的概率现象并不代表随机算法有 bug。数组操作技巧concat 追加this.names this.names.concat(name) 返回新数组符合「不可变更新」的响应式要求slice splice 删除先 slice() 拷贝一份再 splice(idx, 1) 删除目标项最后整体赋回indexOf 查重names.indexOf(name) 0 判定重复trim 去空格input.trim() 避免「 张三 」之类的脏数据。这些操作刻意避开了原地修改concat 生成新数组、slice 生成副本再 splice都是为了给 State 一个全新的引用。你可以把「数组变了页面却没更新」作为一条排查信号遇到时优先怀疑是不是用了原地修改。条件样式列表行用 this.running idx this.highlight 同时判断「正在点名」与「是否命中当前高亮」命中时文字变蓝加粗、行背景变成 #dbe8ff否则保持默认的 #333333 文字与 #f7f8fa 背景。顶部大字的颜色也在 running 时显示 #1a6cff、停止后变为 #0abf53 的绿色。这种把「样式」写成「状态的函数」的写法正是声明式 UI 的核心表达只要状态是对的界面就自动是对的。未来想改高亮色只需改动三元表达式里的颜色值不需要去 UI 里逐个找元素手动改。running 标志位running 承担了三重职责防止重复启动start 里 if (this.running) return、防止空停止stop 里 if (!this.running) return、作为 UI 状态控制样式与文案。它是典型的「状态机位」把业务状态与界面状态统一起来。在异步回调和用户操作并发发生的场景下这种标志位尤为重要它保证了任何时刻系统只存在一个「正在点名」的事实无论你连点多少次开始都只会有一个定时器在运行。生命周期清理aboutToDisappear() 中无条件 clearInterval(this.timer)这是定时器使用的底线要求。这个生命周期方法由框架在页面销毁前自动调用配合 start / stop 里的手动清理构成「启动有处、停止有法、销毁有底」的三层保障。真机上如果出现页面返回后点名还在跑的问题十有八九是这里漏写了清理逻辑。Column / Row / Blank 布局顶层 Column 纵向排布各个区块每个区块内部用 Row 横向排布。返回栏里 Blank() 把「返回」按钮和标题撑在左侧、右侧留白显示卡片用 Column({ space: 8 }) 把「点名结果」小标题和 44 号大字上下排开操作按钮行用 Row({ space: 16 }) 让「开始点名」与「停止」等宽分布。space 参数统一控制子元素间距比手动加 margin 更省心。链式样式与主题色页面用 .layoutWeight(1) 让按钮等宽、.fontSize(44) 放大当前名字、.fontColor(‘#1a6cff’) 与 .fontColor(‘#0abf53’) 区分点名中与定格、.backgroundColor(‘#dbe8ff’) 高亮命中行。统一的蓝色主题开始、高亮、剩余人数配橙色停止按钮#ff8f1f与红色删除标记#ff4d4f功能分区一目了然用户不用读文字也能猜到每个元素的作用。颜色值也可以进一步抽成主题常量统一管理方便整体换肤时一键修改。ForEach 与 key 生成器名单列表用 ForEach(this.names, (item, idx) {…}, (item, idx) idx.toString()) 渲染key 使用下标字符串。因为删除操作会改变数组下标用下标做 key 在增删频繁时会有一定的重建成本但对点名这种小型列表完全够用如果名单规模很大可以考虑给每个名字分配一个独立 id 做 key。promptAction 轻提示与示例 23 一样本页用 promptAction.showToast 承担全部用户反馈点名前的空名单提示、停止后的「选中xxx」、添加时的「请输入姓名」与「该姓名已存在」、删除后的「已删除xxx」。这些 toast 让每次操作都有即时反馈交互体验完整闭环。三、源码逐段解析3.1 点名核心start / stop点名最核心的两个方法分别是 start() 与 stop()private start(): void { if (this.running) return; if (this.names.length 0) { promptAction.showToast({ message: 名单为空请先添加 }); return; } this.running true; this.timer setInterval(() { const idx: number Math.floor(Math.random() * this.names.length); this.highlight idx; this.current this.names[idx]; }, 100); } private stop(): void { if (!this.running) return; this.running false; clearInterval(this.timer); this.timer -1; promptAction.showToast({ message: 选中 this.current }); }start() 的三个步骤幂等保护已运行直接返回→ 空名单兜底toast 提示→ 置 running 并启动定时器。回调中每次随机取下标、更新高亮与当前名字。stop() 先清 running再 clearInterval 并把句柄复位为 -1最后 toast 定格结果。先置 false 再清定时器可以避免回调在最后时刻又写回一次状态造成视觉闪烁。把定时器的执行过程拆开看start() 启动后每 100ms 事件循环就会执行一次箭头函数。假设当前 names 有 12 人某次回调里 Math.random() 返回 0.37Math.floor(0.37 * 12) 4于是 highlight 4、current names[4]。下一次回调可能又算出另一个下标如此反复就形成了肉眼可见的「滚动」。由于每次都是独立随机结果不会出现规律性循环看起来非常自然。3.2 添加与删除名单名单管理由 addName() 与 removeName() 负责private addName(): void { const name: string this.input.trim(); if (name.length 0) { promptAction.showToast({ message: 请输入姓名 }); return; } if (this.names.indexOf(name) 0) { promptAction.showToast({ message: 该姓名已存在 }); return; } this.names this.names.concat(name); this.input ; } private removeName(idx: number): void { if (this.running) this.stop(); const list: string[] this.names.slice(); list.splice(idx, 1); this.names list; this.current 待点名; }addName 依次做「去空格、判空、查重、追加、清输入」。removeName 的细节若正在点名先 stop()把定时器安全停掉然后 slice() 拷贝原数组splice 删除指定下标赋回新数组保证 State 收到新引用从而触发刷新最后把 current 复位为「待点名」状态回到初始。原代码在删除后还会 toast「已删除xxx」此处略去。举一个具体操作输入「陈晨」点添加先 trim 得到「陈晨」长度非 0、indexOf 返回 -1 说明不重复于是 concat 追加到末尾input 清空。再点击第 3 行删除如果正在点名会先 stop()随后 slice 拷贝出 11 项的新数组splice(2, 1) 去掉下标 2 的名字赋回 names列表立刻少一行剩余人数同步减一。3.3 高亮行渲染列表项的高亮样式由条件三元表达式驱动Text(item) .fontColor(this.running idx this.highlight ? #1a6cff : #333333) .fontWeight(this.running idx this.highlight ? FontWeight.Bold : FontWeight.Normal) .backgroundColor(this.running idx this.highlight ? #dbe8ff : #f7f8fa) .onClick(() { this.removeName(idx); })列表行由序号、姓名、右侧红色 ✕ 组成。高亮条件组合了 running 与下标比对既保证只有「点名中」才有滚动效果又精确到行。整行 onClick 绑定 removeName点击即删交互非常直接。另外注意列表行右侧的红色 ✕ 只是装饰性标识真正的删除动作绑定在整行 Row 的 onClick 上也就是说点击名字本身也能删除容错区域更大符合移动端的触控习惯。key 生成函数沿用 idx.toString()配合 slice splice 的不可变更新删除任意一行后列表都能正确重绘。3.4 返回栏与生命周期顶部返回栏结构与示例 23 完全一致Row 里放「返回」按钮与「随机点名」标题点击后 router.back()。页面底部的 aboutToDisappear() 在页面销毁前清理定时器防止页面消失后回调继续执行导致异常。从整体看build() 里的节点结构与数据流是对应的显示卡片绑定 current 与 running操作按钮行绑定 start / stop添加行绑定 input 与 addName名单列表绑定 names 与 removeName。每个交互点都有明确的数据来源和动作入口页面虽小结构却非常规整适合作为理解 ArkTS 页面组织的样板。四、关键实现细节分析4.1 为什么用 100ms 的间隔100ms 是人眼能感知「滚动」又不至于因闪烁过快而疲劳的折中值换算过来就是每秒切换 10 次。间隔调大如 200ms节奏更舒缓调小如 50ms更紧张刺激用户感知差异非常明显可以按场景自由微调。这个参数就是 setInterval 的第二个参数改动成本极低非常适合拿来体验「定时频率对交互手感的影响」。从实现角度100ms 也意味着每秒最多 10 次状态刷新对 ArkTS 的渲染管线来说负担很小即便同时更新 highlight 与 current 两个状态也不会卡顿这个频率同时为后续的减速动画留出了调整余地。4.2 随机下标的联动安全性随机范围始终是 this.names.length所以即使定时器运行中名单被删除或增加下一次回调仍在最新长度内取下标不会越界。这是「随机值必须依赖当前真实数据规模」的直接体现也是把随机范围硬编码成某个固定值最容易踩的坑——名单一变化程序就可能崩溃。换个角度这也是为什么「随机下标」要放在定时器回调里实时计算而不是在 start() 时算好一次前者永远拿到最新长度后者会随着名单增删慢慢失真。4.3 running 标志的三种角色running 是理解整个页面交互的钥匙它同时扮演着三个角色业务幂等start / stop 互斥避免重复启动或重复停止导致定时器句柄错乱定时器生命周期只有 running 为 true 时才持有有效句柄stop 后把句柄复位为 -1UI 状态标题文案、文字颜色、列表高亮全部依赖它做分支。4.4 数组的不可变更新concat 与 slice splice 都返回新数组这正是 State 正确刷新的前提——若直接 push 或原地 splice 原数组ArkTS 无法感知变化UI 不会更新。这是声明式 UI 与命令式编程之间最需要转换的思维习惯不要「改数据」而要「换数据」。习惯这个思维之后很多响应式 bug 都能提前预防比如删行后高亮错位、添加后列表不刷新本质都是没有换新数组源码里 addName 与 removeName 都严格遵循了这个约定。4.5 删除与点名的冲突处理removeName 先 stop() 再删除避免「正在点的名字被删掉」导致 current 指向已不存在的项。删除后 current 复位「待点名」界面状态重新回到一致。这个「先停再改」的顺序很重要如果反过来删除那一刻定时器还可能随机到越界下标。4.6 从状态机视角看整个页面页面状态可以抽象成「待点名 → 点名中 → 定格」三个阶段初始时 current 为「待点名」start() 后进入「点名中」标题变「点名中…」、文字变蓝、列表高亮滚动stop() 后定格toast 告知选中者、文字变绿。删除名单会让 current 回到「待点名」三个状态之间的转移都由 running 与其他 State 共同决定闭环清晰也方便后续扩展新状态。4.7 current 复位的设计添加与删除之后current 与界面状态要保持同步删除时把它复位为「待点名」添加时则不改动它。为什么添加不复位因为添加只是名单扩容当前展示不受影响而删除会改变数组下标如果被删的恰好是当前高亮项继续显示旧名字就会产生「名单里没有这个人却显示他的名字」的矛盾所以删除必须复位。这一行 current ‘待点名’ 看似不起眼却是保证界面状态一致性的关键。4.8 大字号展示与视觉层级当前名字用 .fontSize(44) 放大显示是页面的视觉焦点运行中为蓝色、定格后为绿色用户不用读文字就能从颜色判断状态。配合「点名中… / 点名结果」的小标题与剩余人数行整个页面形成了「状态标题 → 大字结果 → 操作按钮 → 名单列表」的清晰视觉层级。小屏幕上也保持了这个比例44 号字在多数手机上都不会溢出卡片。五、运行效果与操作指南进入页面看到顶部「随机点名」标题中间白色卡片显示「点名结果」与 44 号大字「待点名」。卡片下方是「开始点名」「停止」两个按钮再往下是剩余人数行右侧提示「点击名单行可删除」。底部是 12 行姓名列表每行右侧有一个红色 ✕ 标记。点击「开始点名」标题变为「点名中…」大字开始蓝色高速跳动列表行随机高亮滚动。点击「停止」滚动定格弹出 toast「选中xxx」大字变绿显示最终结果。在输入框输入姓名点「添加」新名字追加到列表末尾剩余人数 1。输入已存在的姓名弹出「该姓名已存在」输入纯空格弹出「请输入姓名」。点击某行姓名该行被删除点名中删除会先自动停止。把名单删空后再点「开始点名」弹出「名单为空请先添加」。操作时可以刻意测试几个边界刚进入页面不点开始直接点「停止」因为 running 为 falsestop() 会直接返回不会误报「选中」把列表删到只剩 1 个人再点名滚动看起来几乎停住因为随机范围只有一个选项连续快速点「开始点名」多次由于幂等保护不会叠加出多个定时器。这些小细节正是 running 标志位和 if 判断存在的意义。如果屏幕较小、名单又长页面主体可以上下滚动名单列表的高度被固定为 240容纳初始 12 个名字绰绰有余但新增到超过 12 人后超出部分不会自动出现滚动条。扩展时可以把高度改为按内容自适应或改用 List 自带的滚动能力让长名单也能完整浏览。六、可扩展方向这个页面已经具备完整的基本流程在此基础上可以轻松做出很多有意思的变体不放回抽取把抽中的名字从名单移除或标记为「已抽」保证一轮点名内不重复适合「点完一个少一个」的场景。减速动画模仿抽奖机「先快后慢」用 setTimeout 递归调用并逐渐拉长间隔最后停在选中项上观感更专业也更适合年会抽奖。音效与震动配合音频组件播放开始与定格音效、用 Vibration 在定格瞬间震动增强现场氛围。名单持久化用 Preferences 保存名单下次打开自动恢复避免每次手动录入。权重点名给每个名字配权重按加权随机算法抽取适合「有偏向」的需求比如让被点名次数少的同学更容易被抽中。分组模式一次抽取多个人组成小组配合卡片展开动画展示结果适合课堂分组活动。数据统计记录每次点名历史统计被点频率形成「被点红人榜」让课堂互动更有趣味。如果要做成正式产品还可以考虑点名结果一键复制或分享到班级群按周、按月维度生成点名报表给姓名列表提供批量导入导出在停止瞬间增加一个放大或翻转动画强调选中者。每个方向都可以复用本页现有的状态机骨架把新的 UI 元素挂到 running 与 current 这两个核心状态上即可改造成本并不高。七、常见问题与调试技巧这一节汇总了阅读源码或真机调试时最可能遇到的几个问题多数都可以在动手前就预防真正遇到时也知道该往哪个方向排查。定时器停不下来检查 stop() 是否因为 if (!this.running) return 提前返回而跳过了 clearInterval也要检查是否在别处又启动了一个新的定时器覆盖了句柄。页面返回后还在滚动确认 aboutToDisappear 里做了 clearInterval否则页面销毁后回调继续执行可能去操作已经销毁的组件而报错。高亮不动确认回调里更新的是 this.highlight 而不是某个局部变量State 变量必须通过 this. 访问才会触发刷新。删除后名单不刷新State 数组务必用新引用更新concat / slice原地 splice 不会触发渲染这是声明式 UI 最常见的翻车点。添加重复名字indexOf 是全等比较注意输入的前后空格要先 trim 再比较否则「张三」和「 张三」会被当成两个名字。随机是否公平Math.random 是伪随机对点名场景足够若要更公平可先把名单洗牌再按顺序取用。调试技巧在 DevEco Studio 里对 start() 和 stop() 打上断点观察 timer 句柄与 running 的变化可以快速理清定时器生命周期高亮样式可以先在 Previewer 里验证不必每次都用真机。回调节引用陈旧如果确实遇到「删除后定时器还在跑」的诡异现象多半是 removeName 里漏了 stop()或者回调误用了局部拷贝的数组去更新。把回调里对 this.names 的访问统一改成最新引用就能避免读到过期数据。参数手感调优把 100ms 抽成常量并在文件顶部注释说明方便日后统一调整测试时可以用 500ms 慢速观察高亮是否精确跟随随机下标确认逻辑正确后再改回正式节奏。八、总结Index24 用约两百行代码实现了一个功能完整、交互流畅的随机点名工具。它把 ArkTS 的几个关键技术点串联得非常自然setInterval 定时器负责「滚动」、Math.random 负责「随机」、State 与不可变数组更新负责「渲染」、running 标志位负责「状态机」、aboutToDisappear 负责「安全清理」。同时添加与删除名单的边界处理判空、查重、删除前停止也展示了产品化的细节思考。如果把 stop() 里的 toast 替换成带按钮的对话框、再给定格瞬间加一个音效这个页面就能直接变成年会抽奖的雏形。相比示例 23 的「输入 → 计算 → 分页展示」这个页面进一步引入了「定时驱动 随机 列表增删」的实时交互模型两者结合起来基本覆盖了 ArkTS 页面开发中高频使用的核心能力。无论是学习定时器的标准用法还是想要一个可以改造成抽奖大屏的起点这份源码都值得仔细研读动手改一改参数你会更快理解背后的运行机制。最后给读者留一个小练习试着把「停止后自动弹出一个确认对话框」加入流程观察在 running 与定时器配合下对话框的弹出时机如何把握或者把名单换成英文昵称或数字编号验证随机与高亮逻辑是否与语言无关。通过这样的改动你会真正掌握声明式 UI 与定时器协同工作的组合拳。