멀티터치 동작 처리

Compose 방법 사용해 보기
Jetpack Compose는 Android에 권장되는 UI 도구 키트입니다. Compose에서 터치 및 입력을 사용하는 방법을 알아보세요.

멀티 터치 동작은 여러 포인터 (손가락)가 동시에 화면을 탭하는 것입니다. 이 문서에서는 여러 포인터와 관련된 동작을 감지하는 방법을 설명합니다.

여러 포인터 추적하기

여러 포인터가 동시에 화면을 탭하면 시스템은 다음과 같은 터치 이벤트를 생성합니다.

  • ACTION_DOWN: 첫 번째 포인터가 화면을 탭할 때 전송됩니다. 이 이벤트가 동작을 시작합니다. 이 포인터의 포인터 데이터는 항상 MotionEvent의 색인 0에 있습니다.
  • ACTION_POINTER_DOWN: 첫 번째 포인터 이후 추가 포인터가 화면으로 들어올 때 전송됩니다. getActionIndex()를 사용하여 방금 다운된 포인터의 색인을 가져올 수 있습니다.
  • ACTION_MOVE: 여러 포인터가 포함된 동작에서 변경이 발생할 때 전송됩니다.
  • ACTION_POINTER_UP: 기본이 아닌 포인터가 위로 올라갈 때 전송됩니다. getActionIndex()를 사용하여 방금 위로 올라온 포인터의 색인을 가져올 수 있습니다.
  • ACTION_UP: 마지막 포인터가 화면 밖으로 나갈 때 전송됩니다.
  • ACTION_CANCEL: 모든 포인터를 포함한 전체 동작이 취소되었음을 나타냅니다.

시작 및 종료 동작

동작은 ACTION_DOWN 이벤트로 시작하여 ACTION_UP 또는 ACTION_CANCEL 이벤트로 끝나는 일련의 이벤트입니다. 한 번에 한 가지 동작만 활성화됩니다. DOWN, MOVE, 위쪽, 취소 작업은 전체 동작에 적용됩니다. 예를 들어 ACTION_MOVE가 있는 이벤트는 해당 시점의 모든 포인터가 이동함을 나타낼 수 있습니다.

포인터 추적하기

포인터의 색인과 ID를 사용하여 MotionEvent 내에서 개별 포인터 위치를 추적합니다.

  • 색인: MotionEvent는 포인터 정보를 배열에 저장합니다. 포인터의 색인은 이 배열 내에서 포인터의 위치입니다. 대부분의 MotionEvent 메서드는 포인터 ID가 아닌 매개변수로 포인터 색인을 사용합니다.
  • ID: 각 포인터에는 터치 이벤트 전체에서 지속성을 유지하는 ID 매핑도 있어 전체 동작에서 개별 포인터를 추적할 수 있습니다.

개별 포인터는 모션 이벤트 내에 정의되지 않은 순서로 표시됩니다. 따라서 포인터의 색인은 이벤트에서 다음 이벤트로 변경될 수 있지만, 포인터의 포인터 ID는 포인터가 활성 상태를 유지하는 한 일정하게 유지됩니다. getPointerId() 메서드를 사용하면 포인터의 ID를 획득하여 동작의 모든 후속 모션 이벤트에서 포인터를 추적할 수 있습니다. 그런 다음, 연속 모션 이벤트에 findPointerIndex() 메서드를 사용하여 해당 모션 이벤트에서 지정된 포인터 ID의 포인터 색인을 가져옵니다. 예:

Kotlin

private var mActivePointerId: Int = 0

override fun onTouchEvent(event: MotionEvent): Boolean {
    ...
    // Get the pointer ID.
    mActivePointerId = event.getPointerId(0)

    // ... Many touch events later...

    // Use the pointer ID to find the index of the active pointer
    // and fetch its position.
    val (x: Float, y: Float) = event.findPointerIndex(mActivePointerId).let { pointerIndex ->
        // Get the pointer's current position.
        event.getX(pointerIndex) to event.getY(pointerIndex)
    }
    ...
}

Java

private int mActivePointerId;

public boolean onTouchEvent(MotionEvent event) {
    ...
    // Get the pointer ID.
    mActivePointerId = event.getPointerId(0);

    // ... Many touch events later...

    // Use the pointer ID to find the index of the active pointer
    // and fetch its position.
    int pointerIndex = event.findPointerIndex(mActivePointerId);
    // Get the pointer's current position.
    float x = event.getX(pointerIndex);
    float y = event.getY(pointerIndex);
    ...
}

