일반 동작 감지

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

터치 동작은 사용자가 터치스크린에 손가락을 한 개 이상 올려놓고 앱에서 이러한 터치 패턴을 동작으로 해석할 때 발생합니다. 동작 감지에는 두 단계가 있습니다.

  1. 터치 이벤트 데이터를 수집하는 중입니다.
  2. 데이터를 해석하여 앱에서 지원하는 동작의 기준을 충족하는지 확인합니다.

AndroidX 클래스

이 문서의 예에서는 GestureDetectorCompatMotionEventCompat 클래스를 사용합니다. 이러한 클래스는 AndroidX 라이브러리에 있습니다. 가능한 경우 AndroidX 클래스를 사용하여 이전 기기와의 호환성을 제공합니다. MotionEventCompatMotionEvent 클래스를 대체하지 않습니다. 해당 이벤트와 관련된 작업을 수신하기 위해 MotionEvent 객체를 전달하는 정적 유틸리티 메서드를 제공합니다.

데이터 수집

사용자가 화면에 손가락을 한 개 이상 올려놓으면 터치 이벤트를 수신하는 뷰에 콜백 onTouchEvent()가 트리거됩니다. 동작으로 식별되는 위치, 압력, 크기, 다른 손가락 추가와 같은 터치 이벤트 시퀀스마다 onTouchEvent()가 여러 번 실행됩니다.

동작은 사용자가 화면을 처음 터치할 때 시작되고 시스템이 사용자의 손가락 또는 손가락 위치를 추적하면서 계속된 후 화면에서 나가는 마지막 손가락의 최종 이벤트를 캡처하여 종료됩니다. 이 상호작용이 진행되는 동안 onTouchEvent()에 전달된 MotionEvent는 모든 상호작용의 세부정보를 제공합니다. 앱은 MotionEvent에서 제공하는 데이터를 사용하여 관심 있는 동작이 발생하는지 확인할 수 있습니다.

활동 또는 뷰의 터치 이벤트 캡처

Activity 또는 View에서 터치 이벤트를 가로채려면 onTouchEvent() 콜백을 재정의합니다.

다음 코드 스니펫은 getAction()를 사용하여 사용자가 실행하는 작업을 event 매개변수에서 추출합니다. 이렇게 하면 관심 있는 동작이 발생하는지 확인하는 데 필요한 원시 데이터가 제공됩니다.

Kotlin

class MainActivity : Activity() {
    ...
    // This example shows an Activity. You can use the same approach if you are 
    // subclassing a View.
    override fun onTouchEvent(event: MotionEvent): Boolean {
        return when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.d(DEBUG_TAG, "Action was DOWN")
                true
            }
            MotionEvent.ACTION_MOVE -> {
                Log.d(DEBUG_TAG, "Action was MOVE")
                true
            }
            MotionEvent.ACTION_UP -> {
                Log.d(DEBUG_TAG, "Action was UP")
                true
            }
            MotionEvent.ACTION_CANCEL -> {
                Log.d(DEBUG_TAG, "Action was CANCEL")
                true
            }
            MotionEvent.ACTION_OUTSIDE -> {
                Log.d(DEBUG_TAG, "Movement occurred outside bounds of current screen element")
                true
            }
            else -> super.onTouchEvent(event)
        }
    }
}

Java

