터치 및 포인터 움직임 추적하기

이 과정에서는 터치 이벤트의 움직임을 추적하는 방법을 설명합니다.

새로운 onTouchEvent()는 현재 터치의 접촉 위치, 압력, 크기 중 하나가 변경될 때마다 ACTION_MOVE 이벤트에서 트리거됩니다. 이러한 이벤트는 모두 일반 동작 감지의 설명과 같이 onTouchEvent()MotionEvent 매개변수에 기록됩니다.

손가락 기반의 터치는 때로는 가장 정확한 상호작용 형식이 아니기 때문에 단순한 접촉보다는 이동을 기반으로 터치 이벤트가 감지되는 경우가 많습니다. 앱이 움직임 기반의 동작(예: 스와이프)과 움직임 기반이 아닌 동작(예: 한 번 탭하기)을 구별하는 데 도움이 되도록 Android에서는 '터치 슬롭'이라는 개념을 포함했습니다. 터치 슬롭은 사용자의 터치 동작이 이동 기반 동작으로 해석되기 전까지 배회할 있는 거리(픽셀 단위)를 말합니다. 이 주제를 자세히 알아보려면 ViewGroup에서 터치 이벤트 관리하기를 참고하세요.

동작에서 움직임을 추적하는 방법은 애플리케이션의 요구사항에 따라 몇 가지가 있습니다. 예를 들면 다음과 같습니다.

  • 포인터의 시작 위치와 종료 위치(예: 화면에서 A 지점에서 B 지점으로 개체 이동)
  • 포인터가 이동하는 방향. x 좌표와 y 좌표에 의해 결정됩니다.
  • 기록. MotionEvent 메서드 getHistorySize()를 호출하여 동작 기록의 크기를 찾을 수 있습니다. 그런 다음 움직임 이벤트의 getHistorical<Value> 메서드를 사용하여 각 기록 이벤트의 위치, 크기, 시간, 압력을 구할 수 있습니다. 기록은 터치 그리기와 같은 용도로 사용자 손가락의 자취를 렌더링할 때 유용합니다. 자세한 내용은 MotionEvent 참조를 확인하세요.
  • 포인터가 터치 스크린 위를 움직일 때 포인터의 속도

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

추적 속도

단순히 포인터가 이동한 거리 또는 방향을 기준으로 하는 움직임 기반 동작이 있을 수 있습니다. 하지만 속도는 동작의 특징을 결정할 때나 동작이 발생했는지 여부를 결정할 때에도 결정적인 요소인 경우가 많습니다. 속도 계산을 더 쉽게 할 수 있도록 Android에서는 VelocityTracker 클래스를 제공합니다. VelocityTracker는 터치 이벤트의 속도를 추적하는 데 도움이 되며 속도가 동작의 기준 중 하나인 동작(예: 살짝 튕기기)에 유용합니다.

다음은 VelocityTracker API의 메서드의 목적을 보여주는 간단한 예입니다.

Kotlin

    private const val DEBUG_TAG = "Velocity"

    class MainActivity : Activity() {
        private var mVelocityTracker: VelocityTracker? = null

        override fun onTouchEvent(event: MotionEvent): Boolean {

            when (event.actionMasked) {
                MotionEvent.ACTION_DOWN -> {
                    // Reset the velocity tracker back to its initial state.
                    mVelocityTracker?.clear()
                    // If necessary retrieve a new VelocityTracker object to watch the
                    // velocity of a motion.
                    mVelocityTracker = mVelocityTracker ?: VelocityTracker.obtain()
                    // Add a user's movement to the tracker.
                    mVelocityTracker?.addMovement(event)
                }
                MotionEvent.ACTION_MOVE -> {
                    mVelocityTracker?.apply {
                        val pointerId: Int = event.getPointerId(event.actionIndex)
                        addMovement(event)
                        // When you want to determine the velocity, call
                        // computeCurrentVelocity(). Then call getXVelocity()
                        // and getYVelocity() to retrieve the velocity for each pointer ID.
                        computeCurrentVelocity(1000)
                        // Log velocity of pixels per second
                        // Best practice to use VelocityTrackerCompat where possible.
                        Log.d("", "X velocity: ${getXVelocity(pointerId)}")
                        Log.d("", "Y velocity: ${getYVelocity(pointerId)}")
                    }
                }
                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                    // Return a VelocityTracker object back to be re-used by others.
                    mVelocityTracker?.recycle()
                    mVelocityTracker = null
                }
            }
            return true
        }
    }
    

자바

    public class MainActivity extends Activity {
        private static final String DEBUG_TAG = "Velocity";
            ...
        private VelocityTracker mVelocityTracker = null;
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            int index = event.getActionIndex();
            int action = event.getActionMasked();
            int pointerId = event.getPointerId(index);

            switch(action) {
                case MotionEvent.ACTION_DOWN:
                    if(mVelocityTracker == null) {
                        // Retrieve a new VelocityTracker object to watch the
                        // velocity of a motion.
                        mVelocityTracker = VelocityTracker.obtain();
                    }
                    else {
                        // Reset the velocity tracker back to its initial state.
                        mVelocityTracker.clear();
                    }
                    // Add a user's movement to the tracker.
                    mVelocityTracker.addMovement(event);
                    break;
                case MotionEvent.ACTION_MOVE:
                    mVelocityTracker.addMovement(event);
                    // When you want to determine the velocity, call
                    // computeCurrentVelocity(). Then call getXVelocity()
                    // and getYVelocity() to retrieve the velocity for each pointer ID.
                    mVelocityTracker.computeCurrentVelocity(1000);
                    // Log velocity of pixels per second
                    // Best practice to use VelocityTrackerCompat where possible.
                    Log.d("", "X velocity: " + mVelocityTracker.getXVelocity(pointerId));
                    Log.d("", "Y velocity: " + mVelocityTracker.getYVelocity(pointerId));
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    // Return a VelocityTracker object back to be re-used by others.
                    mVelocityTracker.recycle();
                    break;
            }
            return true;
        }
    }
    

