触摸事件常见类型为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();
}