public class MainActivity extends Activity {
...
// This example shows an Activity. You can use the same approach if you are
// subclassing a View.
@Override
public boolean onTouchEvent(MotionEvent event){
    switch(event.getAction()) {
        case (MotionEvent.ACTION_DOWN) :
            Log.d(DEBUG_TAG,"Action was DOWN");
            return true;
        case (MotionEvent.ACTION_MOVE) :
            Log.d(DEBUG_TAG,"Action was MOVE");
            return true;
        case (MotionEvent.ACTION_UP) :
            Log.d(DEBUG_TAG,"Action was UP");
            return true;
        case (MotionEvent.ACTION_CANCEL) :
            Log.d(DEBUG_TAG,"Action was CANCEL");
            return true;
        case (MotionEvent.ACTION_OUTSIDE) :
            Log.d(DEBUG_TAG,"Movement occurred outside bounds of current screen element");
            return true;
        default :
            return super.onTouchEvent(event);
    }
}

이 코드는 사용자가 길게 탭하거나 드래그할 때 Logcat에서 다음과 같은 메시지를 생성합니다.

GESTURES D   Action was DOWN
GESTURES D   Action was UP
GESTURES D   Action was MOVE

맞춤 동작의 경우 이러한 이벤트를 직접 처리하여 처리해야 하는 동작을 나타내는지 확인할 수 있습니다. 그러나 앱에서 두 번 탭, 길게 터치, 플링 등 일반적인 동작을 사용하는 경우 GestureDetector 클래스를 활용할 수 있습니다. GestureDetector를 사용하면 개별 터치 이벤트를 직접 처리하지 않고도 일반적인 동작을 더 쉽게 감지할 수 있습니다. 자세한 내용은 동작 감지에서 설명합니다.

단일 뷰의 터치 이벤트 캡처

onTouchEvent() 대신 setOnTouchListener() 메서드를 사용하여 View.OnTouchListener 객체를 View 객체에 연결할 수 있습니다. 이렇게 하면 다음 예와 같이 기존 View를 서브클래스로 분류하지 않고도 터치 이벤트를 수신 대기할 수 있습니다.

Kotlin

findViewById<View>(R.id.my_view).setOnTouchListener { v, event ->
    // Respond to touch events.
    true
}

Java

View myView = findViewById(R.id.my_view);
myView.setOnTouchListener(new OnTouchListener() {
    public boolean onTouch(View v, MotionEvent event) {
        // Respond to touch events.
        return true;
    }
});

ACTION_DOWN 이벤트에 대해 false를 반환하는 리스너를 만들 때 주의하세요. 이렇게 하면 후속 이벤트 ACTION_MOVEACTION_UP 시퀀스에 대해 리스너가 호출되지 않습니다. 이는 ACTION_DOWN가 모든 터치 이벤트의 시작점이기 때문입니다.

맞춤 뷰를 만드는 경우 앞에서 설명한 대로 onTouchEvent()를 재정의할 수 있습니다.

동작 감지

Android는 일반적인 동작 감지를 위한 GestureDetector 클래스를 제공합니다. 이 메서드는 onDown(), onLongPress(), onFling()를 지원합니다. GestureDetector는 앞에서 설명한 onTouchEvent() 메서드와 함께 사용할 수 있습니다.

지원되는 모든 동작 감지

GestureDetectorCompat 객체를 인스턴스화할 때 이 객체에 사용되는 매개변수 중 하나는 GestureDetector.OnGestureListener 인터페이스를 구현하는 클래스입니다. GestureDetector.OnGestureListener는 특정 터치 이벤트가 발생하면 사용자에게 알립니다. GestureDetector 객체가 이벤트를 수신할 수 있도록 하려면 뷰 또는 활동의 onTouchEvent() 메서드를 재정의하고 관찰된 모든 이벤트를 감지기 인스턴스에 전달합니다.

다음 스니펫에서 개별 on<TouchEvent> 메서드의 반환 값 true는 터치 이벤트가 처리됨을 나타냅니다. 반환 값 false는 터치가 처리될 때까지 뷰 스택 전체에 이벤트를 전달합니다.

테스트 앱에서 다음 스니펫을 실행하면 터치스크린과 상호작용할 때 작업이 트리거되는 방식과 각 터치 이벤트에 관한 MotionEvent의 콘텐츠를 파악할 수 있습니다. 그런 다음 간단한 상호작용을 위해 생성되는 데이터의 양을 확인합니다.

Kotlin

private const val DEBUG_TAG = "Gestures"

class MainActivity :
        Activity(),
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {

    private lateinit var mDetector: GestureDetectorCompat

    // Called when the activity is first created.
    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener.
        mDetector = GestureDetectorCompat(this, this)
        // Set the gesture detector as the double-tap
        // listener.
        mDetector.setOnDoubleTapListener(this)
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (mDetector.onTouchEvent(event)) {
            true
        } else {
            super.onTouchEvent(event)
        }
    }

    override fun onDown(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDown: $event")
        return true
    }

    override fun onFling(
            event1: MotionEvent,
            event2: MotionEvent,
            velocityX: Float,
            velocityY: Float
    ): Boolean {
        Log.d(DEBUG_TAG, "onFling: $event1 $event2")
        return true
    }

    override fun onLongPress(event: MotionEvent) {
        Log.d(DEBUG_TAG, "onLongPress: $event")
    }

    override fun onScroll(
            event1: MotionEvent,
            event2: MotionEvent,
            distanceX: Float,
            distanceY: Float
    ): Boolean {
        Log.d(DEBUG_TAG, "onScroll: $event1 $event2")
        return true
    }

    override fun onShowPress(event: MotionEvent) {
        Log.d(DEBUG_TAG, "onShowPress: $event")
    }

