[Gesture Event]
TouchInputMapper에서는 단순히 finger 터치 외에도 stylus, gesture 등을 처리합니다.
그 중 이번에는 Gesture event에 대해 알아보려 합니다.
Gesture
Gesture를 가공하는 가장 중요한 메소드는 preparePointerGestures 입니다.
preparePointerGestures만 알면 Gesture가 어떻게 가공되는지 알 수 있습니다.
sstruct PointerGesture {
enum class Mode {
NEUTRAL, // 0
TAP, // 1
TAP_DRAG, // 2
BUTTON_CLICK_OR_DRAG, // 3
HOVER, // 4
PRESS, // 5
SWIPE, // 6
FREEFORM, // 7
QUIET, // 8
};
cookAndDispatch
앞서 CookAndDispatch에서 POINTER일경우 PointerUsage판단해 넘겨준다.
void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) {
// Dispatch the touches either directly or by translation through a pointer on screen.
if (mDeviceMode == DeviceMode::POINTER) {
if (!mCurrentCookedState.stylusIdBits.isEmpty()) {
} else if (!mCurrentCookedState.fingerIdBits.isEmpty() ||
isPointerDown(mCurrentRawState.buttonState)) {
pointerUsage = PointerUsage::GESTURES;
}
dispatchPointerUsage(when, readTime, policyFlags, pointerUsage);
dispatchPointerUsage
받은 pointerUsage에 따라 dispatchPointer로 전달.
void TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, PointerUsage pointerUsage) {
if (pointerUsage != mPointerUsage) {
abortPointerUsage(when, readTime, policyFlags);
mPointerUsage = pointerUsage;
}
switch (mPointerUsage) {
case PointerUsage::GESTURES:
dispatchPointerGestures(when, readTime, policyFlags, false /*isTimeout*/);
break;
case PointerUsage::STYLUS:
dispatchPointerStylus(when, readTime, policyFlags);
break;
case PointerUsage::MOUSE:
dispatchPointerMouse(when, readTime, policyFlags);
break;
case PointerUsage::NONE:
break;
}
}
dispatchPointerGestures
preparePointerGestures에서 성공적으로 받으면, 동작을 진행하고 아니면 끝낸다.
preparePointerGestures에서 cancelPreviousGesture, finishPreviousGesture 또한 동작하는데 cancel일 경우 돌아와서 cancel을 보내고, finish일 경우에만 dispatchMotion으로 값을 보낸다.
Gesture 주요동작은 preparePointerGesture에서 결정된다고 보면 된다.
void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags, bool isTimeout) {
// Update current gesture coordinates.
bool cancelPreviousGesture, finishPreviousGesture;
bool sendEvents = preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
if (!sendEvents) {
return;
}
if (finishPreviousGesture) {
cancelPreviousGesture = false;
}
// Update the pointer presentation and spots.
if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH) {
mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
if (finishPreviousGesture || cancelPreviousGesture) {
mPointerController->clearSpots();
}
if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
setTouchSpots(mPointerGesture.currentGestureCoords,
mPointerGesture.currentGestureIdToIndex,
mPointerGesture.currentGestureIdBits, mPointerController->getDisplayId());
}
} else {
mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
}
// Show or hide the pointer if needed.
// PointerController->hide하는 부분 (생략)
// cancelPreviousGesture이었다면 모든 포인터에 CANCEL 보냄.
BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
if (!dispatchedGestureIdBits.isEmpty()) {
if (cancelPreviousGesture) {
dispatchMotion(when, readTime, policyFlags, mSource,AMOTION_EVENT_ACTION_CANCEL, 0,flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,mPointerGesture.downTime);
dispatchedGestureIdBits.clear();
} else {
BitSet32 upGestureIdBits;
// finishPreviousGesture었다면 dispatchMotion으로 값 전달.
if (finishPreviousGesture) {
upGestureIdBits = dispatchedGestureIdBits;
} else {
upGestureIdBits.value =
dispatchedGestureIdBits.value & ~mPointerGesture.currentGestureIdBits.value;
}
while (!upGestureIdBits.isEmpty()) {
uint32_t id = upGestureIdBits.clearFirstMarkedBit();
dispatchMotion(when, readTime, policyFlags, mSource,AMOTION_EVENT_ACTION_POINTER_UP, 0, flags,metaState,buttonState,AMOTION_EVENT_EDGE_FLAG_NONE,mPointerGesture.lastGestureProperties,mPointerGesture.lastGestureCoords,mPointerGesture.lastGestureIdToIndex,dispatchedGestureIdBits, id, 0,0, mPointerGesture.downTime);
dispatchedGestureIdBits.clearBit(id);
}
}
}
preparePointerGestures
먼저 Gesture Timeout에 관하여 handling을 한다.
이후 velocity update 해줌으로써 좌표계산을 하고 case문으로 각 Gesture에 맞게 가공을 합니다.
bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture,bool* outFinishPreviousGesture, bool isTimeout) {
*outCancelPreviousGesture = false;
*outFinishPreviousGesture = false;
// Gesture가 timeout일 경우 TAP 조건 외에 모두 false.
if (isTimeout) {
if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) {
if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
// The tap/drag timeout has not yet expired.
getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime +
mConfig.pointerGestureTapDragInterval);
} else {
// The tap is finished.
*outFinishPreviousGesture = true;
mPointerGesture.activeGestureId = -1;
mPointerGesture.currentGestureMode = PointerGesture::Mode::NEUTRAL;
mPointerGesture.currentGestureIdBits.clear();
mPointerVelocityControl.reset();
return true;
}
}
// We did not handle this timeout.
return false;
}
const uint32_t currentFingerCount = mCurrentCookedState.fingerIdBits.count();
const uint32_t lastFingerCount = mLastCookedState.fingerIdBits.count();
// Update the velocity tracker.
{
std::vector<VelocityTracker::Position> positions;
for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
const RawPointerData::Pointer& pointer =
mCurrentRawState.rawPointerData.pointerForId(id);
float x = pointer.x * mPointerXMovementScale;
float y = pointer.y * mPointerYMovementScale;
positions.push_back({x, y});
}
mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits,
positions);
1. mCurrentRawState.rawPointerData.pointerForId(id)에 좌표를 가져옴.
2. mPointerXMovementScale , mPointerYMovementScale 곱해줘서 좌표 정의
3. VelocityTracker x,y 집어넣음
4. velocityTracker.addMovement으로 전달
/*
gesture 가공하기전에 isQuietTime으로 제스쳐가 유효한건지 선 체크
Press, SWIPE, FREEFORM 경우 fingerCount <2 이하면 해당 제스쳐가 아니므로 isQuietTime = true;
그외 제스쳐가 fingerCount >2 이면 해당 제스쳐가 아니므로 isQuietTime = true;
*/
bool isQuietTime = false;
if (activeTouchId < 0) {
mPointerGesture.resetQuietTime();
} else {
isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval;
if (!isQuietTime) {
if ((mPointerGesture.lastGestureMode == PointerGesture::Mode::PRESS ||
mPointerGesture.lastGestureMode == PointerGesture::Mode::SWIPE ||
mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) &&
currentFingerCount < 2) {
// Enter quiet time when exiting swipe or freeform state.
// This is to prevent accidentally entering the hover state and flinging the
// pointer when finishing a swipe and there is still one pointer left onscreen.
isQuietTime = true;
} else if (mPointerGesture.lastGestureMode ==
PointerGesture::Mode::BUTTON_CLICK_OR_DRAG &&
currentFingerCount >= 2 && !isPointerDown(mCurrentRawState.buttonState)) {
// Enter quiet time when releasing the button and there are still two or more
// fingers down. This may indicate that one finger was used to press the button
// but it has not gone up yet.
isQuietTime = true;
}
if (isQuietTime) {
mPointerGesture.quietTime = when;
}
}
}
// 각 Case에 맞게 Gesture 가공
if (isTimeout) {
// Case 1: Quiet time. (QUIET)
else if (isPointerDown(mCurrentRawState.buttonState)) {
// Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG)
else if (currentFingerCount == 0) {
// Case 3. No fingers down and button is not pressed. (NEUTRAL)
else if (currentFingerCount == 1) {
// Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG)
else
// Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
mPointerController->setButtonState(mCurrentRawState.buttonState);
return true;