Android 触摸事件转换为鼠标事件

        触摸事件常见类型为ACTION_DOWN/ACTION_UP/ACTION_MOVE等。而鼠标在触摸事件类型的基础上又多出ACTION_HOVER_MOVE等类型,同时鼠标左右按键在输入事件中的也对应有BUTTON_PRIMARY/BUTTON_SECONDARY等状态,鼠标的滚轮操作也对应着AXIS_VSCROLL的值。所以若在手机中把显示界面作为鼠标触摸板,把触摸事件转换为鼠标事件,则需要对触摸事件做多个判断,针对不同触摸手势做不同的处理。

        Android系统对鼠标事件从驱动到framework的读取实在frameworks/native/services/inputflinger/reader/mapper/CursorInputMapper.cpp进行的,CursorInputMapper::sync(nsecs_t when)函数对鼠标事件做了所有的读取转换,而以下在java中实现触摸事件转换为鼠标事件的方法也大都是参考了其中的逻辑。

1.轻触点击对应鼠标的左键事件

        手指轻触点击对应着click事件,所以当出现触摸短按点击事件后可以直接发送一个鼠标的左键按键事件。
        鼠标左键按键事件会依次发出4个MotionEvent,其类型分别为ACTION_DOWN ACTION_BUTTON_PRESS ACTION_BUTTON_RELEASE ACTION_UP,同时其MotionEvent的ButtonState属性也要变化,依次为MotionEvent.BUTTON_PRIMARY MotionEvent.BUTTON_PRIMARY 0 0。实现代码如下:

    private void sendLeftButton(MotionEvent lastMotionEvent){

        int buttonState = MotionEvent.BUTTON_PRIMARY;
        int buttonDownTime = SystemClock.uptimeMillis();

        MotionEvent.PointerProperties pp1 = new MotionEvent.PointerProperties();
        lastMotionEvent.getPointerProperties(0, pp1);
        MotionEvent.PointerCoords pc1 = new MotionEvent.PointerCoords();
        lastMotionEvent.getPointerCoords(0, pc1);

        properties[0] = pp1;
        pointerCoords[0] = pc1;
        //(1) down
        MotionEvent downMotionEvent = MotionEvent.obtain(buttonDownTime, buttonDownTime
                , MotionEvent.ACTION_DOWN, lastMotionEvent.getPointerCount(), properties, pointerCoords
                , lastMotionEvent.getMetaState(), buttonState
                , lastMotionEvent.getXPrecision(), lastMotionEvent.getYPrecision()
                , lastMotionEvent.getDeviceId(), lastMotionEvent.getEdgeFlags()
                , lastMotionEvent.getSource(), lastMotionEvent.getDisplayId(), lastMotionEvent.getFlags());

        //sendMotionEvent;
        //(2) press
        MotionEvent pressedMotionEvent = MotionEvent.obtain(buttonDownTime, buttonDownTime
                , MotionEvent.ACTION_BUTTON_PRESS, lastMotionEvent.getPointerCount(), properties, pointerCoords
                , lastMotionEvent.getMetaState(), buttonState
                , lastMotionEvent.getXPrecision(), lastMotionEvent.getYPrecision()
                , lastMotionEvent.getDeviceId(), lastMotionEvent.getEdgeFlags()
                , lastMotionEvent.getSource(), lastMotionEvent.getDisplayId(), lastMotionEvent.getFlags());
        //sendMotionEvent;

        //(3) release
        buttonState = 0;
        MotionEvent releaseMotionEvent = MotionEvent.obtain(buttonDownTime, SystemClock.uptimeMillis()
                , MotionEvent.ACTION_BUTTON_RELEASE, lastMotionEvent.getPointerCount(), properties, pointerCoords
                , lastMotionEvent.getMetaState(), buttonState
                , lastMotionEvent.getXPrecision(), lastMotionEvent.getYPrecision()
                , lastMotionEvent.getDeviceId(), lastMotionEvent.getEdgeFlags()
                , lastMotionEvent.getSource(), lastMotionEvent.getDisplayId(), lastMotionEvent.getFlags());
        //sendMotionEvent;

        //(4) up
        MotionEvent upMotionEvent = MotionEvent.obtain(buttonDownTime, SystemClock.uptimeMillis()
                , MotionEvent.ACTION_UP, lastMotionEvent.getPointerCount(), properties, pointerCoords
                , lastMotionEvent.getMetaState(), buttonState
                , lastMotionEvent.getXPrecision(), lastMotionEvent.getYPrecision()
                , lastMotionEvent.getDeviceId(), lastMotionEvent.getEdgeFlags()
                , lastMotionEvent.getSource(), lastMotionEvent.getDisplayId(), lastMotionEvent.getFlags());
        //sendMotionEvent;
    }

