Android TouchEvent Mechanism

choi jeong heon
슬기로운 개발생활
10 min readDec 8, 2021

An Activity contains a Window object, which is implemented by view.PhoneWindow uses DecorView as the root View of the entire application window,

and this DecorView divides the screen into two areas:

TitleView and ContentView. What we usually write is shown in ContentView. The following diagram shows the composition of the Activity.

Activity에는 view로 구현되는 `Window` 객체가 포함되어 있습니다.`PhoneWindow`는 전체 애플리케이션 창의 루트 View로 `DecorView`를 사용하며,

이 `DecorView`는 화면을 두 영역으로 나눕니다.

TitleView 및 ContentView. 우리가 일반적으로 작성하는 내용은 ContentView에 표시됩니다.

Event 전달의 주요 3메서드

  1. Distribution ( dispatchTouchEvent )
    dispatchTouchEvent 의 반환 값이 true이면 이벤트가 현재 view에서 소비 되었음을 나타낸다. false 이면 이벤트가 계속 distribution 됨
  2. Intercept TouchEvent
    메서드 반환 값이 true 인 경우 이벤트를 가로채서 이벤트를 현재 메서드에서 소비하도록 둡니다.
    false인 경우 Intercept가 없음을 의미하며 하위 view로 이벤트가 전달됩니다.
  3. Consumption (onTouchEvent)
    1️⃣ 메서드 반환 값이 true이면 현재 View가 해당 이벤트를 처리할 수 있음을 나타냅니다.
    2️⃣ false의 반환 값은 현재 View가 이 이벤트를 처리하지 않으며 parent View에 전달된 onTouchEvent 메서드에 의해 처리됨을 나타냅니다.
    3️⃣ return super.onTouchEvent(ev) 의 이벤트 처리는 두 가지 경우로 나뉩니다.
    1) View가 Clickable이거나 LongClickable 하면 소비를 나타내는 true를 반환합니다. (이벤트를 여기서 소비하겠다)
    2) View를 클릭할 수 없거나 길게 클릭할 수 없는 경우 false를 반환합니다. 이는 false를 반환하는 것처럼 event를 소비하지 않고 상위로 패스하겠다는 의미입니다.
Android 시스템에는 위 메서드를 가지는 세 가지 클래스가 있습니다.
  • Activity: distribution and consumption.
  • ViewGroup: distribution, interception, and consumption.
  • View: distribute and consume.

🤖 하드웨어와 함께 생각해보면?

When we operate on the touch screen, Linux receives the appropriate hardware interrupt, which is then processed into the original input event and written to the appropriate device node.

스크린을 터치하면, Linux가 hardware interrupt를 수신합니다. 그러면 리눅스는 original input event로 처리하고 적절한 디바이스 노드에 기록합니다.

What our Android Input System does, in summary, is monitor these device nodes, read out and translate data when a device node has data to read, then find the appropriate event recipient in all windows and distribute it.

Android 입력 시스템이 하는 일은 요약하자면 이러한 장치 노드를 모니터링하고, 읽어내느 것입니다. 장치 노드에 읽을 데이터가 있을 때 데이터를 translate하고 모든 windows에서 적절한 이벤트 수신자를 찾아 distribute 합니다.

When the click event is generated, the event is passed to the current Activity and completed by PhoneWindow in the Activity. PhoneWindow handles the event processing to DecorView, and then DecorView handles the event processing to ViewGroup.The source flow is as follows:

click 이벤트가 생성되면 이벤트가 현재 Activity에 전달되고 PhoneWindow에 의해 완료됩니다. PhoneWindow는 이벤트를 DecorView로 전달하고 DecorView는 이벤트를 ViewGroup으로 전달합니다.

소스 코드상 흐름은 아래와 같습니다. (⭐️ pseudo code 입니다.)

Activity#dispatchTouchEvent

Activity#getWindow().superDispatchTouchEvent() 에서
true가 반환되면 Activity의 dispatchTouchEvent()도 true를 반환하여 touch이벤트가 ViewGroup에 성공적으로 배포되고 ViewGroup이 다음 레이어를 계속 배포하고 Activity의 배포 작업이 종료됩니다.

false가 반환되면 Activity 계층에서 touch 이벤트가 소비되고 Activity TouchEvent() 가 실행됨을 의미합니다.

ViewGroup#dispatchTouchEvent

루트 ViewGroup의 경우 dispatchTouchEvent 는 주로 이벤트가 하위 View 처리에 배포되는지 아니면 자체적으로 배포되는지 결정합니다.

ViewGroup의 onInterceptTouchEvent 메서드가 true를 반환하여 이벤트를 가로채기를 원하는 경우 해당 이벤트는 ViewGroup에 의해 처리됩니다. 즉, 해당 onTouchEvent 메서드가 호출됩니다.

ViewGroup의 onInterceptTouchEvent 메서드가 false를 반환하면 , 현재 이벤트를 가로채지 않고 현재 이벤트가 계속해서 자식 요소에 전달되고 자식 요소의 dispatchTouchEvent 메서드가 호출되는 식으로 이벤트가 최종적으로 처리될 때까지 계속됩니다.

View#dispatchTouchEvent

ViewGroup에서 event를 intercept 하지 않았다면 View의 dispatchTouchEvent가 호출됩니다.

View에서 event를 소비하지 않으면 상위 요소의 onTouchEvent가 호출됩니다. 즉, ViewGroup의 onTouchEvent 가 호출됩니다.

📑 테스트를 해보자

위와 같이 앱을 구성하고, 실행을 시킨 다음 화면을 터치하면

D/Test: MainActivity : dispatchTouchEvent
D/Test: VG1 : dispatchTouchEvent
D/Test: VG1 : onInterceptTouchEvent
D/Test: V1 : dispatchTouchEvent
D/Test: V1 : onTouchEvent
D/Test: VG1 : onTouchEvent
D/Test: MainActivity : onTouchEvent

가장 하위 요소인 V1(View) 까지 event가 전달되었고,
event를 처리하지 않아 ViewGroup과 Activity의 onTouchEvent 도 호출되었습니다.

이번에는 VG1(ViewGroup) 에서 event를 intercept 해보겠습니다.

class VG1: ConstraintLayout {
//...생략

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
Log.d("Test", "VG1 : onInterceptTouchEvent")
return true
}

override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.d("Test", "VG1 : onTouchEvent")
return true
}
}
D/Test: MainActivity : dispatchTouchEvent
D/Test: VG1 : dispatchTouchEvent
D/Test: VG1 : onInterceptTouchEvent
D/Test: VG1 : onTouchEvent

VG1 의 onInterceptTouchEvent 에서 true 를 반환했더니 onTouchEvent 를 호출했습니다.
그리고 여기서 event 를 소비(onTouchEvent 에서 true를 리턴) 했더니 Activity로 다시 전달되지 않았습니다.
만약 여기 onTouchEvent 에서 false 를 리턴하면 Activity의 onTouchEvent가 호출됩니다.

아래 그림을 참고하면 이해하기 더 쉽습니다.

https://programmer.ink/think/android-touch-event-delivery-mechanism-as-before.html

참고 자료

--

--