안드로이드 개발자라면 누구나 한번 쯤 setOnTouchListener()
를 사용해 봤을 것이다. 이 메소드에는 Boolean
리턴값을 보내게 되어 있는데, 어떤 값을 보내 던 setOnTouchListener()
에 정의한 메소드는 잘 실행 되기 때문에, 이 리턴값에 대해 깊게 생각해보지 않을 수 있다.
이런 안일함이 불러온 버그를 보자. Edit Text를 누르면 커스텀 키보드 레이아웃이 올라온다. 이 키보드 레이아웃은 기존에 홈, 뒤로가기 버튼이 있던 곳 위에 위치하게 된다. 문제는 키보드가 올라온 상태에서 홈, 뒤로가기 버튼이 있는 부근을 터치하면, 키보드의 터치이벤트도 발생하고, 홈버튼의 터치이벤트도 발생한다는 것.
keyboard_stage.setOnTouchListener { view, event ->
if (event.action == MotionEvent.ACTION_UP) view.performClick()
return@setOnTouchListener false
}
코드를 보면 키보드 레이아웃의 터치이벤트를 실행시키고 난 후, false
값을 리턴해주고 있다. 이 리턴값을 true
로 고치고나니 버그를 해결할 수 있었다. 대~충 리턴값이 다음 이벤트를 발생시키는 트리거역할을 하는것임을 눈치챌 수 있을것이다.
사용자가 기계에 터치를 했을때, 안드로이드 앱은 어디서부터 이 이벤트를 받아서 처리할까?
터치가 발생하면 Activity가 제일 먼저 알아차리고, 이어서 Activity > ViewGroup > View 순으로 Notify된다.
반대로 이벤트의 처리는 View > ViweGroup > Activity 순으로 처리가 된다.
Understanding Android touch flow control / 이미지와 이 글에 영감을 준 출처
업무가 들어왔을 때 팀장> 대리> 사원 순으로 업무가 내려가는 것을 상상해보자. Activity 팀장은 터치가 들어올 때 자기가 먼저 처리(onTouchEvent
)하지 않고 ViewGroup 대리에게 dispatch
한다. ViewGroup 대리는 View 사원에게 dispatch
한다. View 사원도 dispatch
를 한다..! 어? 그런데 더이상 넘겨줄 View가 없으므로 dispatch
(정확히는 dispatchTouchEvent()
) 라는 메소드 안에서 onTouchEvent()
를 실행시킨다.
참고로
onTouchListener()
가 정의되어있으면onTouchEvent()
대신onTouch()
가 실행된다)
onTouchEvent()
는 터치에 대한 일처리가 이루어지는 것이라고 보면 된다. 여기서 View 사원이 true
를 리턴하면 '일 처리가 끝났으니 ViewGroup 대리님과 Activity팀장님은 onTouchEvent()
하지 않으셔도 됩니다.' 라는 뜻이다. 반대로 false
를 리턴하면 ViewGroup 대리의 onTouchEvent()
가 실행되고, 여기서도 false
를 리턴하면 Activity 팀장의 onTouchEvent()
가 실행이 된다.
터치 이벤트의 흐름을 파악하기 위해 Elye라는 분이 만든 앱이다.
하얀 부분은 Activity, 빨간색 부분은 ViewGroup(Layout), 파란색 부분은 View이다.
아래 토글버튼으로 리턴값을 조절할 수 있다. 이 상태에서 파란색 부분을 클릭하면
touchEvent에서 모두 false 리턴시
MyActivity: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN End with false
MyView: dispatchTouchEvent ACTION_DOWN Start
MyView: onTouchEvent ACTION_DOWN Start
MyView: onTouchEvent ACTION_DOWN End with false
MyView: dispatchTouchEvent ACTION_DOWN End with false
MyViewGroup: onTouchEvent ACTION_DOWN Start
MyViewGroup: onTouchEvent ACTION_DOWN End with false
MyViewGroup: dispatchTouchEvent ACTION_DOWN End with false
MyActivity: onTouchEvent ACTION_DOWN Start
MyActivity: onTouchEvent ACTION_DOWN End with false
MyActivity: dispatchTouchEvent ACTION_DOWN End with false
View > ViweGroup > Activity 순으로 dispatch 되고, View에서부터 순차적으로 onTouchEvnet
가 실행되는 걸 볼 수 있다.
View 의 touchEvent 가 true 리턴 시
MyActivity: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN End with false
MyView: dispatchTouchEvent ACTION_DOWN Start
MyView: onTouchEvent ACTION_DOWN Start
MyView: onTouchEvent ACTION_DOWN End with true
MyView: dispatchTouchEvent ACTION_DOWN End with true
MyViewGroup: dispatchTouchEvent ACTION_DOWN End with true
MyActivity: dispatchTouchEvent ACTION_DOWN End with true
View > ViweGroup > Activity 순으로 dispatch 되고, View에서 onTouchEvnet
true 리턴 시, ViewGroup과 Activity에서는 onTouchEvent
가 실행되지 않는 걸 확인할 수 있다.
만약 ViewGroup 대리가 View 사원에게 이 일을 알리지 않고 자기선에서 처리하고 싶을 수도 있을 것이다. 이럴때 사용되는게 onInterceptTouchEvnet()
이다. ViewGroup 이 Intercept 할 경우, View는 터치이벤트를 전달받지 못한다. 따라서 바로 ViewGroup의 onTouchEvent()
가 실행이 된다.
ViewGroup(Layout) 에서 onInterceptTouchEvent 를 했을 경우
MyActivity: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN End with true
MyViewGroup: onTouchEvent ACTION_DOWN Start
MyViewGroup: onTouchEvent ACTION_DOWN End with false
MyViewGroup: dispatchTouchEvent ACTION_DOWN End with false
MyActivity: onTouchEvent ACTION_DOWN Start
MyActivity: onTouchEvent ACTION_DOWN End with false
MyActivity: dispatchTouchEvent ACTION_DOWN End with false
참고로
onInterceptTouchEvent
는 ViewGroup에서만 할 수 있다.
드래그와 같은 연속된 터치이벤트 발생시, 이벤트가 dispatch되지 않고 생략될 수 있다.
드래그는 누르기
>움직이기
>떼기
' 를 해야하기 때문에, 차례대로 ACTION_DOWN
>ACTION_MOVE
>ACTION_UP
이벤트가 연속 발생된다.
만약 첫번째 이벤트인ACTION_DOWN
일 때, Activity
에서 touchEvent를 true
로 리턴한다고 해보자.
MyActivity: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN End with false
MyView: dispatchTouchEvent ACTION_DOWN Start
MyView: onTouchEvent ACTION_DOWN Start
MyView: onTouchEvent ACTION_DOWN End with false
MyView: dispatchTouchEvent ACTION_DOWN End with false
MyViewGroup: onTouchEvent ACTION_DOWN Start
MyViewGroup: onTouchEvent ACTION_DOWN End with false
MyViewGroup: dispatchTouchEvent ACTION_DOWN End with false
MyActivity: onTouchEvent ACTION_DOWN Start
MyActivity: onTouchEvent ACTION_DOWN End with true
MyActivity: dispatchTouchEvent ACTION_DOWN End with true
사용자의 ACTION_DOWN
터치는 View 까지 dispatch
되어onTouchEvent
발생후 Activity 에 와서야 true
값을 리턴받는다.
이 말은 즉, 해당 터치에 대한 관심은 Activity만 가지고 있다고 볼 수 있다. 따라서 뒤에 이어지는 ACTION_MOVE
>ACTION_UP
은 굳이 View까지 dispatch
되지 않고 Activity에서 모든 일을 처리하게 된다.
MyActivity: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: dispatchTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN Start
MyViewGroup: onInterceptTouchEvent ACTION_DOWN End wi
MyView: dispatchTouchEvent ACTION_DOWN Start
MyView: onTouchEvent ACTION_DOWN Start
MyView: onTouchEvent ACTION_DOWN End with fal
MyView: dispatchTouchEvent ACTION_DOWN End with f
MyViewGroup: onTouchEvent ACTION_DOWN Start
MyViewGroup: onTouchEvent ACTION_DOWN End with false
MyViewGroup: dispatchTouchEvent ACTION_DOWN End with false
MyActivity: onTouchEvent ACTION_DOWN Start
MyActivity: onTouchEvent ACTION_DOWN End with true
MyActivity: dispatchTouchEvent ACTION_DOWN End with true
---> Activity에서 ACTION_DOWN onTouchEvent true값 리턴
MyActivity: dispatchTouchEvent ACTION_MOVE Start
MyActivity: onTouchEvent ACTION_MOVE Start
MyActivity: onTouchEvent ACTION_MOVE End with true
MyActivity: dispatchTouchEvent ACTION_MOVE End with true
---> ACTION_MOVE 이벤트는 dispatch 되지 않고 Activity 안에서 처리
MyActivity: dispatchTouchEvent ACTION_UP Start
MyActivity: onTouchEvent ACTION_UP Start
MyActivity: onTouchEvent ACTION_UP End with true
MyActivity: dispatchTouchEvent ACTION_UP End with true
---> ACTION_UP 이벤트도 dispatch 되지 않고 Activity 안에서 처리
- 참고로
ACTION_DOWN
에서 Activity 가false
를 리턴해도 결과는 같았다. ViwGroup, View에서 이미false
를 했기 때문에, 그 뒤에 이어지는 터치이벤트는 Activity의onTouchEvent
값과 상관없이 View까지dispatch
되지 않는 것으로 보인다.- 당연한 말이지만 ViwGroup에서
onTouchEvent
true 리턴시, 연이어 전달되는 이벤트들은 View까지 전달되지 않고 ViewGroup까지만 전달된다.
터치 이벤트 핸들링이 어떻게 되는지 잘 표현된 이미지가 있어 첨부해 본다.