ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【共创稿事节】HarmonyOS 7视线追踪交互设计:凝视点选与注视触发

【共创稿事节】HarmonyOS 7视线追踪交互设计:凝视点选与注视触发 用手指点屏幕点和选是同一个动作用户不会怀疑自己到底点了没有。换成视线就麻烦了人眼每分钟要做上万次无意识的扫视如果每看一眼就触发一次界面会疯掉。这就是视线交互要解决的核心矛盾——如何从连绵不断的眼动里分辨出哪一次才是我想点它。这篇讲凝视点选Gaze Selection的原理、注视触发Dwell的参数设计以及怎么把抖动和误触压下去。凝视点选与迈达斯之触视线交互最早、也最直接的实现是凝视点选系统持续计算注视点坐标落在哪个可交互目标上就把哪个目标当作当前选择。这个方案会遇到一个著名的难题叫迈达斯之触Midas Touch。古希腊神话里迈达斯王碰什么什么变金子最后连食物都吃不了。视线交互也是这个困境眼睛看到哪里哪里就被碰了。用户只是习惯性地瞟一眼时间结果打开了日历想看清楚某个按钮的字结果把它按了。迈达斯之触的根子在于注视Fixation和意图Intent不等价。人看东西分两类动作扫视Saccade快速跳动视线从 A 跳到 B持续 20~40ms是无意识的。注视Fixation视线在某点附近停留持续 100ms 以上通常对应认知加工。但即便是一次 300ms 的注视也可能只是我在读它不等于我要激活它。所以纯凝视点选几乎不可用。工程上必须引入一个额外的、明确的信号来区分看和选。常见的四种解法方案触发信号优点缺点纯凝视注视达阈值即触发无需其他设备迈达斯之触严重基本不可用注视 停留Dwell注视持续超过 N 毫秒仅靠眼动即可完成等待感强误触仍在注视 手势确认注视选目标手势提交误触率极低心智清晰需手势设备多一步操作注视 眨眼识别特定眨眼模式单手可完成眨眼误识别率高疲劳实际产品里注视 停留和注视 手势用得最多前者适合无手的浏览场景后者适合精确操作。注视触发的时长与反馈如果真的要用 Dwell停留触发时长就是最关键的参数。设短了误触设长了让人等。业界摸索出的经验区间在400ms 到 1000ms之间低于 400ms正常阅读过程中的停驻就会被判为触发误触率飙升。高于 1000ms用户会产生明显的卡顿感尤其连续操作时很难受。600ms 左右是较常用的折中值适合大多数点击类操作。对于有破坏性的操作删除、支付可以拉长到 800~1000ms并且加二次确认。比时长更重要的是反馈。Dwell 最大的体验问题是等待期间界面毫无动静用户不知道系统在数数。好的做法是给一个与计时同步的视觉反馈让等待变成进度环形进度目标周围画一个圈随时间填充填满即触发。径向放大目标随停留逐渐放大到达阈值时弹一下确认。颜色渐深背景色随停留加深。反馈要让用户能预判还差多少。用户一旦看到圈快满了就会下意识多看半秒——这一下恰好把误触和漏触都降低了。还有几个细节计时必须在注视离开目标的瞬间清零不能跨目标累积。阈值可以动态对重要目标用较长阈值对高频操作用较短阈值。提供全局 Dwell 开关让不喜欢停留触发的用户关掉改用显式确认。抖动与误触的抑制眼动追踪的原始数据带有明显噪声直接拿来算注视点光标会像喝了咖啡一样抖。抑制分两层。数据层滤波与去噪最常用的是滑动平均或指数平滑EMA。视线信号的抖动频率比真实注视转移频率高低通滤波能压掉大部分。但滤波有代价——它引入延迟而视线交互对延迟极其敏感超过 100ms 用户就会觉得眼睛和光标不同步。所以要平衡平滑窗口不能太大。一个更聪明的做法是基于速度的过滤。眼动的速度分布是双峰的扫视时速度极高几百 deg/s注视时极低几 deg/s。设定一个速度阈值只有速度低于阈值时才算可能是注视高速段直接标记为扫视并跳过。逻辑层注视聚类与目标吸附原始注视点是连续坐标需要聚类成一次注视事件。常用算法是I-DTDispersion-Threshold Identification在时间窗内如果注视点的离散度最大最小坐标差小于某个阈值就判定为一次注视。聚类出注视后再把它吸附到最近的交互目标上。吸附有一个磁吸半径Magnet Radius超出半径就不算落在目标上。这样即使原始坐标略有偏差只要落在目标附近就能正确命中。否 扫视是 可能注视否是否是原始注视点流 60~120Hz瞬时速度是否低于阈值丢弃 不参与选择I-DT 聚类 时间窗离散度判断离散度是否小于阈值生成一次注视事件吸附到最近目标 磁吸半径判定是否命中目标启动 Dwell 计时 显示环形反馈误触防线三重保险数据滤波和聚类之后再加三道防线目标尺寸下限。视线精度有限通常 1~2 度视角误差小目标不要用纯视线选。可交互目标在视锥里至少要占 3 度以上。二次确认。破坏性操作必须二次确认Dwell 一次是选中再 Dwell 一次或加手势才是执行。可撤销。触发后提供明确、及时、低成本的撤销入口且撤销窗口不能太短。代码注视焦点状态机与 Dwell 反馈下面用 ArkUI 的状态管理与动画实现一个注视焦点 停留进度的可视化组件。onHover在这里作为注视点进入/离开目标的代理事件真实项目接入眼动 SDK 后替换数据源即可Dwell 进度用State驱动环形动画。Componentexportstruct GazeTarget{ProptargetId:string;Proplabel:string;StateisFocusing:booleanfalse;// 注视是否落在本目标StatedwellProgress:number0;// 停留进度 0.0~1.0Stateconfirmed:booleanfalse;// 是否已触发// 触发前参数停留阈值越长误触越少等待感越强privatereadonlyDWELL_MS:number600;privatedwellTimer:number-1;privatestartTs:number0;onConfirm?:(id:string)void;// 注视进入启动计时离开立即清零防止跨目标累积privatehandleFocusChange(focusing:boolean):void{if(focusingthis.isFocusing){return;}this.isFocusingfocusing;if(focusing){this.startDwell();}else{this.stopDwell();}}privatestartDwell():void{this.stopDwell();this.confirmedfalse;this.startTsDate.now();this.dwellTimersetInterval((){constelapsedDate.now()-this.startTs;this.dwellProgressMath.min(elapsed/this.DWELL_MS,1);if(this.dwellProgress1){this.stopDwell();this.confirmedtrue;this.onConfirm?.(this.targetId);// 触发确认回调}},16);// 约 60fps保证进度条顺滑}privatestopDwell():void{if(this.dwellTimer!-1){clearInterval(this.dwellTimer);this.dwellTimer-1;}this.dwellProgress0;}aboutToDisappear():void{this.stopDwell();}build(){Stack(){// 目标卡片注视时高亮并轻微放大Text(this.label).width(180).height(100).textAlign(TextAlign.Center).borderRadius(16).backgroundColor(this.confirmed?#2E9E5B:(this.isFocusing?#4A90D9:#E8E8E8)).scale(this.isFocusing?{x:1.05,y:1.05}:{x:1,y:1}).onHover((isHover:boolean){this.handleFocusChange(isHover);// 真实场景替换为注视点命中判定})// 环形停留进度只在注视且未确认时显示if(this.isFocusing!this.confirmed){Progress({value:this.dwellProgress*100,total:100,type:ProgressType.Ring}).width(120).height(120).color(#FFFFFF).style({strokeWidth:6}).hitTestBehavior(HitTestMode.None)// 不拦截事件避免影响定位}}}}这段代码有两个设计点。一是stopDwell在每次注视离开时把dwellProgress归零杜绝了扫过多个目标累计凑满进度这类误触。二是进度环hitTestBehavior设为None反馈层不能干扰命中测试否则动画元素自己会把注视点吃掉。真实接入眼动 SDK 时onHover换成注视点坐标与目标包围盒的相交判断即可其余逻辑不变。配合一个完整的注视 确认场景宿主组件负责把注视命中和手势确认拼起来EntryComponentstruct GazeDemo{StateconfirmedId:string;build(){Column({space:20}){Text(this.confirmedId?注视目标并保持 0.6 秒:已选中${this.confirmedId}).fontSize(18)Row({space:16}){GazeTarget({targetId:A,label:商品 A,onConfirm:(id:string){this.confirmedIdid;}})GazeTarget({targetId:B,label:商品 B,onConfirm:(id:string){this.confirmedIdid;}})}}.width(100%).height(100%).justifyContent(FlexAlign.Center)}}案例博物馆导览里的凝视展品看详情一个文旅展馆的空间应用面前陈列着若干 3D 展品模型每件展品旁边悬浮一块信息牌。用户戴着头显或者站在大屏前浏览。如果每件展品都用看一眼就展开详情用户会崩溃——浏览时眼睛到处扫详情面板会不停跳出来又收回去。这里的做法是分开两件事凝视预览注视展品超过 400ms展品缓慢旋转一圈信息牌显示一行简短的年代与名称。这一步不打开详情只是我注意到了。注视确认用户确实想看详情时注视信息牌上的展开按钮停留 600ms。环形进度填满详情面板从展品后方滑出。为什么给不同的目标不同阈值因为展品本体很大误触无所谓而展开按钮一旦误触会打断浏览节奏值得让用户多等 200ms 换更低的误触率。这种按目标重要程度分配阈值的做法在视线交互里很常见。细节上还加了两个保护详情面板打开后注视停留在面板外超过 1 秒才自动收起避免用户看别处一眼面板就没了面板收起有 200ms 的淡出过渡防止频繁闪烁。总结一下下先决定看和选分不分家。如果场景允许手势优先做注视手势把副作用交给显式动作只有在免手场景下才考虑 Dwell并且接受它必然存在的误触残余。Dwell 阈值按目标重要性分层。不要全局一个值。高频、低风险的操作可以短低频、高风险的操作要长。反馈是 Dwell 的一部分不是装饰。没有进度的等待对用户就是卡顿环形进度、径向放大这类反馈直接决定了 Dwell 能不能用。滤波延迟和抖动要一起调。平滑窗口开大压抖动但同时增加延迟视线交互对延迟的敏感度比想象中高找到平衡点需要配合真机实测纸面参数不可靠。一定要留开关。眼动追踪的个体差异很大戴眼镜、眼睛疲劳、光线变化都会影响精度。给用户一个关闭视线触发改用手势的入口比强行优化所有边缘情况划算。容易出问题的地方O计时跨目标累积。注视在 A 停了 300ms移到 B 又停 300ms如果计时不清零B 会在 600ms 时被误触发。这是最常见的实现 bug务必在目标切换时归零。反馈层拦截了命中测试。动画、进度环、粒子效果这些叠加在目标上的图层如果没有设置hitTestBehavior(HitTestMode.None)会把原本该落到目标上的交互截胡表现出来就是看着明明点中了却没反应。忘记处理数据缺失。用户眨眼、追踪丢失比如手遮住摄像头时数据流会中断。如果不做超时和重连处理Dwell 计时会用旧数据继续跑导致莫名其妙的触发。数据中断超过 100ms 就该清除状态。用线性插值处理注视点。注视点从 A 平滑移到 B 的过程中路径上会经过中间的目标如果对插值结果做命中判定会连续触发一串目标。要么只在注视事件级别判定要么对插值过程做屏蔽。忽略可访问性。不是所有人的眼动特征都标准也不是所有设备都有高质量眼动追踪。视线交互必须有一条不依赖它的降级路径否则就是把一部分用户挡在门外。
返回列表