平面界面里,用户的眼睛被屏幕边界框着,注意力顶多在矩形内跳来跳去。空间界面没有这个框,用户能看的地方变多了,注意力反而更容易散。这时候设计的活儿就是主动引导:明确告诉用户"先看这里,再看那里"。焦点和景深是两把最顺手的工具。
焦点怎么建立
让一个元素成为视觉焦点,可用的手段不止放大一种。把它们列全了,才好知道什么时候用哪个:
| 手段 | 具体做法 | 强度 | 副作用 |
|---|---|---|---|
| 尺度 | 放大元素、缩短视距 | 强 | 占视野,挤压周围 |
| 亮度 | 提高brightness,焦点更亮 | 中 | 过亮会刺眼 |
| 对比 | 焦点明、背景暗,或反色 | 强 | 对比过强显生硬 |
| 位置 | 放在视线中心、中轴线上 | 强 | 与舒适区绑定 |
| 颜色 | 焦点用饱和色,背景降饱和 | 中 | 品牌色过多会失效 |
| 运动 | 焦点有微动,背景静止 | 强 | 滥用会干扰阅读 |
| 清晰度 | 焦点实,背景虚 | 强 | 算力开销 |
这些手段单独用都成立,但叠两到三种就够。七种全上,用户看到的是一团互相打架的信号,焦点反而没了。经验值是:尺度 + 清晰度作为主手段,亮度或颜色作为辅助,运动只在需要"抓住"用户时用。
一个常见问题:焦点一堆,等于没有焦点。空间界面里,同一时刻的强焦点建议只留一个,其余降一档。
景深(DoF)为什么能引导注意力
景深原本是光学概念——镜头对焦在某段距离上,那段距离内清晰,前后逐渐模糊。人眼本身也有这个机制,所以"清晰 vs 模糊"天然对应"关注 vs 忽略",不需要额外学习。
在 ArkUI 里没有真正的物理景深相机,但可以用视觉信号模拟,而且效果够用:
- 模糊:
blur(value)给离焦层加模糊,值越大越虚,单位 px; - 亮度:
brightness(value)乘数调亮度,值小于 1 变暗(1 为原样); - 饱和度:
saturate(value)降饱和(1 为原样),让离焦内容退成灰调; - 透明度:
opacity降透明,让离焦层更"退后"; - 线性渐变模糊:
linearGradientBlur可以只在某个方向上渐次模糊,适合做"近处清晰、远处渐散"的空间纵深。
组合起来就是一套"离焦权重":
// 离焦程度 0~1:0 为完全聚焦,1 为完全离焦@Builderdefocused(level:number){Column(){// 组件内容}.blur(level*14)// 模糊随离焦加深.brightness(1-level*0.35)// 变暗,退到次要位置.saturate(1-level*0.6)// 降饱和,收掉颜色干扰.opacity(1-level*0.25)// 稍微透明}这几个值按同一个level联动,比各调各的要稳。聚焦态level = 0,完全一致地回到原样。
视觉权重怎么分配
多层信息同时在场时,给每层定一个"权重预算",避免所有东西都在喊"看我"。下面是一份可直接套用的起始表,权重总和控制在 100% 以内:
| 层级 | 尺度 | 亮度系数 | 饱和度 | 模糊 px | 透明度 | 权重预算 |
|---|---|---|---|---|---|---|
| 焦点 | 1.0 | 1.05 | 1.0 | 0 | 1.0 | 55% |
| 次焦点 | 0.95 | 0.9 | 0.8 | 0~4 | 0.95 | 25% |
| 内容 | 0.9 | 0.8 | 0.7 | 6~10 | 0.9 | 15% |
| 环境 | 0.8 | 0.6 | 0.5 | 14~20 | 0.8 | 5% |
这套值不是硬规定,是"起步价"。真正落地时按场景调,但层级之间要拉开可见的差距,相邻两层差别太小,用户读不出主次。
焦点切换的参数化实现
把上面这张表写成一个可复用的焦点档位,用枚举表达语义,代码里就不会到处散落魔法数字。
// 焦点档位:越靠前权重越高enumFocusLevel{Focus=0,// 焦点Secondary=1,// 次焦点Content=2,// 内容Ambient=3// 环境}// 每个档位对应一组视觉参数interfaceFocusStyle{blur:numberbrightness:numbersaturate:numberopacity:numberscale:number}functionstyleOf(level:FocusLevel):FocusStyle{switch(level){caseFocusLevel.Focus:return{blur:0,brightness:1.05,saturate:1.0,opacity:1.0,scale:1.0}caseFocusLevel.Secondary:return{blur:3,brightness:0.9,saturate:0.8,opacity:0.95,scale:0.96}caseFocusLevel.Content:return{blur:8,brightness:0.78,saturate:0.68,opacity:0.9,scale:0.92}default:return{blur:16,brightness:0.6,saturate:0.5,opacity:0.8,scale:0.88}}}案例:空间化通知中心的聚焦切换
手机通知中心就是把一堆通知平铺排列,用户靠滑动和分组找重点。空间化后,可以让通知卡片围成一圈或几排,用户把视线落在哪张卡上,那张卡就自动进入焦点档,其余按距离退到次焦点、内容、环境档。
// NotificationOrbit.ets —— 通知卡片环形排布,聚焦项自动升权@Entry@Componentstruct NotificationOrbit{// 当前聚焦的卡片索引,-1 表示无聚焦@StatefocusIndex:number=-1// 示例通知数据privatenotices:string[]=['日程提醒','系统更新','消息回复','待办临近','快递到达','账单提醒']// 根据与聚焦项的距离推导档位:越远越离焦privatelevelFor(index:number):FocusLevel{if(this.focusIndex<0)returnFocusLevel.Content// 未聚焦时全部等权constdist=Math.abs(index-this.focusIndex)if(dist===0)returnFocusLevel.Focusif(dist===1)returnFocusLevel.Secondaryif(dist===2)returnFocusLevel.ContentreturnFocusLevel.Ambient}build(){Stack({alignContent:Alignment.Center}){ForEach(this.notices,(text:string,index:number)=>{Column(){Text(text).fontSize(16).fontColor(Color.White)}.width(160).height(96).borderRadius(16).backgroundColor('#1E2740')// 用角度把卡片摆成一圈,转头的动作对应切换聚焦.rotate({angle:index*30,centerX:'50%',centerY:'700%',perspective:1200}).applyFocus(this.levelFor(index))// 自定义扩展:套用焦点档位.onHover((isHover:boolean)=>{// 视线/指针落在哪张卡上,哪张卡成为焦点this.getUIContext()?.animateTo({duration:220,curve:Curve.Friction},()=>{this.focusIndex=isHover?index:-1})})},(text:string)=>text)}.width('100%').height('100%').backgroundColor('#0B1020')}}// 通过 AttributeModifier / 自定义修饰器把 FocusStyle 落到组件上@Extend(Column)functionapplyFocus(level:FocusLevel){.blur(styleOf(level).blur).brightness(styleOf(level).brightness).saturate(styleOf(level).saturate).opacity(styleOf(level).opacity).scale({x:styleOf(level).scale,y:styleOf(level).scale})}这套逻辑的关键是档位由"距离"推导,而不是每张卡单独设状态。聚焦项一动,整排的权重自动重排,不用维护一堆if。onHover在这里只是示意入口,实际项目里换成onFocus、视线追踪回调或者指针位置都可以。
聚焦与离焦的对照
| 维度 | 聚焦(Focus) | 离焦(Defocus) |
|---|---|---|
| 距离感 | 近,前移 | 远,后退 |
| 清晰度 | 实,blur=0 | 虚,blur 随距离递增 |
| 亮度 | 略提亮 | 压暗 |
| 颜色 | 保持饱和 | 降饱和 |
| 动效 | 前移、轻微呼吸 | 静止、退让 |
| 交互 | 接收输入 | 关闭命中 |
小小经验
- 同一时刻一个强焦点。两个同等强度的焦点会互相削弱,用户的视线会在两者间来回跳。
- 模糊是"降权",不是装饰。如果只是想加个好看的虚化,别用 blur,它会被用户读成"这里不用看"。
- 聚焦切换要快。200~250ms 左右,比深度层级切换(300~400ms)更快,因为它是高频动作。
- 给离焦内容留"回来的路"。离焦不等于消失,保留轮廓和轻度可读性,用户才知道可以移回去。
- 亮度优先于模糊做降权。模糊开销大,能用亮度和饱和度区分的场合,别急着上 blur。
容易出问题的地方哦
- 所有元素一起提亮做焦点。提亮是全屏相对概念,全都亮等于全都没亮,得留暗部做对比。
- blur 值给太大。超过 20px 的内容基本认不出,用户会以为加载失败,而不是"这里次要"。
- 聚焦态还保持可点。离焦的卡片如果还能点,用户误触后会觉得"我没看它怎么触发了",建议离焦层关闭或降低命中优先级。
- 只改视觉不改顺序。焦点在视觉上是前面的,但如果
zIndex没跟着调,它可能被后面的卡片盖住。 - 动画里同时改太多属性。blur、brightness、scale 一起重算,低端设备容易掉帧,建议聚焦切换时限制在 2~3 个属性同时变化。
- 忽略个体差异。有的人对运动敏感,大范围的聚焦动画可能引起不适,建议给系统级的"减少动效"开关留一个退化路径。