    override fun onSingleTapUp(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onSingleTapUp: $event")
        return true
    }

    override fun onDoubleTap(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDoubleTap: $event")
        return true
    }

    override fun onDoubleTapEvent(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: $event")
        return true
    }

    override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: $event")
        return true
    }

}

Java

public class MainActivity extends Activity implements
        GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener{

    private static final String DEBUG_TAG = "Gestures";
    private GestureDetectorCompat mDetector;

    // Called when the activity is first created.
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Instantiate the gesture detector with the
        // application context and an implementation of
        // GestureDetector.OnGestureListener.
        mDetector = new GestureDetectorCompat(this,this);
        // Set the gesture detector as the double-tap
        // listener.
        mDetector.setOnDoubleTapListener(this);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
            return true;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent event) {
        Log.d(DEBUG_TAG,"onDown: " + event.toString());
        return true;
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2,
            float velocityX, float velocityY) {
        Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onLongPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onLongPress: " + event.toString());
    }

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
            float distanceY) {
        Log.d(DEBUG_TAG, "onScroll: " + event1.toString() + event2.toString());
        return true;
    }

    @Override
    public void onShowPress(MotionEvent event) {
        Log.d(DEBUG_TAG, "onShowPress: " + event.toString());
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapUp: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTap: " + event.toString());
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        Log.d(DEBUG_TAG, "onDoubleTapEvent: " + event.toString());
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        Log.d(DEBUG_TAG, "onSingleTapConfirmed: " + event.toString());
        return true;
    }
}

지원되는 동작 하위 집합 감지

몇 가지 동작만 처리하려면 GestureDetector.OnGestureListener 인터페이스를 구현하는 대신 GestureDetector.SimpleOnGestureListener를 확장하면 됩니다.

GestureDetector.SimpleOnGestureListener은 모든 메서드에 대해 false를 반환하여 on<TouchEvent> 모든 메서드에 대한 구현을 제공합니다. 이렇게 하면 중요한 메서드만 재정의할 수 있습니다. 예를 들어 다음 코드 스니펫은 GestureDetector.SimpleOnGestureListener를 확장하고 onFling()onDown()를 재정의하는 클래스를 만듭니다.

GestureDetector.OnGestureListener를 사용하든 GestureDetector.SimpleOnGestureListener를 사용하든 true를 반환하는 onDown() 메서드를 구현하는 것이 좋습니다. 모든 동작이 onDown() 메시지로 시작하기 때문입니다. GestureDetector.SimpleOnGestureListener에서 기본적으로 하는 것처럼 onDown()에서 false를 반환하면 시스템은 개발자가 나머지 동작을 무시하려고 하며 GestureDetector.OnGestureListener의 다른 메서드는 호출되지 않는다고 가정합니다. 이로 인해 앱에 예기치 않은 문제가 발생할 수 있습니다. 전체 동작을 정말로 무시하려면 onDown()에서 false만 반환합니다.

Kotlin

private const val DEBUG_TAG = "Gestures"

class MainActivity : Activity() {

    private lateinit var mDetector: GestureDetectorCompat

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mDetector = GestureDetectorCompat(this, MyGestureListener())
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        mDetector.onTouchEvent(event)
        return super.onTouchEvent(event)
    }

    private class MyGestureListener : GestureDetector.SimpleOnGestureListener() {

        override fun onDown(event: MotionEvent): Boolean {
            Log.d(DEBUG_TAG, "onDown: $event")
            return true
        }

        override fun onFling(
                event1: MotionEvent,
                event2: MotionEvent,
                velocityX: Float,
                velocityY: Float
        ): Boolean {
            Log.d(DEBUG_TAG, "onFling: $event1 $event2")
            return true
        }
    }
}

Java

public class MainActivity extends Activity {

    private GestureDetectorCompat mDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDetector = new GestureDetectorCompat(this, new MyGestureListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent event){
        if (this.mDetector.onTouchEvent(event)) {
              return true;
        }
        return super.onTouchEvent(event);
    }

    class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
        private static final String DEBUG_TAG = "Gestures";

        @Override
        public boolean onDown(MotionEvent event) {
            Log.d(DEBUG_TAG,"onDown: " + event.toString());
            return true;
        }

        @Override
        public boolean onFling(MotionEvent event1, MotionEvent event2,
                float velocityX, float velocityY) {
            Log.d(DEBUG_TAG, "onFling: " + event1.toString() + event2.toString());
            return true;
        }
    }
}

추가 리소스