포인터 캡처 사용하기

게임, 원격 데스크톱, 가상화 클라이언트와 일부 앱은 마우스 포인터를 제어할 수 있게 되면 큰 이점을 누릴 수 있습니다. 포인터 캡처는 모든 마우스 이벤트를 앱의 포커스 뷰에 제공함으로써 이러한 제어를 제공하는 기능으로, Android 8.0(API 레벨 26) 이상에서 사용할 수 있습니다.

포인터 캡처 요청하기

앱의 뷰는 뷰가 포함된 뷰 계층 구조에 포커스가 있을 때만 포인터 캡처를 요청할 수 있습니다. 이런 이유로, 뷰에 특정 사용자 작업이 있을 때(예: onClick() 이벤트 중이나 활동의 onWindowFocusChanged() 이벤트 핸들러에서) 포인터 캡처를 요청해야 합니다.

포인터 캡처를 요청하려면 뷰에서 requestPointerCapture() 메서드를 호출합니다. 다음 코드 예는 사용자가 뷰를 클릭할 때 포인터 캡처를 요청하는 방법을 보여줍니다.

Kotlin

    fun onClick(view: View) {
        view.requestPointerCapture()
    }
    

자바

    @Override
    public void onClick(View view) {
        view.requestPointerCapture();
    }
    

포인터 캡처 요청이 성공하면 Android는 onPointerCaptureChange(true)를 호출합니다. 캡처를 요청한 뷰와 동일한 뷰 계층 구조인 경우 앱의 포커스 뷰에 마우스 이벤트가 전달됩니다. 다른 앱은 ACTION_OUTSIDE 이벤트를 포함하여 캡처가 해제될 때까지 마우스 이벤트 수신을 중지합니다. Android는 일반적으로 마우스 이외의 소스에서 발생한 포인터 이벤트를 전달하지만, 마우스 포인트가 더 이상 표시되지 않습니다.

캡처한 포인터 이벤트 처리하기

뷰가 포인터 캡처를 획득하고 나면 Android는 마우스 이벤트를 전달하기 시작합니다. 포커스 뷰는 다음 작업 중 하나를 수행하여 이벤트를 처리할 수 있습니다.

  1. 맞춤 뷰를 사용 중인 경우 onCapturedPointerEvent(MotionEvent)를 재정의합니다.
  2. 또는, OnCapturedPointerListener를 등록합니다.

다음 코드 예에서는 onCapturedPointerEvent(MotionEvent)를 구현하는 방법을 보여줍니다.

Kotlin

    override fun onCapturedPointerEvent(motionEvent: MotionEvent): Boolean {
        // Get the coordinates required by your app
        val verticalOffset: Float = motionEvent.y
        // Use the coordinates to update your view and return true if the event was
        // successfully processed
        return true
    }
    

자바

    @Override
    public boolean onCapturedPointerEvent(MotionEvent motionEvent) {
      // Get the coordinates required by your app
      float verticalOffset = motionEvent.getY();
      // Use the coordinates to update your view and return true if the event was
      // successfully processed
      return true;
    }
    

다음 코드 예에서는 OnCapturedPointerListener를 등록하는 방법을 보여줍니다.

Kotlin

    myView.setOnCapturedPointerListener { view, motionEvent ->
        // Get the coordinates required by your app
        val horizontalOffset: Float = motionEvent.x
        // Use the coordinates to update your view and return true if the event was
        // successfully processed
        true
    }
    

자바

    myView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
      @Override
      public boolean onCapturedPointer (View view, MotionEvent motionEvent) {
        // Get the coordinates required by your app
        float horizontalOffset = motionEvent.getX();
        // Use the coordinates to update your view and return true if the event was
        // successfully processed
        return true;
      }
    });
    

맞춤 뷰를 사용하든 리스너를 등록하든 상관없이 뷰는 트랙볼 기기가 전달한 좌표와 비슷하게 상대적 움직임(예: X/Y 델타)을 지정하는 포인터 좌표와 함께 MotionEvent를 수신합니다. getX()getY()를 사용하여 좌표를 검색할 수 있습니다.

포인터 캡처 해제하기

앱의 뷰는 다음 코드 예와 같이 releasePointerCapture()를 호출하여 포인터 캡처를 해제할 수 있습니다.

Kotlin

    override fun onClick(view: View) {
        view.releasePointerCapture()
    }
    

자바

    @Override
    public void onClick(View view) {
        view.releasePointerCapture();
    }
    

가장 일반적으로 캡처를 요청한 뷰가 포함된 뷰 계층 구조에 포커스가 없어졌을 때 시스템에서는 개발자가 명시적으로 releasePointerCapture()를 호출하지 않아도 캡처를 뷰에서 해제할 수 있습니다.