2.正常滑动对应鼠标的移动事件

        手指在显示的界面滑动时,会一直有ACTION_MOVE事件,那么可以把此ACTION_MOVE事件(包括ACTION_DOWN/ACTION_UP事件)转换为ACTION_HOVER_MOVE事件。同时其鼠标位置的移动则需要修改MotionEvent.PointerProperties的值。实现代码如下:

    private MotionEvent.PointerCoords mTouchDownPointerCoords = null;
    private MotionEvent.PointerCoords mMouseDownPointerCoords = new MotionEvent.PointerCoords();

    private boolean handlerFingerTouchArea(MotionEvent ev,int pointerID){
        switch (ev.getActionMasked()){
            case MotionEvent.ACTION_DOWN:
                mTouchDownPointerCoords = new MotionEvent.PointerCoords();
                mTouchDownPointerCoords.x = ev.getX();
                mTouchDownPointerCoords.y = ev.getY();
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mTouchDownPointerCoords = null;
                break;
        }

        float moveX = 0;
        float moveY = 0;
        if(mTouchDownPointerCoords != null){
            moveX = ev.getX() - mTouchDownPointerCoords.x;
            moveY = ev.getY() - mTouchDownPointerCoords.y;
            mTouchDownPointerCoords.x = ev.getX();
            mTouchDownPointerCoords.y = ev.getY();
        }

        //get (X,Y)
        MotionEvent.PointerProperties pp1 = new MotionEvent.PointerProperties();
        pp1.id = 0;
        pp1.toolType = MotionEvent.TOOL_TYPE_MOUSE;
        properties[0] = pp1;

        MotionEvent.PointerCoords pc1 = new MotionEvent.PointerCoords();
        pc1.x = Math.min(mMouseDownPointerCoords.x + moveX, 0);
        pc1.y = Math.min(mMouseDownPointerCoords.y + moveY, 0);
        mMouseDownPointerCoords.x = pc1.x;
        mMouseDownPointerCoords.y = pc1.y;

        pc1.pressure = 1;
        pc1.size = 1;
        pointerCoords[0] = pc1;

        // init Motion Event
        long eventTime = SystemClock.uptimeMillis();
        int action = MotionEvent.ACTION_HOVER_MOVE;

        MotionEvent customEvent = MotionEvent.obtain(0, eventTime,
                action, 1, properties, pointerCoords
                , 0, 0, 1, 1
                , 7, 0, InputDevice.SOURCE_MOUSE, displayID, 0);
        lastMotionEvent = MotionEvent.obtain(customEvent);
        //sendMotionEvent;

        return true;
    }

3.鼠标移动且鼠标按住左键时

        鼠标移动加上鼠标按键实现很简单,就是在鼠标移动事件的过程中加入鼠标的左键事件,同时在鼠标左键未松开时所有的MotionEvent的ButtonState属性要一直为MotionEvent.BUTTON_PRIMARY。   

4.鼠标的滚轮事件

        鼠标的滚轮事件比较单一,就是修改MotionEvent.PointerProperties的AXIS_VSCROLL的值,其值为滚轮滚动的距离。实现代码如下:

    private boolean handlerMouseWheel(MotionEvent ev){
        MotionEvent clickMotionEvent = null;
        MotionEvent.PointerProperties pp1 = new MotionEvent.PointerProperties();
        MotionEvent.PointerCoords pc1 = new MotionEvent.PointerCoords();
        switch (ev.getActionMasked()){
            case MotionEvent.ACTION_DOWN:
                mVScrollDownY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float vscroll = ev.getY() - mVScrollDownY;
                if(Math.abs(vscroll) > 20){
                    mVScrollDownY = ev.getY();
                    ev.getPointerProperties(0, pp1);
                    ev.getPointerCoords(0, pc1);
                    pc1.setAxisValue(MotionEvent.AXIS_VSCROLL,vscroll > 0 ? 1 : -1);

                    properties[0] = pp1;
                    pointerCoords[0] = pc1;
                    clickMotionEvent = MotionEvent.obtain(buttonDownTime, SystemClock.uptimeMillis()
                            , MotionEvent.ACTION_SCROLL, ev.getPointerCount(), properties, pointerCoords
                            , ev.getMetaState(), 0
                            , ev.getXPrecision(), ev.getYPrecision()
                            , ev.getDeviceId(), ev.getEdgeFlags()
                            , ev.getSource(), ev.getDisplayId(), ev.getFlags());
                }
                break;
        }
        if(clickMotionEvent != null){
            //sendMotionEvent;
        }
        return true;
    }

