안드로이드 Touch에 대한 이해

매일 수정하는 GNOSS LV5·2022년 1월 26일
1

AndroidStudio

목록 보기
42/83

안드로이드에는 모든 행동들이 터치로 인해 이루어진다.
그렇다보니 개발을 하다보면

”어 버튼이 왜 안눌려지지?”
”왜 스크롤이 안되는거야?” 등등...

이와 같은 터치에 대한 에러가 발생할 경우가 생긴다.
이는 복합적인 원인이 있겠다.

  1. 터치 이벤트를 적용하지 않음.
  2. 터치 이벤트를 엉뚱한 component에 연결했을때
  3. 뷰끼리 겹치는 경우 내가 터치하고자 하는 뷰가 아래에 깔려있을 경우
  4. 스크롤이 겹치는경우(Nested - Recyclerview)... 수직 + 수직 Or 수평 or 수평

이를 해결하기 위해 좀 더 근본적인 걸 알아보자.

배경지식


Motion Event

터치의 모든 행동은 MotionEvent에 보고된다. MotionEven 는 모든 이벤트 정보와 연관성이 있다.

  • action (액션이 수행되는 것.)
  • x (x축 방향으로 터치)
  • y (y축 방향으로 터치)
  • rawX (device의 x축 절대좌표)
  • rawY (device의 y축 절대좌표)
  • eventTime ([SystemClock.uptimeMillis()](https://developer.android.com/reference/android/os/SystemClock#uptimeMillis()) 에 기반을 둔 이벤트가 발생한 시간)

Screen coordinates

x,y = 0,0은 디바이스의 좌측 상단지점이며 x, y = max,max는 우측 하단 지점이다.

Action

MotionEvent.getAction()은 다음과 같은 Int값을 리턴한다.

모든 값들은 공식문서에 있으며 이 포스트에서는 4방향을 언급하겠다.

  • ACTION_DOWN: 손가락이 화면에 처음 닿을때. 제스처의 첫 포지션이 들어간다.
  • ACTION_UP: 제스처의 최종 릴리즈 포지션을 나타낸다.
  • ACTION_MOVE:  ACTION_DOWN 과 ACTION_UP, 사이의 모든 움직임을 반환한다. 초기와 최종 위치가 다를때 나타난다.
  • ACTION_CANCEL: 현재 제스처가 취소당했을때이다. 부모뷰가 자식뷰의 이벤트를 가로채는 경우에 발생한다.

보통의 제스처는 ACTION_DOWN 으로 시작하여 ACTION_UP 혹은 ACTION_CANCEL 로 끝이난다. 만약 사용자가 드래그와같이 시작지점과 끝지점을 다르게 가져갈 경우 ACTION_MOVE 로 끝이 날 것이다.

Android가 MotionEvent를 다루는 방법


이벤트가 발생하면 top - down으로 이벤트가 발생한다. 가장 root인 Activity부터 시작한다. 만약 intercept 당하지 않는다면 그 다음으로 넘어가게 된다. 같은 방식으로 Button과 같은 컴포넌트까지 이어진다.

지나갈때는 각 뷰마다 dispatchTouchEvent()를 호출하면서 지나간다.

onInterceptTouchEvent()는 모든 뷰에 상속되고있다. 만약 이 메서드가 true를 반환한다면, 다음으로 내려가지 않고 해당 뷰에서 이벤트가 소비된다는 것을 의미한다.
false가 반환된다는 것은 뷰가 이벤트를 인식했지만 통과시켰다는 의미가 된다.

가장 down으로 이어지게 되면 onTouchEvent()가 발생하게 되고 이벤트를 소비한다.

타 문서에서 이벤트를 소비한다고 표현하였는데 이것이 아주 와닿는 표현이였다.


dispatchTouchEvent()

맨 처음 호출된다. 만약 호출한 뷰가 이벤트를 소비할 생각이라면 true를 반환한다.

View.dispatchTouchEvent

뷰는 자식이 없으므로 심플하다. onTouchEvent() 호출후 뷰의 리스너를 호출한다. 그리고 이벤트를 소비할 무언가가 있다면 true를 반환한다.

ViewGroup.dispatchTouchEvent

뷰그룹은 onInterceptTouchEvent() 를 호출한다. 만약 onInterceptTouchEvent() 가 false를 반환한다면 chilld view로 내려가 child.dispatchTouchEvent() 를 호출하고 이것을 계속해서 반복한다.
커스텀 ViewGroup에서 dispatchTouchEvent는 이벤트 감지 외에 추가적인 기능을 수행하므로 onInterceptTouchEvent() 를 수정하는것이 좋다.

ScrollView.dispatchTouchEvent

ViewGroup과 동일하다.

Activity.dispatchTouchEvent

자식의 dispatchTouchEvent() 를 호출한다. Activity는 onInterceptTouchEvent() 를 제공하지 않는다. 만약 자식의 onTouchEvent() true를 반환했다면 Activity의 onTouchEvent() 는 호출되지 않는다.


onInterceptTouchEvent()

View.onInterceptTouchEvent

View는 가지고있지 않다.

ViewGroup.onInterceptTouchEvent

모든 의도와 목적을 위해, 기본 구현은 false를 반환합니다.

이 방법을 재정의하는 주된 목적은 자녀가 다른 유형을 처리하도록 하면서 ViewGroup이 특정 유형의 터치 이벤트를 처리하도록 하는 것입니다. 예를 들어, ScrollView는 자식이 클릭과 같은 것을 처리하도록 하면서 스크롤을 처리하도록 재정의합니다.

ScrollView.onInterceptTouchEvent

ScrollView는 ViewGroup의 onInterceptTouchEvent()를 재정의합니다. 이벤트가 ACTION_MOVE인 경우, 이벤트가 드래그로 간주되기에 지원되는 방향으로 충분한 속도를 가지고 있는지 확인합니다.

만약 그렇다면, 뷰는 true를 반환하고 그 자식들은 ACTION_CANCELLED를 받는다.

이 함수는 또한 requestDisallowInterceptTouchEvent()를 호출합니다.

즉, 조상 뷰의 onInterceptTouchEvent()는 무시되고 스크롤이 ScrollView의 조상이 터치에서 하고 싶은 것보다 우선합니다.

Activity.onInterceptTouchEvent

가지고 있지 않다.


onTouchEvent()

View.onTouchEvent

뷰를 클릭할 수 있는 경우 기본 구현은 true를 반환하지만, 몇 개의 플래그를 업데이트하는 것 외에는 많은 것을 하지 않습니다. 안드로이드 문서는 상태 관리를 하기 때문에 사용자 지정 보기에서 이것을 재정의할 때 super.onTouchEvent()를 호출할 것을 권장합니다. 이 문서는 또한 클릭 제스처만 처리하려는 경우 performClick()을 재정의하는 것이 좋습니다.

ViewGroup.onTouchEvent

View 와 동일하다.

ScrollView.onTouchEvent

onTouchEvent()는 이벤트의 정보를 사용하여 뷰를 스크롤하고 스크롤을 수행하는 양을 파악합니다. 또한 스크롤과 관련된 모든 애니메이션을 처리합니다(예: 오버스크롤 효과).

Activity.onTouchEvent

기본 값은 항상 false를 반환합니다.


요약 테이블

requestDisallowInterceptTouchEvent()

이 기능은ViewParent 인터페이스로써 child View가 상위의 부모뷰나 조상이 이벤트를 가로채지 못하게 하기 위해 사용합니다.

ViewParent와 그 조상은 제스처 기간 동안 이 요청을 준수해야 하며, 이는 뷰 조상의 요격기가 ACTION_DOWN부터 ACTION_UP 또는 ACTION_CANCEL까지 허용되지 않습니다. 각각의 새로운 제스처에 대해 requestDisallowInterceptTouchEvent()를 다시 호출해야 합니다.

뷰의 조상에게 이벤트를 처리할 기회를 주고 싶지 않다면, 뷰의 onTouchEvent()는 이벤트가 다시 흐를 때 조상의 onTouchEvent()가 트리거되지 않도록 true를 반환해야 합니다.

예를 들어, 스크롤뷰 호출은 스크롤을 감지하면 onInterceptTouchEvent()와 onTouchEvent()입니다.

profile
러닝커브를 따라서 등반중입니다.

0개의 댓글