[안드로이드] 터치 이벤트의 흐름

Dev Hanna·2021년 6월 6일
2

안드로이드

목록 보기
7/9

setOnTouchListener 의 리턴값이 불러온 버그


안드로이드 개발자라면 누구나 한번 쯤 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가 실행되지 않는 걸 확인할 수 있다.

onInterceptTouchEvent 의 역할


만약 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에서만 할 수 있다.

연속된 TouchEvent의 처리


드래그와 같은 연속된 터치이벤트 발생시, 이벤트가 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까지만 전달된다.

이미지로 보는 터치이벤트의 흐름


터치 이벤트 핸들링이 어떻게 되는지 잘 표현된 이미지가 있어 첨부해 본다.

profile
오늘도 1보 걷기

0개의 댓글