일반 동작 감지하기

'터치 동작'은 사용자가 터치 스크린에 손가락을 한 개 이상 올려놓을 때 발생하며, 애플리케이션은 터치의 패턴을 특정 동작으로 해석합니다. 그에 따른 두 단계로 동작 감지가 이루어집니다.

  1. 터치 이벤트에 관련된 데이터를 수집합니다.
  2. 데이터를 해석하여 앱에서 지원되는 동작의 조건을 충족하는지 여부를 확인합니다.

다음 관련 리소스를 참조하세요.

지원 라이브러리 클래스

이 과정의 예에서는 GestureDetectorCompat 클래스와 MotionEventCompat 클래스를 사용합니다. 이러한 클래스는 지원 라이브러리에 있습니다. 가능한 경우 지원 라이브러리 클래스를 사용하여 Android 1.6 이상을 실행하는 기기와의 호환성을 제공해야 합니다. MotionEventCompatMotionEvent 클래스를 대체하는 것이 아니라 이벤트와 관련하여 원하는 작업을 수신하기 위해 MotionEvent 개체를 전달하는 정적 유틸리티 메서드를 제공합니다.

데이터 수집하기

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

사용자가 화면을 처음 터치하면 동작이 시작되고, 시스템에서 사용자의 손가락 위치를 추적하면서 동작이 계속되고, 사용자의 손가락이 화면에서 떨어지는 최종 이벤트가 캡처되면 동작이 종료됩니다. 이러한 상호작용 과정에서 onTouchEvent()에 전달된 MotionEvent는 모든 상호작용의 세부정보를 제공합니다. 앱은 MotionEvent에서 제공된 데이터를 사용하여 관심 대상인 동작이 발생했는지 여부를 결정합니다.

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

활동 또는 뷰의 터치 이벤트를 가로채려면 onTouchEvent() 콜백을 재정의하세요.

다음 스니펫은 getActionMasked()를 사용하여 사용자가 수행한 작업을 event 매개변수에서 추출합니다. 관심 대상인 동작이 발생했는지 여부를 확인하는 데 필요한 원시 데이터를 제공합니다.

Kotlin

    class MainActivity : Activity() {
        ...
        // This example shows an Activity, but you would use the same approach if
        // you were subclassing a View.
        override fun onTouchEvent(event: MotionEvent): Boolean {

            val action: Int = MotionEventCompat.getActionMasked(event)

            return when (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)
            }
        }
    }
    

자바

    public class MainActivity extends Activity {
    ...
    // This example shows an Activity, but you would use the same approach if
    // you were subclassing a View.
    @Override
    public boolean onTouchEvent(MotionEvent event){

        int action = MotionEventCompat.getActionMasked(event);

        switch(action) {
            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);
        }
    }
    

그런 다음, 이러한 이벤트를 고유하게 처리하여 동작 발생 여부를 확인할 수 있습니다. 즉, 맞춤 동작에 수행해야 하는 종류의 처리를 진행합니다. 하지만 앱에서 두 번 탭하기, 길게 누르기, 살짝 튕기기 등의 일반적인 동작을 사용하는 경우에는 GestureDetector 클래스를 활용할 수 있습니다. GestureDetector를 사용하면 개별 터치 이벤트를 직접 처리하지 않고도 일반적인 동작을 쉽게 감지할 수 있습니다. 아래의 동작 감지에서 자세히 확인할 수 있습니다.

단일 뷰의 터치 이벤트 캡처하기

onTouchEvent() 대신 View.OnTouchListener 개체를 View 개체에 연결할 수 있습니다(setOnTouchListener() 메서드 사용). 이렇게 하면 기존 View의 서브클래스를 생성하지 않고 터치 이벤트를 수신할 수 있습니다. 예를 들면 다음과 같습니다.

Kotlin

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

자바

    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_MOVE 문자열과 ACTION_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
        }

    }
    

자바

    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.SimpleOnGestureListener를 확장하면 됩니다(GestureDetector.OnGestureListener 인터페이스를 구현하지 않음).

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

GestureDetector.OnGestureListener 사용 여부에 관계없이 true를 반환하는 onDown() 메서드를 구현하는 것이 좋습니다. 모든 동작이 onDown() 메시지로 시작되기 때문입니다. onDown()에서 false를 반환하는(GestureDetector.SimpleOnGestureListener의 기본적인 반환 값) 경우 시스템에서는 동작의 나머지 부분을 무시하고 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
            }
        }
    }
    

자바

    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){
            this.mDetector.onTouchEvent(event);
            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;
            }
        }
    }