
1. 六代希沃一体机侧边栏改造的背景与核心思路1.1 为什么要在侧边栏做随机抽选六代希沃一体机在教室里的使用频率极高老师日常上课最常用的几个动作无非是打开白板、切换课件、调用班级优化大师、随机点人回答问题。前三个动作系统都给了比较顺手的入口唯独“随机抽选”这个高频需求在六代机的侧边栏里默认是没有的。很多老师要么退出去开班级优化大师要么用第三方小工具操作链路一长课堂节奏就被打断了。我所在的学校去年批量换装了六代机老师们反馈最多的就是“能不能在侧边栏直接点一下就能抽人”。这个需求听起来小但它是真实发生在每一节课上的。侧边栏本身就是希沃给老师预留的快捷操作区把随机抽选塞进去等于把最高频的课堂互动动作压缩到一次点击这个改造的价值就在这里。需要说明的是六代希沃一体机运行的是基于 Android 深度定制的教学系统侧边栏本质上是系统层的一个悬浮窗服务它并不是一个完全开放的插件市场。所以“添加功能”这件事本质上是在系统允许的范围内通过侧边栏的自定义入口去挂载一个我们自己的抽选应用或网页。这一点想清楚了后面的方案选型才不会走偏。1.2 三种可行方案与选型逻辑在动手之前我把能走的路都捋了一遍大致有三种方案A利用侧边栏已有的自定义应用入口。六代机的侧边栏设置里部分系统版本允许添加“自定义应用”快捷方式。把抽选程序打包成一个 APK 装到系统里再把它挂到侧边栏。优点是体验最原生点一下直接拉起缺点是不同批次的六代机系统版本差异较大有的版本这个入口被隐藏了。方案B用侧边栏的网页快捷方式挂本地 HTML。把抽选逻辑写成一个单文件 HTML放到本地存储通过侧边栏的浏览器快捷方式打开。优点是零安装、跨版本兼容性好缺点是首次打开有加载感且需要处理本地文件的访问权限。方案C借助第三方侧边栏工具做中转。在系统里装一个支持自定义快捷方式的悬浮侧边栏工具把抽选功能挂到它上面。优点是灵活缺点是会和系统原生侧边栏产生视觉和操作上的冲突老师容易点错。我最终选的是方案A为主、方案B为备的组合策略。原因很直接六代机的原生侧边栏在视觉层级上是最高的老师已经形成了肌肉记忆任何额外的悬浮工具都会造成干扰。方案A能跑通就优先用A跑不通的机器退回B保证每个班都能用上。这个取舍的核心逻辑是课堂工具的稳定性优先级高于功能的丰富度宁可少一个动画效果也不能出现点不开的情况。1.3 改造前必须确认的三件事动手之前有三件事必须先确认否则后面全是返工第一确认机器的系统版本和侧边栏版本。在“设置-关于本机”里能看到 Android 版本号和希沃系统版本号。六代机常见的系统版本跨度比较大侧边栏的设置项位置和可添加的应用类型都不一样。我建议先把版本号记下来后面排查问题时这是第一手信息。第二确认是否开启了未知来源应用安装权限。要装自己打包的 APK这个权限是前提。部分学校统一管控的机器这个权限被锁了那就只能走方案B。第三确认侧边栏是否处于可编辑状态。有的机器侧边栏被管理员锁定长按不出现编辑按钮。这种情况需要先联系设备管理员解锁不要硬试硬试容易触发系统的保护机制。提示改造前建议先用U盘把原始侧边栏的配置截图保存一份万一改乱了可以对照恢复。这个习惯我养成了很多年救过好几次场。2. 随机抽选功能的核心细节与实操要点2.1 抽选逻辑到底该怎么设计很多人一上来就想着写个随机数但课堂抽选和普通的随机数生成是两回事。我踩过的坑是第一版直接用Math.random()抽结果连续三次抽到同一个学生老师当场就尴尬了。课堂场景对“随机”的期待其实是均匀且不重复而不是数学意义上的纯随机。所以抽选逻辑我做了三层设计去重层维护一个已抽名单抽过的人在当前轮次内不再出现直到所有人都抽过一遍才重置。这解决了连续抽到同一个人的问题。权重层支持给个别学生设置权重比如某个学生最近状态不好老师想多给他机会可以调高权重。这个功能不是必须的但加上之后老师会觉得“懂我”。展示层抽选过程要有短暂的滚动动画最后定格。纯瞬间出结果会让课堂缺少那个“悬念感”滚动动画本身就是课堂互动的一部分。数据结构上我用一个数组存名单一个 Set 存已抽记录一个 Map 存权重。抽选时先过滤掉已抽的再按权重做加权随机。这个逻辑不复杂但每一步都对应着真实的课堂需求。2.2 名单数据的存储与读取名单不能每次打开都重新输入必须持久化。六代机的本地存储有几个选择localStorage、IndexedDB、以及写到应用私有目录的文件。我选的是localStorage原因是抽选程序本身很轻名单数据量小一个班最多几十人localStorage 的 5MB 容量绰绰有余而且读写是同步的代码简单不容易出 bug。存储结构我设计成这样{ className: 高二(3)班, students: [张三, 李四, 王五], drawn: [张三], weights: {李四: 2}, lastUpdated: 2025-01-15T10:30:00 }这里有个细节要注意drawn数组在每次抽选后要同步更新并且要在页面关闭前确保写入。我遇到过页面被系统直接杀掉导致数据没存上的情况后来改成每次抽选后立即写入问题就没了。虽然多了一点 IO但数据安全比性能重要。另外名单导入我做了两种方式手动逐个添加以及粘贴批量导入。批量导入用换行或逗号分隔都支持老师从 Excel 里复制一列名字直接粘进来就能用。这个细节看着小但省了老师大量输入时间。2.3 侧边栏挂载的关键操作把抽选程序挂到侧边栏是整个改造里最容易卡住的一步。我分两种情况说情况一系统侧边栏支持自定义应用。进入侧边栏编辑模式找到“添加应用”或“自定义快捷方式”从已安装应用列表里选中我们的抽选 APK。这里有个坑部分系统版本只显示系统预装应用不显示第三方应用。遇到这种情况需要先把 APK 安装到系统应用目录需要 root 或系统签名普通安装方式挂不上去。如果学校机器没有 root这条路就走不通直接转方案B。情况二系统侧边栏只支持网页快捷方式。把抽选 HTML 放到/sdcard/xuanji/index.html然后在侧边栏添加网页快捷方式地址填file:///sdcard/xuanji/index.html。这里要注意 Android 的文件访问权限部分系统版本对file://协议有限制需要改用content://或者把文件放到应用可访问的目录。我实测下来放到应用私有目录再通过 WebView 加载是最稳的。注意不要试图去修改系统侧边栏的 APK 文件六代机有系统完整性校验改了大概率开不了机。这个坑我替你们踩过了代价是一台机器返厂。2.4 界面适配与触控优化一体机的屏幕大老师站在屏幕前操作手指点击的精度远不如手机。所以抽选界面的按钮必须做大最小触控区域我建议不小于 80px。字体也要大保证后排学生也能看清抽选结果。配色上我用的是高对比度方案深色背景配亮色文字抽选结果用大号字体居中显示。教室光线复杂浅色背景在强光下反光严重深色背景更稳。这个细节是实际用下来才体会到的第一版用白色背景下午阳光一照屏幕上的字基本看不清。另外抽选按钮我做了防误触处理点击后 500ms 内不接受第二次点击。老师有时候手快会连点没有防抖的话会连续抽两次结果就乱了。3. 完整实操流程与核心环节实现3.1 开发环境与工具准备抽选程序我选择用纯前端技术栈写HTML CSS JavaScript不依赖任何框架。原因很简单一体机上的 WebView 版本参差不齐用框架容易遇到兼容性问题纯原生写法最稳。开发工具用 VS Code调试用 Chrome 的远程调试功能连到一体机上。如果你要打包成 APK需要一个简单的 WebView 壳。我用的是 Android Studio 建一个空项目主 Activity 里放一个全屏 WebView 加载本地 HTML。这个壳代码量很少核心就是几行配置。打包时注意 minSdkVersion 要覆盖六代机的系统版本我设的是 API 24实测覆盖了绝大多数机器。文件目录结构我这样组织xuanji/ ├── index.html # 主界面 ├── style.css # 样式 ├── app.js # 抽选逻辑 ├── data.js # 名单数据管理 └── assets/ # 图标等资源3.2 抽选核心代码实现抽选的核心逻辑我单独放在app.js里关键函数是pickStudent()。先看加权随机的实现function weightedRandom(students, weights) { // 计算总权重 let totalWeight 0; students.forEach(name { totalWeight (weights[name] || 1); }); // 生成随机点 let random Math.random() * totalWeight; // 找到对应的学生 for (let name of students) { random - (weights[name] || 1); if (random 0) { return name; } } return students[students.length - 1]; }这个加权随机的思路是把每个学生的权重想象成一段线段的长度权重越大线段越长随机点落在这段线段上的概率就越大。这是加权抽样的标准做法比“按权重复制多份再随机”要高效得多。然后是去重逻辑function pickStudent() { // 获取未抽过的学生 let available students.filter(name !drawn.includes(name)); // 如果都抽过了重置 if (available.length 0) { drawn []; available [...students]; } // 加权随机 let picked weightedRandom(available, weights); // 记录已抽 drawn.push(picked); saveData(); return picked; }这里有个边界情况要处理如果名单里只有一个学生available长度为 1加权随机直接返回这个学生逻辑是对的。但如果名单为空要给出提示而不是报错。我在调用pickStudent()之前加了一个空名单检查。3.3 滚动动画的实现细节滚动动画我用的是requestAnimationFrame做逐帧更新而不是 CSS 动画。原因是 CSS 动画的时长和缓动曲线不好精确控制而课堂场景需要动画时长刚好我设的是 1.5 秒太快没悬念太慢老师等得着急。动画逻辑是这样的先快速滚动显示随机名字速度逐渐减慢最后定格在抽选结果上。关键代码如下function rollAnimation(finalResult, callback) { const duration 1500; // 总时长1.5秒 const startTime performance.now(); function frame(currentTime) { const elapsed currentTime - startTime; const progress Math.min(elapsed / duration, 1); // 缓动函数先快后慢 const eased 1 - Math.pow(1 - progress, 3); if (progress 1) { // 滚动中显示随机名字 const randomName students[Math.floor(Math.random() * students.length)]; displayName(randomName); requestAnimationFrame(frame); } else { // 结束显示最终结果 displayName(finalResult); callback callback(); } } requestAnimationFrame(frame); }缓动函数1 - Math.pow(1 - progress, 3)是三次缓出效果是开始快、结尾慢视觉上很自然。这个函数我调了好几次参数二次缓出结尾还是有点急三次刚好。3.4 名单管理界面的实现名单管理我做成一个可折叠的面板默认收起点击“管理名单”才展开。这样主界面保持干净老师上课时不会被管理功能干扰。管理面板里支持添加单个学生、批量粘贴导入、删除学生、清空名单、重置已抽记录。批量导入的解析逻辑要处理多种分隔符function parseNames(input) { // 支持换行、逗号、顿号、空格分隔 return input .split(/[\n,、\s]/) .map(name name.trim()) .filter(name name.length 0); }这个正则[\n,、\s]覆盖了换行、英文逗号、中文逗号、顿号、空白字符。老师从 Excel 复制过来的名字通常是换行分隔的从 Word 复制可能是逗号或顿号这个正则都能处理。导入后要去重避免同一个名字出现两次。去重我用 Setconst uniqueNames [...new Set(parsedNames)];3.5 部署到一体机的完整步骤部署流程我整理成了一张表按顺序操作就行步骤操作内容注意事项1开启未知来源安装权限设置-安全-未知来源2安装抽选 APK用U盘拷贝安装包3打开抽选程序测试确认名单功能正常4进入侧边栏编辑模式长按侧边栏空白处5添加自定义应用从应用列表选抽选程序6调整侧边栏图标位置拖到顺手的位置7退出编辑模式测试点击图标确认能拉起如果第5步找不到抽选程序说明系统侧边栏不显示第三方应用转方案B把 HTML 文件放到/sdcard/xuanji/侧边栏添加网页快捷方式地址填file:///sdcard/xuanji/index.html。提示部署完成后建议重启一次机器确认侧边栏配置持久化生效。有的机器不重启的话侧边栏配置不会写入系统。4. 常见问题排查与避坑经验实录4.1 侧边栏图标点了没反应这是最常见的问题排查思路按顺序来先确认抽选程序本身能不能独立打开。从应用列表里直接点开如果能打开说明程序没问题问题出在侧边栏的挂载上。如果独立打开也失败那就是程序本身的问题先解决程序。侧边栏挂载的问题通常是这几种快捷方式指向的路径变了APK 被卸载重装后路径会变、系统侧边栏缓存了旧的快捷方式信息、或者权限不足。解决办法是删掉侧边栏的快捷方式重新添加一般能解决。如果还不行重启机器清一下侧边栏缓存。4.2 抽选结果和显示不一致这个问题我遇到过原因是动画的最终结果和实际抽选结果用了两次随机。正确的做法是先抽选确定结果再拿这个结果去做动画动画过程中显示的是随机名字但最终定格必须是那个已经确定的结果。如果动画里又抽了一次就会出现显示和记录不一致的情况。代码上的关键点是pickStudent()只调用一次把返回值传给rollAnimation()动画结束时显示这个传入的值而不是再抽一次。4.3 名单数据丢失数据丢失通常发生在程序被系统强制杀掉的时候。Android 系统在内存紧张时会杀后台如果抽选程序在后台被杀而数据还没写入 localStorage就会丢。解决办法有两个一是每次抽选后立即写入不要等页面关闭二是用beforeunload事件做兜底写入。两个都加上基本不会丢数据了。window.addEventListener(beforeunload, function() { saveData(); });4.4 不同班级名单切换麻烦一个老师通常带多个班每次换班都要重新导入名单太麻烦。我的做法是支持多班级管理每个班级一份独立数据用班级名做 key 存在 localStorage 里。界面上加一个班级切换下拉框切换时加载对应数据。存储结构改成这样{ currentClass: 高二(3)班, classes: { 高二(3)班: { students: [...], drawn: [...], weights: {...} }, 高二(5)班: { students: [...], drawn: [...], weights: {...} } } }这个改动让程序的实用性上了一个台阶老师不用每次换班都折腾名单了。4.5 常见问题速查表问题现象可能原因解决办法侧边栏图标点击无反应快捷方式路径失效删除后重新添加抽选结果与显示不符动画中重复随机只抽一次结果传入动画名单数据丢失未及时写入存储每次操作后立即保存批量导入名字有乱码编码格式不对统一用 UTF-8 编码动画卡顿设备性能不足降低动画帧率或缩短时长侧边栏不显示第三方应用系统版本限制改用网页快捷方式方案触控不灵敏按钮区域太小触控区域不小于80px换班后名单没变未切换班级数据使用多班级管理功能4.6 几个我踩过的坑第一个坑是系统更新导致侧边栏配置重置。六代机有一次系统推送更新后侧边栏的自定义快捷方式全没了。后来我养成了习惯每次系统更新后检查侧边栏配置并且把抽选程序的安装包和配置文件在U盘里备份一份随时能恢复。第二个坑是名单里的特殊字符。有学生的名字里有生僻字在某些字体下显示成方框。解决办法是嵌入一个覆盖生僻字的字体文件或者用系统默认字体渲染。我选的是后者因为嵌入字体会增加程序体积。第三个坑是多台机器同步名单。一开始我想做云端同步后来发现学校网络环境复杂云端同步反而增加了不确定性。最后改成用U盘手动同步名单导出成 JSON 文件拷到其他机器导入。虽然土但稳定可靠不会因为网络问题掉链子。第四个坑是侧边栏图标被学生误删。有学生好奇长按侧边栏把抽选图标拖没了。后来我在侧边栏设置里加了锁定功能锁定后需要密码才能编辑。这个功能系统自带在侧边栏设置里找“锁定侧边栏”就行。4.7 性能优化的一点经验抽选程序本身很轻但在老机器上还是做了些优化。主要是减少 DOM 操作滚动动画不重新创建元素而是复用同一个文本节点只改textContent。这个改动让动画在老机器上也能跑满 60 帧。另外名单数据在内存里缓存一份不要每次抽选都从 localStorage 读。localStorage 的读写在主线程上是同步的频繁读写会阻塞 UI。我的做法是启动时读一次到内存之后所有操作都在内存里做只在保存时写回 localStorage。这些优化单独看都不起眼但叠加起来在老机器上的体验提升是明显的。课堂工具的第一要求是流畅卡一下老师的节奏就断了。最后分享一个实际使用中的小技巧抽选按钮旁边我加了一个“再抽一次”的快捷按钮但它的逻辑是“把当前结果放回池子重新抽”。这样老师如果觉得抽到的人不合适可以快速重抽而不用去管理界面重置。这个功能用到的频率比我想象的高算是意外收获。