HarmonyOS APP开发跨设备输入
背景
你有没有遇到过这种场景:用电视看视频,想快进一下,但电视遥控器操作太麻烦;或者在平板上展示PPT,想用手机当激光笔;又或者在电脑上玩游戏,想用手机当手柄。
这就是分布式输入要解决的问题——让输入事件可以跨设备流动,一个设备的输入可以控制另一个设备的应用。
传统方案下,输入设备是绑死在主机上的。键盘只能控制连接的电脑,触摸屏只能控制所在的设备。想要跨设备输入,要么用专门的远程控制软件,要么用蓝牙配对的外设,体验都不够原生。
HarmonyOS的分布式输入能力,让输入设备变成了"共享资源"。你的手机可以变成电视的遥控器,平板可以变成电脑的触摸板,手表可以变成手机的快捷按键。更重要的是,这一切对应用是透明的——应用不需要关心输入来自哪个设备,只需要响应输入事件。
核心原理
分布式输入架构
分布式输入基于以下几个核心概念:
- InputProvider:输入提供者,声明本设备的输入能力
- InputConsumer:输入消费者,接收并处理远程输入事件
- InputRouter:输入路由器,决定输入事件应该发送到哪个设备
- InputMapper:输入映射器,将输入事件转换为目标设备可识别的格式
输入事件类型
分布式输入支持多种输入事件类型:
1. 触摸事件:触摸屏的触摸、滑动、多点触控
2. 按键事件:物理按键、虚拟按键的按下和释放
3. 鼠标事件:鼠标移动、点击、滚轮
4. 手势事件:识别后的手势,如捏合、旋转
5. 传感器事件:加速度计、陀螺仪等传感器数据
输入事件转换
不同设备的输入能力不同,需要进行事件转换:
// 输入事件转换示例
interface InputTransform {
// 源事件类型
sourceType: InputEventType;
// 目标事件类型
targetType: InputEventType;
// 转换函数
transform: (event: InputEvent) => InputEvent;
}
// 示例:手机触摸转电视鼠标
const touchToMouse: InputTransform = {
sourceType: 'touch',
targetType: 'mouse',
transform: (touchEvent) => ({
type: 'mouse_move',
x: touchEvent.x * screenScale,
y: touchEvent.y * screenScale,
timestamp: touchEvent.timestamp
})
};
代码实战
示例一:基础分布式输入
这是最基础的分布式输入示例,将手机的触摸输入转发到电视。
// DistributedInput.ets
import inputConsumer from '@ohos.multimodalInput.inputConsumer';
import deviceManager from '@ohos.distributedDeviceManager';
import distributedInput from '@ohos.distributedInput';
export class DistributedInput {
private context: common.UIAbilityContext;
private deviceManager: deviceManager.DeviceManager | null = null;
// 输入提供者(本设备作为输入源)
private inputProvider: distributedInput.InputProvider | null = null;
// 输入消费者(本设备作为输入目标)
private inputConsumer: distributedInput.InputConsumer | null = null;
// 输入路由器
private inputRouter: distributedInput.InputRouter | null = null;
// 输入状态
private inputState: InputState = {
isProviding: false,
isConsuming: false,
targetDevice: '',
sourceDevice: ''
};
constructor(context: common.UIAbilityContext) {
this.context = context;
}
// 初始化
async initialize(): Promise<void> {
console.info('[DistributedInput] Initializing...');
try {
// 初始化设备管理器
this.deviceManager = deviceManager.createDeviceManager(
this.context.applicationInfo.name
);
// 初始化输入路由器
this.inputRouter = distributedInput.createInputRouter();
console.info('[DistributedInput] Initialized');
} catch (error) {
console.error('[DistributedInput] Init failed:', error);
}
}
// 作为输入提供者:注册本设备的输入能力
async registerAsInputProvider(): Promise<void> {
console.info('[DistributedInput] Registering as input provider');
try {
// 定义输入能力
const inputCapabilities: distributedInput.InputCapability[] = [
{
type: 'touch',
properties: {
maxTouchPoints: 10,
supportsGestures: true,
supportsPressure: true
}
},
{
type: 'key',
properties: {
keys: ['volume_up', 'volume_down', 'back', 'menu']
}
},
{
type: 'sensor',
properties: {
sensors: ['accelerometer', 'gyroscope', 'magnetometer']
}
}
];
// 创建输入提供者
this.inputProvider = await distributedInput.createInputProvider({
capabilities: inputCapabilities,
deviceId: 'local'
});
// 监听输入路由请求
this.inputProvider.on('routeRequest', (request: RouteRequest) => {
this.handleRouteRequest(request);
});
console.info('[DistributedInput] Registered as provider');
} catch (error) {
console.error('[DistributedInput] Register failed:', error);
}
}
// 作为输入消费者:接收远程输入
async registerAsInputConsumer(): Promise<void> {
console.info('[DistributedInput] Registering as input consumer');
try {
// 创建输入消费者
this.inputConsumer = await distributedInput.createInputConsumer();
// 设置输入事件监听
this.setupInputListeners();
console.info('[DistributedInput] Registered as consumer');
} catch (error) {
console.error('[DistributedInput] Register failed:', error);
}
}
// 设置输入事件监听
private setupInputListeners(): void {
if (!this.inputConsumer) return;
// 监听触摸事件
this.inputConsumer.on('touchEvent', (event: distributedInput.TouchEvent) => {
this.handleRemoteTouchEvent(event);
});
// 监听按键事件
this.inputConsumer.on('keyEvent', (event: distributedInput.KeyEvent) => {
this.handleRemoteKeyEvent(event);
});
// 监听鼠标事件
this.inputConsumer.on('mouseEvent', (event: distributedInput.MouseEvent) => {
this.handleRemoteMouseEvent(event);
});
// 监听手势事件
this.inputConsumer.on('gestureEvent', (event: distributedInput.GestureEvent) => {
this.handleRemoteGestureEvent(event);
});
// 监听传感器事件
this.inputConsumer.on('sensorEvent', (event: distributedInput.SensorEvent) => {
this.handleRemoteSensorEvent(event);
});
}
// 处理远程触摸事件
private handleRemoteTouchEvent(event: distributedInput.TouchEvent): void {
console.info('[DistributedInput] Remote touch event:', event.type);
// 坐标转换
const localEvent = this.transformTouchCoordinates(event);
// 派发到本地UI
this.dispatchTouchEvent(localEvent);
}
// 处理远程按键事件
private handleRemoteKeyEvent(event: distributedInput.KeyEvent): void {
console.info('[DistributedInput] Remote key event:', event.keyCode);
// 派发按键事件
this.dispatchKeyEvent(event);
}
// 处理远程鼠标事件
private handleRemoteMouseEvent(event: distributedInput.MouseEvent): void {
console.info('[DistributedInput] Remote mouse event:', event.action);
// 派发鼠标事件
this.dispatchMouseEvent(event);
}
// 处理远程手势事件
private handleRemoteGestureEvent(event: distributedInput.GestureEvent): void {
console.info('[DistributedInput] Remote gesture:', event.gestureType);
// 根据手势类型处理
switch (event.gestureType) {
case 'pinch':
this.handlePinchGesture(event);
break;
case 'rotate':
this.handleRotateGesture(event);
break;
case 'swipe':
this.handleSwipeGesture(event);
break;
}
}
// 处理远程传感器事件
private handleRemoteSensorEvent(event: distributedInput.SensorEvent): void {
// 传感器数据可用于控制,如用手机倾斜控制电视光标
if (event.sensorType === 'accelerometer') {
this.handleAccelerometerControl(event);
}
}
// 开始向目标设备提供输入
async startProvidingInput(targetDeviceId: string): Promise<boolean> {
console.info('[DistributedInput] Start providing to:', targetDeviceId);
if (this.inputState.isProviding) {
console.warn('[DistributedInput] Already providing');
return false;
}
try {
// 配置输入路由
const routeConfig: distributedInput.RouteConfig = {
targetDeviceId: targetDeviceId,
// 输入转换配置
transforms: [
{
sourceType: 'touch',
targetType: 'touch',
// 坐标映射
coordinateMap: {
scaleX: await this.getScreenScaleX(targetDeviceId),
scaleY: await this.getScreenScaleY(targetDeviceId)
}
}
],
// 输入过滤
filters: {
// 只转发特定类型的输入
eventTypes: ['touch', 'key'],
// 过滤特定区域(如虚拟按键区域)
excludeRegions: []
}
};
// 启动输入路由
await this.inputRouter?.startRouting(routeConfig);
// 监听本地输入事件
this.startLocalInputCapture();
this.inputState.isProviding = true;
this.inputState.targetDevice = targetDeviceId;
console.info('[DistributedInput] Started providing');
return true;
} catch (error) {
console.error('[DistributedInput] Start failed:', error);
return false;
}
}
// 开始捕获本地输入
private startLocalInputCapture(): void {
// 捕获触摸事件
inputConsumer.on('touch', (touchEvent: TouchEvent) => {
this.forwardInputEvent({
type: 'touch',
data: touchEvent,
timestamp: Date.now()
});
});
// 捕获按键事件
inputConsumer.on('key', (keyEvent: KeyEvent) => {
this.forwardInputEvent({
type: 'key',
data: keyEvent,
timestamp: Date.now()
});
});
}
// 转发输入事件
private async forwardInputEvent(event: distributedInput.InputEvent): Promise<void> {
if (!this.inputState.isProviding) return;
try {
await this.inputRouter?.sendEvent(event);
} catch (error) {
console.error('[DistributedInput] Forward failed:', error);
}
}
// 停止提供输入
async stopProvidingInput(): Promise<void> {
console.info('[DistributedInput] Stop providing');
if (!this.inputState.isProviding) return;
try {
// 停止输入路由
await this.inputRouter?.stopRouting();
// 停止本地输入捕获
inputConsumer.off('touch');
inputConsumer.off('key');
this.inputState.isProviding = false;
this.inputState.targetDevice = '';
console.info('[DistributedInput] Stopped providing');
} catch (error) {
console.error('[DistributedInput] Stop failed:', error);
}
}
// 坐标转换
private transformTouchCoordinates(event: distributedInput.TouchEvent): TouchEvent {
// 获取屏幕缩放比例
const scaleX = this.getScreenScaleX('local');
const scaleY = this.getScreenScaleY('local');
return {
...event,
x: event.x * scaleX,
y: event.y * scaleY,
touches: event.touches?.map(t => ({
...t,
x: t.x * scaleX,
y: t.y * scaleY
}))
};
}
// 事件派发方法
private dispatchTouchEvent(event: TouchEvent): void {
// 实际实现需要派发到UI
console.info('[DistributedInput] Dispatch touch:', event.x, event.y);
}
private dispatchKeyEvent(event: distributedInput.KeyEvent): void {
// 实际实现
}
private dispatchMouseEvent(event: distributedInput.MouseEvent): void {
// 实际实现
}
// 手势处理
private handlePinchGesture(event: distributedInput.GestureEvent): void {
// 缩放操作
const scale = event.scale;
console.info('[DistributedInput] Pinch scale:', scale);
}
private handleRotateGesture(event: distributedInput.GestureEvent): void {
// 旋转操作
const rotation = event.rotation;
console.info('[DistributedInput] Rotation:', rotation);
}
private handleSwipeGesture(event: distributedInput.GestureEvent): void {
// 滑动手势
const direction = event.direction;
console.info('[DistributedInput] Swipe direction:', direction);
}
// 加速度计控制
private handleAccelerometerControl(event: distributedInput.SensorEvent): void {
// 用手机倾斜控制光标
const { x, y, z } = event.data;
// 计算倾斜角度
const tiltX = Math.atan2(x, z) * 180 / Math.PI;
const tiltY = Math.atan2(y, z) * 180 / Math.PI;
// 转换为光标移动
const cursorDeltaX = tiltX * 5; // 灵敏度系数
const cursorDeltaY = tiltY * 5;
// 移动光标
this.moveCursor(cursorDeltaX, cursorDeltaY);
}
private moveCursor(deltaX: number, deltaY: number): void {
// 实际实现
}
// 处理路由请求
private handleRouteRequest(request: RouteRequest): void {
console.info('[DistributedInput] Route request from:', request.sourceDeviceId);
// 可以根据请求决定是否接受
// 实际实现可能需要用户确认
}
// 获取屏幕缩放比例
private async getScreenScaleX(deviceId: string): Promise<number> {
// 实际实现需要获取设备屏幕信息
return 1.0;
}
private async getScreenScaleY(deviceId: string): Promise<number> {
return 1.0;
}
// 释放资源
async release(): Promise<void> {
// 停止提供输入
if (this.inputState.isProviding) {
await this.stopProvidingInput();
}
// 释放输入提供者
if (this.inputProvider) {
await this.inputProvider.release();
this.inputProvider = null;
}
// 释放输入消费者
if (this.inputConsumer) {
await this.inputConsumer.release();
this.inputConsumer = null;
}
// 释放路由器
if (this.inputRouter) {
distributedInput.releaseInputRouter(this.inputRouter);
this.inputRouter = null;
}
// 释放设备管理器
if (this.deviceManager) {
deviceManager.releaseDeviceManager(this.deviceManager);
this.deviceManager = null;
}
}
}
// 接口定义
interface InputState {
isProviding: boolean;
isConsuming: boolean;
targetDevice: string;
sourceDevice: string;
}
interface RouteRequest {
sourceDeviceId: string;
capabilities: distributedInput.InputCapability[];
}
interface TouchEvent {
type: string;
x: number;
y: number;
timestamp: number;
touches?: TouchPoint[];
}
interface TouchPoint {
id: number;
x: number;
y: number;
}
interface KeyEvent {
keyCode: number;
action: string;
timestamp: number;
}
示例二:手机作为电视遥控器
将手机变成功能丰富的电视遥控器,支持触摸板、语音控制、手势控制等。
// RemoteController.ets
import distributedInput from '@ohos.distributedInput';
import speechRecognizer from '@ohos.ai.speechRecognizer';
export class RemoteController {
private inputRouter: distributedInput.InputRouter | null = null;
private targetDeviceId: string = '';
// 遥控器模式
private controllerMode: ControllerMode = 'touchpad';
// 触摸板状态
private touchpadState: TouchpadState = {
isTracking: false,
lastX: 0,
lastY: 0,
sensitivity: 1.0
};
// 初始化遥控器
async initialize(targetDeviceId: string): Promise<void> {
console.info('[RemoteController] Initializing for:', targetDeviceId);
this.targetDeviceId = targetDeviceId;
this.inputRouter = distributedInput.createInputRouter();
// 启动输入路由
await this.inputRouter.startRouting({
targetDeviceId: targetDeviceId,
transforms: this.getTransformsForMode(this.controllerMode)
});
}
// 切换遥控器模式
async switchMode(mode: ControllerMode): Promise<void> {
console.info('[RemoteController] Switching to mode:', mode);
this.controllerMode = mode;
// 更新输入转换
await this.inputRouter?.updateTransforms(this.getTransformsForMode(mode));
}
// 获取模式对应的输入转换
private getTransformsForMode(mode: ControllerMode): distributedInput.Transform[] {
switch (mode) {
case 'touchpad':
return [{
sourceType: 'touch',
targetType: 'mouse',
transform: this.touchToMouse.bind(this)
}];
case 'gesture':
return [{
sourceType: 'gesture',
targetType: 'command',
transform: this.gestureToCommand.bind(this)
}];
case 'motion':
return [{
sourceType: 'sensor',
targetType: 'mouse',
transform: this.motionToMouse.bind(this)
}];
default:
return [];
}
}
// 触摸板模式:触摸转鼠标
private async handleTouchpadInput(touchEvent: TouchEvent): Promise<void> {
switch (touchEvent.type) {
case 'down':
this.touchpadState.isTracking = true;
this.touchpadState.lastX = touchEvent.x;
this.touchpadState.lastY = touchEvent.y;
break;
case 'move':
if (this.touchpadState.isTracking) {
const deltaX = (touchEvent.x - this.touchpadState.lastX) *
this.touchpadState.sensitivity;
const deltaY = (touchEvent.y - this.touchpadState.lastY) *
this.touchpadState.sensitivity;
// 发送鼠标移动事件
await this.sendMouseMove(deltaX, deltaY);
this.touchpadState.lastX = touchEvent.x;
this.touchpadState.lastY = touchEvent.y;
}
break;
case 'up':
this.touchpadState.isTracking = false;
break;
}
}
// 单击事件
async sendClick(): Promise<void> {
await this.inputRouter?.sendEvent({
type: 'mouse',
data: {
action: 'click',
button: 'left'
}
});
}
// 双击事件
async sendDoubleClick(): Promise<void> {
await this.inputRouter?.sendEvent({
type: 'mouse',
data: {
action: 'double_click',
button: 'left'
}
});
}
// 右键点击
async sendRightClick(): Promise<void> {
await this.inputRouter?.sendEvent({
type: 'mouse',
data: {
action: 'click',
button: 'right'
}
});
}
// 滚轮事件
async sendScroll(delta: number): Promise<void> {
await this.inputRouter?.sendEvent({
type: 'mouse',
data: {
action: 'scroll',
delta: delta
}
});
}
// 发送方向键
async sendDirectionKey(direction: 'up' | 'down' | 'left' | 'right'): Promise<void> {
const keyCodeMap = {
'up': 19,
'down': 20,
'left': 21,
'right': 22
};
await this.inputRouter?.sendEvent({
type: 'key',
data: {
keyCode: keyCodeMap[direction],
action: 'down'
}
});
// 短暂后发送释放事件
setTimeout(async () => {
await this.inputRouter?.sendEvent({
type: 'key',
data: {
keyCode: keyCodeMap[direction],
action: 'up'
}
});
}, 50);
}
// 发送确认键
async sendConfirmKey(): Promise<void> {
await this.inputRouter?.sendEvent({
type: 'key',
data: {
keyCode: 66, // Enter键
action: 'down'
}
});
}
// 发送返回键
async sendBackKey(): Promise<void> {
await this.inputRouter?.sendEvent({
type: 'key',
data: {
keyCode: 4, // Back键
action: 'down'
}
});
}
// 发送音量控制
async sendVolumeControl(action: 'up' | 'down' | 'mute'): Promise<void> {
const keyCodeMap = {
'up': 24,
'down': 25,
'mute': 164
};
await this.inputRouter?.sendEvent({
type: 'key',
data: {
keyCode: keyCodeMap[action],
action: 'down'
}
});
}
// 语音控制
async startVoiceControl(): Promise<void> {
console.info('[RemoteController] Starting voice control');
// 创建语音识别器
const recognizer = speechRecognizer.createSpeechRecognizer();
// 开始监听
recognizer.on('result', async (result: speechRecognizer.SpeechRecognitionResult) => {
console.info('[RemoteController] Voice recognized:', result.text);
// 解析语音命令
const command = this.parseVoiceCommand(result.text);
if (command) {
await this.executeCommand(command);
}
});
await recognizer.startListening({
language: 'zh-CN',
continuous: true
});
}
// 解析语音命令
private parseVoiceCommand(text: string): VoiceCommand | null {
// 简单的命令解析
const commandMap: Record<string, VoiceCommand> = {
'打开': { type: 'open', action: 'app' },
'关闭': { type: 'close', action: 'app' },
'播放': { type: 'media', action: 'play' },
'暂停': { type: 'media', action: 'pause' },
'停止': { type: 'media', action: 'stop' },
'快进': { type: 'media', action: 'forward' },
'快退': { type: 'media', action: 'rewind' },
'上一个': { type: 'media', action: 'previous' },
'下一个': { type: 'media', action: 'next' },
'音量加大': { type: 'volume', action: 'up' },
'音量减小': { type: 'volume', action: 'down' },
'静音': { type: 'volume', action: 'mute' }
};
for (const [keyword, command] of Object.entries(commandMap)) {
if (text.includes(keyword)) {
return command;
}
}
return null;
}
// 执行命令
private async executeCommand(command: VoiceCommand): Promise<void> {
console.info('[RemoteController] Executing command:', command);
switch (command.type) {
case 'media':
await this.executeMediaCommand(command.action);
break;
case 'volume':
await this.executeVolumeCommand(command.action);
break;
case 'open':
case 'close':
await this.executeAppCommand(command.type, command.action);
break;
}
}
// 执行媒体命令
private async executeMediaCommand(action: string): Promise<void> {
const keyCodeMap: Record<string, number> = {
'play': 85,
'pause': 85,
'stop': 86,
'forward': 90,
'rewind': 89,
'previous': 88,
'next': 87
};
const keyCode = keyCodeMap[action];
if (keyCode) {
await this.inputRouter?.sendEvent({
type: 'key',
data: { keyCode, action: 'down' }
});
}
}
// 执行音量命令
private async executeVolumeCommand(action: string): Promise<void> {
await this.sendVolumeControl(action as any);
}
// 执行应用命令
private async executeAppCommand(type: string, action: string): Promise<void> {
// 实际实现需要应用启动/关闭逻辑
}
// 辅助方法
private touchToMouse(touch: any): any {
return {
type: 'mouse_move',
x: touch.x,
y: touch.y
};
}
private gestureToCommand(gesture: any): any {
return {
type: 'command',
gesture: gesture.type
};
}
private motionToMouse(sensor: any): any {
return {
type: 'mouse_move',
deltaX: sensor.x * 10,
deltaY: sensor.y * 10
};
}
private async sendMouseMove(deltaX: number, deltaY: number): Promise<void> {
await this.inputRouter?.sendEvent({
type: 'mouse',
data: {
action: 'move',
deltaX: deltaX,
deltaY: deltaY
}
});
}
}
// 接口定义
type ControllerMode = 'touchpad' | 'gesture' | 'motion' | 'voice';
interface TouchpadState {
isTracking: boolean;
lastX: number;
lastY: number;
sensitivity: number;
}
interface VoiceCommand {
type: string;
action: string;
}
示例三:多输入源聚合
支持多个设备同时作为输入源,聚合输入事件。
// MultiInputAggregator.ets
import distributedInput from '@ohos.distributedInput';
export class MultiInputAggregator {
// 输入源管理
private inputSources: Map<string, InputSource> = new Map();
// 输入事件聚合器
private eventAggregator: InputEventAggregator | null = null;
// 冲突解决策略
private conflictResolver: ConflictResolver | null = null;
// 注册输入源
async registerInputSource(sourceId: string, config: InputSourceConfig): Promise<void> {
console.info('[MultiInput] Registering source:', sourceId);
// 创建输入消费者
const consumer = await distributedInput.createInputConsumer();
// 保存输入源信息
this.inputSources.set(sourceId, {
id: sourceId,
config: config,
consumer: consumer,
priority: config.priority || 0,
isActive: true
});
// 设置输入监听
this.setupSourceListener(sourceId, consumer);
}
// 设置输入源监听
private setupSourceListener(
sourceId: string,
consumer: distributedInput.InputConsumer
): void {
consumer.on('touchEvent', (event: distributedInput.TouchEvent) => {
this.handleInputEvent(sourceId, 'touch', event);
});
consumer.on('keyEvent', (event: distributedInput.KeyEvent) => {
this.handleInputEvent(sourceId, 'key', event);
});
consumer.on('mouseEvent', (event: distributedInput.MouseEvent) => {
this.handleInputEvent(sourceId, 'mouse', event);
});
}
// 处理输入事件
private handleInputEvent(
sourceId: string,
eventType: string,
event: any
): void {
// 获取输入源信息
const source = this.inputSources.get(sourceId);
if (!source || !source.isActive) return;
// 包装事件
const wrappedEvent: WrappedInputEvent = {
sourceId: sourceId,
eventType: eventType,
event: event,
timestamp: Date.now(),
priority: source.priority
};
// 聚合事件
this.eventAggregator?.aggregate(wrappedEvent);
}
// 设置输入源优先级
setSourcePriority(sourceId: string, priority: number): void {
const source = this.inputSources.get(sourceId);
if (source) {
source.priority = priority;
}
}
// 激活/停用输入源
setSourceActive(sourceId: string, active: boolean): void {
const source = this.inputSources.get(sourceId);
if (source) {
source.isActive = active;
}
}
// 移除输入源
async removeInputSource(sourceId: string): Promise<void> {
const source = this.inputSources.get(sourceId);
if (source) {
await source.consumer.release();
this.inputSources.delete(sourceId);
}
}
}
// 输入事件聚合器
class InputEventAggregator {
// 事件队列
private eventQueue: WrappedInputEvent[] = [];
// 合并窗口(毫秒)
private mergeWindow: number = 16;
// 聚合事件
aggregate(event: WrappedInputEvent): void {
this.eventQueue.push(event);
// 检查是否可以合并
this.tryMerge();
}
// 尝试合并事件
private tryMerge(): void {
if (this.eventQueue.length < 2) return;
const now = Date.now();
// 找出可以合并的事件组
const mergeGroups: WrappedInputEvent[][] = [];
let currentGroup: WrappedInputEvent[] = [];
for (const event of this.eventQueue) {
if (currentGroup.length === 0) {
currentGroup.push(event);
} else {
const lastEvent = currentGroup[currentGroup.length - 1];
const timeDiff = event.timestamp - lastEvent.timestamp;
// 同一输入源、同类型、时间间隔小于合并窗口的事件可以合并
if (event.sourceId === lastEvent.sourceId &&
event.eventType === lastEvent.eventType &&
timeDiff < this.mergeWindow) {
currentGroup.push(event);
} else {
mergeGroups.push(currentGroup);
currentGroup = [event];
}
}
}
if (currentGroup.length > 0) {
mergeGroups.push(currentGroup);
}
// 处理合并后的事件
for (const group of mergeGroups) {
if (group.length === 1) {
this.dispatchEvent(group[0]);
} else {
const merged = this.mergeEvents(group);
this.dispatchEvent(merged);
}
}
// 清空队列
this.eventQueue = [];
}
// 合并事件
private mergeEvents(events: WrappedInputEvent[]): WrappedInputEvent {
// 取最后一个事件作为基础
const base = events[events.length - 1];
// 根据事件类型进行特定合并
switch (base.eventType) {
case 'touch':
// 触摸事件:保留最后一个位置
return base;
case 'mouse':
// 鼠标事件:累加移动距离
let totalDeltaX = 0;
let totalDeltaY = 0;
for (const e of events) {
totalDeltaX += e.event.deltaX || 0;
totalDeltaY += e.event.deltaY || 0;
}
return {
...base,
event: {
...base.event,
deltaX: totalDeltaX,
deltaY: totalDeltaY
}
};
default:
return base;
}
}
// 派发事件
private dispatchEvent(event: WrappedInputEvent): void {
// 实际实现需要派发到应用
console.info('[Aggregator] Dispatch event:', event.eventType, 'from:', event.sourceId);
}
}
// 冲突解决器
class ConflictResolver {
// 解决输入冲突
resolve(events: WrappedInputEvent[]): WrappedInputEvent | null {
if (events.length === 0) return null;
if (events.length === 1) return events[0];
// 按优先级排序
const sorted = events.sort((a, b) => b.priority - a.priority);
// 返回最高优先级的事件
return sorted[0];
}
}
// 接口定义
interface InputSource {
id: string;
config: InputSourceConfig;
consumer: distributedInput.InputConsumer;
priority: number;
isActive: boolean;
}
interface InputSourceConfig {
priority?: number;
capabilities?: string[];
}
interface WrappedInputEvent {
sourceId: string;
eventType: string;
event: any;
timestamp: number;
priority: number;
}
踰坑与注意事项
坑一:坐标转换精度问题
问题描述:跨设备坐标转换时,由于分辨率差异,可能出现精度丢失或偏移。
解决方案:使用浮点坐标和校准机制
// 精确坐标转换
class PreciseCoordinateTransformer {
private calibration: Map<string, CalibrationData> = new Map();
// 执行校准
async calibrate(sourceDevice: string, targetDevice: string): Promise<void> {
// 显示校准点,让用户点击
const calibrationPoints = [
{ x: 0.1, y: 0.1 },
{ x: 0.9, y: 0.1 },
{ x: 0.5, y: 0.5 },
{ x: 0.1, y: 0.9 },
{ x: 0.9, y: 0.9 }
];
const measuredPoints: Point[] = [];
for (const point of calibrationPoints) {
// 在源设备显示点,获取用户点击位置
const measured = await this.measureCalibrationPoint(point);
measuredPoints.push(measured);
}
// 计算转换矩阵
const transform = this.calculateTransform(calibrationPoints, measuredPoints);
this.calibration.set(`${sourceDevice}_${targetDevice}`, {
transform: transform
});
}
// 转换坐标
transform(sourceDevice: string, targetDevice: string, point: Point): Point {
const key = `${sourceDevice}_${targetDevice}`;
const calibration = this.calibration.get(key);
if (!calibration) {
// 没有校准数据,使用简单缩放
return point;
}
// 应用转换矩阵
return this.applyTransform(point, calibration.transform);
}
}
坑二:输入延迟影响体验
问题描述:跨设备输入存在网络延迟,导致操作响应不及时。
解决方案:预测和插值
// 输入预测器
class InputPredictor {
private history: InputEvent[] = [];
private maxHistory = 10;
// 记录输入
record(event: InputEvent): void {
this.history.push(event);
if (this.history.length > this.maxHistory) {
this.history.shift();
}
}
// 预测下一个输入位置
predict(): Point | null {
if (this.history.length < 2) return null;
// 使用最近两个事件计算速度
const recent = this.history.slice(-2);
const dt = recent[1].timestamp - recent[0].timestamp;
if (dt === 0) return null;
const vx = (recent[1].x - recent[0].x) / dt;
const vy = (recent[1].y - recent[0].y) / dt;
// 预测延迟时间后的位置
const latency = 50; // 假设50ms延迟
return {
x: recent[1].x + vx * latency,
y: recent[1].y + vy * latency
};
}
}
坑三:多点触控同步问题
问题描述:多点触控时,不同触点的到达时间不一致,导致手势识别错误。
解决方案:触点分组和同步
// 多点触控同步器
class MultiTouchSynchronizer {
private touchGroups: Map<number, TouchPoint[]> = new Map();
private syncWindow: number = 10; // 10ms同步窗口
// 添加触点
addTouchPoint(point: TouchPoint): void {
// 找到或创建触点组
let group = this.findOrCreateGroup(point);
group.push(point);
// 检查是否可以派发
this.checkAndDispatch(group);
}
// 查找或创建触点组
private findOrCreateGroup(point: TouchPoint): TouchPoint[] {
// 查找时间窗口内的现有组
for (const [id, group] of this.touchGroups) {
const lastTime = group[group.length - 1].timestamp;
if (Math.abs(point.timestamp - lastTime) < this.syncWindow) {
return group;
}
}
// 创建新组
const newGroup: TouchPoint[] = [];
this.touchGroups.set(point.id, newGroup);
return newGroup;
}
// 检查并派发
private checkAndDispatch(group: TouchPoint[]): void {
// 当所有触点都到达时派发
// 实际实现需要更复杂的逻辑
}
}
HarmonyOS 6适配
新增输入事件录制与回放
// 录制输入序列
const recorder = distributedInput.createInputRecorder();
await recorder.startRecording();
// ... 用户操作 ...
const recordedSequence = await recorder.stopRecording();
// 回放输入序列
await distributedInput.playback(recordedSequence, {
speed: 1.0,
repeat: false
});
增强的输入分析
// 输入意图识别
const analyzer = distributedInput.createInputAnalyzer();
analyzer.on('intent', (intent: InputIntent) => {
console.info('Detected intent:', intent.type);
switch (intent.type) {
case 'scroll':
// 用户想滚动
break;
case 'zoom':
// 用户想缩放
break;
case 'select':
// 用户想选择
break;
}
});
await analyzer.start();
输入设备虚拟化
// 创建虚拟输入设备
const virtualKeyboard = await distributedInput.createVirtualDevice({
type: 'keyboard',
layout: 'qwerty',
// 自定义按键映射
keyMap: {
'a': { keyCode: 29, label: 'A' },
'b': { keyCode: 30, label: 'B' },
// ...
}
});
// 发送虚拟按键
await virtualKeyboard.sendKey('a');
总结
分布式输入让输入设备突破了物理限制,一个设备的输入可以控制另一个设备的应用。无论是手机变遥控器、平板变触摸板,还是多设备协同输入,都为用户提供了更灵活、更强大的交互方式。
核心要点:
- 输入提供者与消费者:设备可以作为输入源或输入目标
- 输入事件转换:不同设备间的输入事件需要转换和映射
- 多输入源聚合:支持多个设备同时作为输入源
- 输入同步与冲突解决:处理延迟和冲突问题
最佳实践:
- 实现精确的坐标转换和校准机制
- 使用预测和插值减少延迟影响
- 多点触控需要同步处理
- 根据场景选择合适的输入模式
- 善用语音、手势等高级输入方式
分布式输入是HarmonyOS分布式能力的重要组成,下一篇我们将探讨分布式传感器,看看如何实现跨设备的传感器共享。
- 点赞
- 收藏
- 关注作者
评论(0)