[Android] 화면 터치 여부를 작동하는 dispatchTouchEvent는 어떻게 작동할까

안드로이드 개발을 하다보면 상황에 따라 화면 터치 여부를 파악하거나 이벤트를 추가해야하는 경우가 있는데요! 

onTouchEvent(), onClick 등 많이 사용해보았지만 내부 구조에까지는 알지 못했던 것 같아

뷰 계층 구조를 통해 터치 이벤트가 흐르는 방식에 대해 정리해보려고 합니다.


1. MotionEvent

간단하게 MotionEvent 종류에 대해 다시 한번 정리 해보도록 하겠습니다.

  • ACTION_DOWN : 손가락 또는 물체가 화면에 처음 접촉할 때 발생하는 이벤트입니다. 이벤트에는 제스처의 초기 시작 위치가 포함됩니다.
  • ACTION_UP : 손가락 또는 물체가 화면에서 떼어질 때 발생하는 이벤트입니다. 제스처의 최종 놓은 위치가 포함됩니다.
  • ACTION_MOVE : ACTION_DOWN과 ACTION_UP 사이의 모든 이동이 포함됩니다. 손가락의 최종 놓은 위치가 초기 시작 위치와 다른 경우에 발생합니다.
  • ACTION_CANCEL: 현재 제스처가 중단되었음을 나타내는 이벤트입니다. 이는 부모 뷰가 자식으로부터 이벤트를 가로챌 때 발생합니다. 예를 들어 사용자가 스크롤 뷰에서 버튼을 누르는 대신 스크롤링을 시작할만큼 충분히 드래그한 경우에 발생합니다.

 

 

2. Android에서 MotionEvents를 처리하는 방법

보통 터치 이벤트가 발생하면, 뷰 계층 구조를 통해 위에서 아래(Top-down)로 흐릅니다. 즉 뷰 트리의 루트(ex: 액티비티)에서 시작하여, 이벤트가 발생한 뷰까지 이어지며 dispatchTouchEvent() 라는 것이 호출됩니다. 즉 위에 사진과 같은 구조라면 Activity -> ScrollView -> FrameLayout -> View A -> View B 순으로 흐릅니다. 또한, 터치 이벤트를 받은 View B에서 터치 이벤트에 대해 처리한다면 반대로 View B부터 Activity까지 다시 전달이 됩니다.

 

그렇다면 루트뷰에서 터치 이벤트가 발생한 뷰까지 어떻게 작동하는지 더 자세하게 알아봅시다!

 

dispatchTouchEvent()

위 그림을 보면 dispatchTouchEvent() 메서드를 확인할 수 있습니다. dispatchTouchEvent()는 onTouchEvent()와 뷰에 설정된 터치 리스너를 호출하고, 그 중 어느 하나라도 true를 반환하면 이벤트가 소비되었다는 의미로 true를 반환합니다.

 

가장 아래에 있는 ViewA, B는 자식을 가지지 않으므로 간단합니다. dispatchTouchEvent()는 추가적으로 상태 관리와 함께 onTouchEvent()를 즉시 호출하기 때문에, 기본 상태 관리를 변경하지 않기 위해 커스텀 뷰에서는 dispatchTouchEvent()보다는 onTouchEvent()를 오버라이드하는 것은 권장한다고 합니다.

 

다음은 ViewGroup인 FrameLayout과 ScrollView 영역을 살펴보도록 하겠습니다.

ViewGroup에서는 onInterceptTouchEvent()가 호출됩니다. onInterceptTouchEvent()가 false를 반환하면, child.dispatchTouchEvent()를 호출합니다. 만약 onInterceptTouchEvent()가 true를 반환한다면, 뷰그룹에서 더 이상 자식 뷰에게 터치 이벤트를 전달하지 않습니다.

 

onInterceptTouchEvent()

그림과 같이  onInterceptTouchEvent()는 ViewGroup에 존재하며 View는 onInterceptTouchEvent() 함수를 가지지 않습니다. 해당 메서드의 목적은  ViewGroup이 특정 유형의 터치 이벤트를 처리하면서 동시에 자식 요소가 다른 유형을 처리하도록 하는 것입니다. 예를 들어, ScrollView는 스크롤링을 처리하기 위해 이를 재정의하고 자식 요소가 클릭과 같은 다른 작업을 처리하도록 합니다.

 

onTouchEvent()

onTouchEvent()도 마찬가지로 반환 값에 따라 결과를 전달합니다. false인 경우 상위 뷰로 결과를 전달하지만, true인 경우 더 이상 전달하지 않게 됩니다.

 

 

 

onInterceptTouchEvent()가 true를 반환하는 경우도 그림으로 살펴보겠습니다.

그림과 같이 ViewB에서 터치하여 스크롤을 진행하지만, 오른쪽 그림과 같이 ViewGroup인 ScrollView에서 onInterceptTouchEvent()가 true를 반환하여 하위 계층까지 이벤트가 전달되지 않고 ScrollView에 있는 onTouchEvent()가 호출됩니다. 결국, View B까지 이벤트가 전달되지 않는 것을 알 수 있습니다.

 

 

 

개발하다보면 저도 스크롤이 안먹을 때, 터치 이벤트가 감지되지 않을 때가 있었는데요, 이번 기회에 터치 이벤트가 어떻게 작동되는지 자세하게 알게되어 금방 원인을 찾을 수 있을 것 같습니다!

 

 

 

 

Reference

https://developer.android.com/training/gestures/viewgroup?hl=ko 

https://readystory.tistory.com/185

https://proandroiddev.com/android-touch-system-part-2-common-touch-event-scenarios-a37a885f5f75