以下附上CursorInputMapper::sync(nsecs_t when)函数的Android 11平台的源码,对照的话还是要以此为主:

void CursorInputMapper::sync(nsecs_t when) {
    int32_t lastButtonState = mButtonState;
    int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
    mButtonState = currentButtonState;

    bool wasDown = isPointerDown(lastButtonState);
    bool down = isPointerDown(currentButtonState);
    bool downChanged;
    if (!wasDown && down) {
        mDownTime = when;
        downChanged = true;
    } else if (wasDown && !down) {
        downChanged = true;
    } else {
        downChanged = false;
    }
    nsecs_t downTime = mDownTime;
    bool buttonsChanged = currentButtonState != lastButtonState;
    int32_t buttonsPressed = currentButtonState & ~lastButtonState;
    int32_t buttonsReleased = lastButtonState & ~currentButtonState;

    float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale;
    float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
    bool moved = deltaX != 0 || deltaY != 0;

    // Rotate delta according to orientation if needed.
    if (mParameters.orientationAware && mParameters.hasAssociatedDisplay &&
        (deltaX != 0.0f || deltaY != 0.0f)) {
        rotateDelta(mOrientation, &deltaX, &deltaY);
    }

    // Move the pointer.
    PointerProperties pointerProperties;
    pointerProperties.clear();
    pointerProperties.id = 0;
    pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE;

    PointerCoords pointerCoords;
    pointerCoords.clear();

    float vscroll = mCursorScrollAccumulator.getRelativeVWheel();
    float hscroll = mCursorScrollAccumulator.getRelativeHWheel();
    bool scrolled = vscroll != 0 || hscroll != 0;

    mWheelYVelocityControl.move(when, nullptr, &vscroll);
    mWheelXVelocityControl.move(when, &hscroll, nullptr);

    mPointerVelocityControl.move(when, &deltaX, &deltaY);

    int32_t displayId;
    float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
    float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
    if (mSource == AINPUT_SOURCE_MOUSE) {
        if (moved || scrolled || buttonsChanged) {
            mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);

            if (moved) {
                mPointerController->move(deltaX, deltaY);
            }

            if (buttonsChanged) {
                mPointerController->setButtonState(currentButtonState);
            }

            mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
        }

        mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
        displayId = mPointerController->getDisplayId();
    } else {
        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX);
        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY);
        displayId = ADISPLAY_ID_NONE;
    }

    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);

    // Moving an external trackball or mouse should wake the device.
    // We don't do this for internal cursor devices to prevent them from waking up
    // the device in your pocket.
    // TODO: Use the input device configuration to control this behavior more finely.
    uint32_t policyFlags = 0;
    if ((buttonsPressed || moved || scrolled) && getDeviceContext().isExternal()) {
        policyFlags |= POLICY_FLAG_WAKE;
    }

    // Synthesize key down from buttons if needed.
    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
                         displayId, policyFlags, lastButtonState, currentButtonState);

    // Send motion event.
    if (downChanged || moved || scrolled || buttonsChanged) {
        int32_t metaState = getContext()->getGlobalMetaState();
        int32_t buttonState = lastButtonState;
        int32_t motionEventAction;
        if (downChanged) {
            motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
        } else if (down || (mSource != AINPUT_SOURCE_MOUSE)) {
            motionEventAction = AMOTION_EVENT_ACTION_MOVE;
        } else {
            motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE;
        }

        if (buttonsReleased) {
            BitSet32 released(buttonsReleased);
            while (!released.isEmpty()) {
                int32_t actionButton = BitSet32::valueForBit(released.clearFirstMarkedBit());
                buttonState &= ~actionButton;
                NotifyMotionArgs releaseArgs(getContext()->getNextId(), when, getDeviceId(),
                                             mSource, displayId, policyFlags,
                                             AMOTION_EVENT_ACTION_BUTTON_RELEASE, actionButton, 0,
                                             metaState, buttonState, MotionClassification::NONE,
                                             AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                             &pointerCoords, mXPrecision, mYPrecision,
                                             xCursorPosition, yCursorPosition, downTime,
                                             /* videoFrames */ {});
                getListener()->notifyMotion(&releaseArgs);
            }
        }

        NotifyMotionArgs args(getContext()->getNextId(), when, getDeviceId(), mSource, displayId,
                              policyFlags, motionEventAction, 0, 0, metaState, currentButtonState,
                              MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
                              &pointerProperties, &pointerCoords, mXPrecision, mYPrecision,
                              xCursorPosition, yCursorPosition, downTime,
                              /* videoFrames */ {});
        getListener()->notifyMotion(&args);

        if (buttonsPressed) {
            BitSet32 pressed(buttonsPressed);
            while (!pressed.isEmpty()) {
                int32_t actionButton = BitSet32::valueForBit(pressed.clearFirstMarkedBit());
                buttonState |= actionButton;
                NotifyMotionArgs pressArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
                                           displayId, policyFlags,
                                           AMOTION_EVENT_ACTION_BUTTON_PRESS, actionButton, 0,
                                           metaState, buttonState, MotionClassification::NONE,
                                           AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                           &pointerCoords, mXPrecision, mYPrecision,
                                           xCursorPosition, yCursorPosition, downTime,
                                           /* videoFrames */ {});
                getListener()->notifyMotion(&pressArgs);
            }
        }

        ALOG_ASSERT(buttonState == currentButtonState);

        // Send hover move after UP to tell the application that the mouse is hovering now.
        if (motionEventAction == AMOTION_EVENT_ACTION_UP && (mSource == AINPUT_SOURCE_MOUSE)) {
            NotifyMotionArgs hoverArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
                                       displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
                                       0, metaState, currentButtonState, MotionClassification::NONE,
                                       AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                       &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
                                       yCursorPosition, downTime, /* videoFrames */ {});
            getListener()->notifyMotion(&hoverArgs);
        }

        // Send scroll events.
        if (scrolled) {
            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);

            NotifyMotionArgs scrollArgs(getContext()->getNextId(), when, getDeviceId(), mSource,
                                        displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
                                        metaState, currentButtonState, MotionClassification::NONE,
                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                        &pointerCoords, mXPrecision, mYPrecision, xCursorPosition,
                                        yCursorPosition, downTime, /* videoFrames */ {});
            getListener()->notifyMotion(&scrollArgs);
        }
    }

    // Synthesize key up from buttons if needed.
    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
                         displayId, policyFlags, lastButtonState, currentButtonState);

    mCursorMotionAccumulator.finishSync();
    mCursorScrollAccumulator.finishSync();
}

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android触摸事件分发是指在屏幕上发生触摸事件时,Android系统将该事件分发给适当的视图处理。触摸事件分发的过程涉及多个层级,包括Activity、ViewGroup和View。 当用户触摸屏幕时,Android系统首先将触摸事件发送给当前活动的Window。然后,Window将事件传递给顶级ViewGroup(通常是根布局),该ViewGroup负责协调子视图的事件处理。 在ViewGroup中,触摸事件会按照一定的规则进行分发。常见的分发方式有以下几种: 1. 捕获阶段(Capture Phase):从根布局向下遍历,让父级ViewGroup有机会拦截事件。可以通过重写`onInterceptTouchEvent()`方法来实现事件的拦截。 2. 目标阶段(Target Phase):如果没有被拦截,触摸事件将传递给目标View,即最终接收事件的视图。目标View将调用`onTouchEvent()`方法处理事件。 3. 冒泡阶段(Bubble Phase):如果目标View没有消耗事件事件将向上传递给父级ViewGroup,直到根布局。在这个阶段,可以通过返回值来控制是否继续向上传递。 除了上述的默认分发方式外,还可以通过重写`dispatchTouchEvent()`方法来自定义事件分发逻辑。通过调用`super.dispatchTouchEvent()`来保持默认行为,或者根据需求进行处理。 总结来说,Android触摸事件分发涉及捕获阶段、目标阶段和冒泡阶段,通过重写相关方法或自定义分发逻辑,可以实现对触摸事件的处理和控制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值