【HarmonyOS 7新能力|030】沉浸光感工程封装:把接入逻辑放进可维护的分层结构

光随指动、局部高光和非线性形变可以让界面获得更强的空间感,但如果把坐标、动画与渲染全部堆进组件事件,常见结果就是抖动、松手后跳变、列表复用串状态,以及低性能设备上持续掉帧。
本文将沉浸光感拆成交互输入、坐标处理、动效状态、参数计算、渲染和治理六层,构建从输入到收敛的工程链路。示例类型、公式与阈值均为应用侧教学封装,不代表 HarmonyOS 7 官方 API 或性能承诺;具体组件能力和动效接口应以当前 SDK 与华为官方文档为准。
一、先明确效果服务的交互目标
光感不能只追求"炫"。它应帮助用户识别可交互区域、感知按压位置或理解卡片层级。若效果遮挡文字、误导点击范围或持续抢夺注意力,就偏离了交互目标。
第一版只作用于单个卡片:指针进入后高光平滑跟随,按压时产生受限形变,释放后回弹,离开或取消时收敛到静止。验收还包括低性能降级、减少动态效果模式、页面退出后停止更新。
二、统一不同输入源
触摸、鼠标和手写笔事件字段不同,上层动效不应分别维护三套算法。先映射为统一输入样本。
ts
type PointerKind = 'touch' | 'mouse' | 'pen'
interface PointerSample {
id: number
kind: PointerKind
phase: 'enter' | 'move' | 'down' | 'up' | 'leave' | 'cancel'
globalX: number
globalY: number
occurredAt: number
}
取消事件必须进入统一链路,不能只处理 up。系统手势抢占、窗口失焦和组件销毁都可能产生取消。
三、坐标先转换再归一化
光场中心应使用组件局部坐标。全局坐标直接参与计算,会在滚动、窗口移动和布局变化后偏移。
ts
interface LocalPoint { x: number; y: number }
function normalize(point: LocalPoint, width: number, height: number): LocalPoint {
if (width <= 0 || height <= 0) return { x: 0.5, y: 0.5 }
return {
x: Math.min(1, Math.max(0, point.x / width)),
y: Math.min(1, Math.max(0, point.y / height))
}
}
归一化后参数不再依赖像素尺寸,同一逻辑可复用于不同设备和卡片宽度。
四、速度需要基于时间计算
只看相邻坐标差会受事件采样频率影响。速度要除以时间差,并过滤时间倒退、间隔过大和异常跳点。
ts
interface MotionVector { vx: number; vy: number; speed: number }
function velocity(a: PointerSample, b: PointerSample): MotionVector {
const dt = Math.max(1, b.occurredAt - a.occurredAt)
const vx = (b.globalX - a.globalX) / dt
const vy = (b.globalY - a.globalY) / dt
return { vx, vy, speed: Math.hypot(vx, vy) }
}
速度用于调节拖尾和形变强度,但必须设置上限,避免单次异常事件把网格拉出边界。
五、状态机管理跟随与回弹

动效至少区分静止、进入、跟随、按压、释放、回弹和降级。用状态机比多个布尔值更容易保证取消和离开路径完整。
ts
type LightState = 'idle' | 'entering' | 'following' | 'pressed' | 'releasing' | 'settling' | 'reduced'
interface LightSession {
state: LightState
revision: number
target: LocalPoint
current: LocalPoint
intensity: number
}
每次重新开始交互增加 revision,旧动画回调只做清理,不能覆盖新状态。
六、光场参数与视觉实现解耦
光场计算输出中心、半径、强度和衰减,上层不关心最终使用渐变、着色器还是其他平台能力。
ts
interface LightField {
centerX: number
centerY: number
radius: number
intensity: number
falloff: number
}
function createField(point: LocalPoint, speed: number): LightField {
return { centerX: point.x, centerY: point.y, radius: 0.28,
intensity: Math.min(1, 0.45 + speed * 0.2), falloff: 1.8 }
}
参数必须限制在视觉系统允许范围,并在深浅色主题分别验收文字对比度。
七、非线性形变必须受边界约束

形变可以根据指针距离和速度改变局部网格,但不应改变真实点击区域,也不应让内容越过卡片裁剪边界。
ts
function boundedDeformation(distance: number, pressure: number): number {
const d = Math.min(1, Math.max(0, distance))
const p = Math.min(1, Math.max(0, pressure))
const eased = 1 - Math.pow(d, 2)
return Math.min(0.04, eased * p * 0.04)
}
这里的数值是示意。实际幅度需结合文本可读性、触控命中和目标设备表现确定。
八、每帧只提交一次视觉状态
输入事件可能比屏幕刷新更密集。不要在每个 move 回调中立即触发完整重绘,而是保存最新目标,在帧回调中合并计算并提交一次。
ts
class FrameAccumulator {
private latest: PointerSample | null = null
push(sample: PointerSample): void { this.latest = sample }
take(): PointerSample | null {
const value = this.latest
this.latest = null
return value
}
}
这种"保留最新值"的背压策略适合连续视觉反馈,不适合需要逐点保留的签名或绘图场景。
九、用弹簧收敛避免松手跳变
释放时直接把参数归零会产生明显断层。可以用受控弹簧或缓动逐步回到静止,并在误差足够小时停止帧调度。
ts
interface SpringState { value: number; velocity: number }
function springStep(s: SpringState, target: number, dt: number): SpringState {
const stiffness = 180
const damping = 24
const acceleration = stiffness * (target - s.value) - damping * s.velocity
return { value: s.value + s.velocity * dt, velocity: s.velocity + acceleration * dt }
}
dt 要设上限,应用从后台恢复后的巨大时间差不能直接送入积分。
十、性能分级要在运行时生效
治理层观察帧耗时和设备状态,选择完整、均衡、节能或静态反馈。降级应减少网格密度、关闭拖尾或只保留简单高光,而不是突然消失。
ts
type EffectTier = 'full' | 'balanced' | 'lite' | 'static'
function chooseTier(frameCostMs: number, reduceMotion: boolean): EffectTier {
if (reduceMotion) return 'static'
if (frameCostMs > 24) return 'lite'
if (frameCostMs > 16) return 'balanced'
return 'full'
}
阈值需通过目标设备测量调整,示例不代表平台标准。
十一、生命周期与列表复用必须清理
组件不可见、页面失焦、窗口切换或列表单元复用时,应停止帧回调、清空指针 ID 并让状态安全收敛。状态应绑定稳定业务 ID,不能只绑定列表下标。
ts
function deactivate(session: LightSession): LightSession {
return { ...session, revision: session.revision + 1, state: 'idle',
target: { x: 0.5, y: 0.5 }, current: { x: 0.5, y: 0.5 }, intensity: 0 }
}
销毁后到达的回调只能丢弃,不能重新启动调度器。
十二、用边界场景完成验收
测试覆盖:快速进出、按下后取消、双指切换、鼠标离开、手写笔悬停、滚动中的坐标变化、组件尺寸为零、列表复用、横竖屏切换、深浅色主题、帧耗时升高和减少动态效果模式。
同时确认视觉形变不改变点击命中,文字对比度不降低,回弹最终停止且多轮交互后没有残留计时器。只有主路径和降级路径都稳定,沉浸光感才具备产品级可用性。
沉浸动效的工程价值不在参数越多,而在输入、状态、计算、渲染和治理之间边界明确。通过归一化坐标、帧合并、受限形变、平滑收敛和可访问降级,光随指动才能保持自然,同时不牺牲稳定性与可用性。