여러 터치 포인터를 지원하려면 개별 ACTION_POINTER_DOWNACTION_DOWN 이벤트 시간에 ID를 사용하여 모든 활성 포인터를 캐시하면 됩니다. ACTION_POINTER_UPACTION_UP 이벤트에서 캐시에서 포인터를 삭제합니다. 이러한 캐시된 ID는 다른 작업 이벤트를 올바르게 처리하는 데 유용합니다. 예를 들어 ACTION_MOVE 이벤트를 처리할 때 캐시된 각 활성 포인터 ID의 색인을 찾고 getX()getY() 함수를 사용하여 포인터의 좌표를 검색한 다음 이 좌표를 캐시된 좌표와 비교하여 이동한 포인터를 확인합니다.

getActionIndex() 함수는 ACTION_POINTER_UPACTION_POINTER_DOWN 이벤트에만 사용합니다. 이 함수는 항상 0를 반환하므로 ACTION_MOVE 이벤트와 함께 사용하지 마세요.

작업 MotionEvent개 가져오기

getActionMasked() 메서드 또는 호환성 버전 MotionEventCompat.getActionMasked()을 사용하여 MotionEvent의 작업을 검색합니다. 이전 getAction() 메서드와 달리 getActionMasked()는 여러 포인터와 함께 작동하도록 설계되었습니다. 포인터 인덱스 없이 작업을 반환합니다. 포인터 색인이 유효한 작업의 경우 다음 스니펫과 같이 getActionIndex()를 사용하여 작업과 연결된 포인터의 색인을 반환합니다.

Kotlin

val (xPos: Int, yPos: Int) = MotionEventCompat.getActionMasked(event).let { action ->
    Log.d(DEBUG_TAG, "The action is ${actionToString(action)}")
    // Get the index of the pointer associated with the action.
    MotionEventCompat.getActionIndex(event).let { index ->
        // The coordinates of the current screen contact, relative to
        // the responding View or Activity.
        MotionEventCompat.getX(event, index).toInt() to MotionEventCompat.getY(event, index).toInt()
    }
}

if (event.pointerCount > 1) {
    Log.d(DEBUG_TAG, "Multitouch event")

} else {
    // Single touch event.
    Log.d(DEBUG_TAG, "Single touch event")
}

...

// Given an action int, returns a string description.
fun actionToString(action: Int): String {
    return when (action) {
        MotionEvent.ACTION_DOWN -> "Down"
        MotionEvent.ACTION_MOVE -> "Move"
        MotionEvent.ACTION_POINTER_DOWN -> "Pointer Down"
        MotionEvent.ACTION_UP -> "Up"
        MotionEvent.ACTION_POINTER_UP -> "Pointer Up"
        MotionEvent.ACTION_OUTSIDE -> "Outside"
        MotionEvent.ACTION_CANCEL -> "Cancel"
        else -> ""
    }
}

Java

int action = MotionEventCompat.getActionMasked(event);
// Get the index of the pointer associated with the action.
int index = MotionEventCompat.getActionIndex(event);
int xPos = -1;
int yPos = -1;

Log.d(DEBUG_TAG,"The action is " + actionToString(action));

if (event.getPointerCount() > 1) {
    Log.d(DEBUG_TAG,"Multitouch event");
    // The coordinates of the current screen contact, relative to
    // the responding View or Activity.
    xPos = (int)MotionEventCompat.getX(event, index);
    yPos = (int)MotionEventCompat.getY(event, index);

} else {
    // Single touch event.
    Log.d(DEBUG_TAG,"Single touch event");
    xPos = (int)MotionEventCompat.getX(event, index);
    yPos = (int)MotionEventCompat.getY(event, index);
}
...

// Given an action int, returns a string description
public static String actionToString(int action) {
    switch (action) {

        case MotionEvent.ACTION_DOWN: return "Down";
	case MotionEvent.ACTION_MOVE: return "Move";
	case MotionEvent.ACTION_POINTER_DOWN: return "Pointer Down";
	case MotionEvent.ACTION_UP: return "Up";
	case MotionEvent.ACTION_POINTER_UP: return "Pointer Up";
	case MotionEvent.ACTION_OUTSIDE: return "Outside";
	case MotionEvent.ACTION_CANCEL: return "Cancel";
    }
    return "";
}
그림 1. 멀티터치 그리기 패턴

추가 리소스

입력 이벤트와 관련된 자세한 내용은 다음 참조를 확인하세요.