HarmonyOS APP开发跨设备输入

举报
Jack20 发表于 2026/06/19 23:48:37 2026/06/19
【摘要】 背景你有没有遇到过这种场景:用电视看视频,想快进一下,但电视遥控器操作太麻烦;或者在平板上展示PPT,想用手机当激光笔;又或者在电脑上玩游戏,想用手机当手柄。这就是分布式输入要解决的问题——让输入事件可以跨设备流动,一个设备的输入可以控制另一个设备的应用。传统方案下,输入设备是绑死在主机上的。键盘只能控制连接的电脑,触摸屏只能控制所在的设备。想要跨设备输入,要么用专门的远程控制软件,要么用...

背景

你有没有遇到过这种场景:用电视看视频,想快进一下,但电视遥控器操作太麻烦;或者在平板上展示PPT,想用手机当激光笔;又或者在电脑上玩游戏,想用手机当手柄。

这就是分布式输入要解决的问题——让输入事件可以跨设备流动,一个设备的输入可以控制另一个设备的应用。

传统方案下,输入设备是绑死在主机上的。键盘只能控制连接的电脑,触摸屏只能控制所在的设备。想要跨设备输入,要么用专门的远程控制软件,要么用蓝牙配对的外设,体验都不够原生。

HarmonyOS的分布式输入能力,让输入设备变成了"共享资源"。你的手机可以变成电视的遥控器,平板可以变成电脑的触摸板,手表可以变成手机的快捷按键。更重要的是,这一切对应用是透明的——应用不需要关心输入来自哪个设备,只需要响应输入事件。

核心原理

分布式输入架构

分布式输入基于以下几个核心概念:

  1. InputProvider:输入提供者,声明本设备的输入能力
  2. InputConsumer:输入消费者,接收并处理远程输入事件
  3. InputRouter:输入路由器,决定输入事件应该发送到哪个设备
  4. 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');

总结

分布式输入让输入设备突破了物理限制,一个设备的输入可以控制另一个设备的应用。无论是手机变遥控器、平板变触摸板,还是多设备协同输入,都为用户提供了更灵活、更强大的交互方式。

核心要点

  1. 输入提供者与消费者:设备可以作为输入源或输入目标
  2. 输入事件转换:不同设备间的输入事件需要转换和映射
  3. 多输入源聚合:支持多个设备同时作为输入源
  4. 输入同步与冲突解决:处理延迟和冲突问题

最佳实践

  • 实现精确的坐标转换和校准机制
  • 使用预测和插值减少延迟影响
  • 多点触控需要同步处理
  • 根据场景选择合适的输入模式
  • 善用语音、手势等高级输入方式

分布式输入是HarmonyOS分布式能力的重要组成,下一篇我们将探讨分布式传感器,看看如何实现跨设备的传感器共享。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。