[컴][안드로이드] onTouch() 와 onTouchEvent() 가 호출되는 순서

touch 의 event 발생시 전달되는 경로.

touch evnet flow

Activity 를 하나 상속받아서 view 를 하나 만들었다고 하자. 이 때 그 view 위에서 touch 를 하게 되면 아래와 같은 경로로 event 가 전달된다.

말로 설명을 하자면, activity의 dispatchTouchEvent() 가 호출되면서 window 의 superDispatchTouchEvent(ev) 를 호출한다. 이 superDispatchTouchEvent() 가 최상위에 있는 Dispatch 해주는 녀석이라고 봐도 무방할 듯 하다. 아무튼 이 녀석으로부터 시작해서 child 의 dispatchTouchEvent(event) 를 호출하고, 그 child 의 child 의 dispatchTouchEvent(event)를 호출하고, 또 그 child의 child 의 child 의 dispatchTouchEvent(event)를 호출한다. 이런 식으로 쭉 밑으로 내려 가다보면, View 의 dispatchTouchEvent(event) 로 가게 되고, 여기서 최종적으로 View 의 OnTouchEvent() 를 호출하게 된다.

하지만 참고로 Window 가 가장 밑에 있는 녀석을 이야기 하는 것이다. 가장 마지막의 child 가 가장 상위의 widget 이 되는 것이다.


ViewFlowExample(Activity).dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
getWindow().superDispatchTouchEvent(ev) --> PhoneWindow$DecorView(ViewGroup).dispatchTouchEvent

PhoneWindow$DecorView(ViewGroup).dispatchTouchEvent(MotionEvent)
  intercepted = onInterceptTouchEvent(ev);
  if (!canceled && !intercepted)
    if (actionMasked == MotionEvent.ACTION_DOWN
        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) 
      if (childrenCount != 0)
        for (int i = childrenCount - 1; i >= 0; i--)
          dispatchTransformedTouchEvent()
            handled = child.dispatchTouchEvent(event);
              
            return handled;
            
child.dispatchTouchEvent(event); ---> LinearLayout(ViewGroup).dispatchTouchEvent()

LinearLayout(ViewGroup).dispatchTouchEvent()
  intercepted = onInterceptTouchEvent(ev);
  if (!canceled && !intercepted)
  dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
    handled = child.dispatchTouchEvent(event);

FrameLayout(ViewGroup).dispatchTouchEvent()
  intercepted = onInterceptTouchEvent(ev);
  if (!canceled && !intercepted)
    dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
      handled = child.dispatchTouchEvent(event);

LinearLayout(ViewGroup).dispatchTouchEvent()
  intercepted = onInterceptTouchEvent(ev);
  if (!canceled && !intercepted)
    dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
      handled = child.dispatchTouchEvent(event);

ViewFlow(ViewGroup).dispatchTouchEvent()
  intercepted = onInterceptTouchEvent(ev);
  if (!canceled && !intercepted)
    dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
      handled = child.dispatchTouchEvent(event);

        LinearLayout(ViewGroup).dispatchTouchEvent()
          intercepted = onInterceptTouchEvent(ev);
          if (!canceled && !intercepted)
            dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)
              handled = child.dispatchTouchEvent(event);
                // i don't know why the below routine is travered.            
                TextView(View).dispatchTouchEvent()
                  TextView.onTouchEvent()
                  super.onTouchEvent()

          if (mFirstTouchTarget == null) 
            handled = dispatchTransformedTouchEvent(ev, canceled, child=null, 
                    TouchTarget.ALL_POINTER_IDS); 
          {
            if (newPointerIdBits == oldPointerIdBits) 
              if (child == null || child.hasIdentityMatrix())
                  if (child == null) 
                      handled = super.dispatchTouchEvent(event);
                      TextView(View).dispatchTouchEvent()
                     {
                       if(li.mOnTouchListener.onTouch(this, event) ...)
                       // OnTouchListener which is bined to the view.
                         return true
                       View.onTouchEvent(event)
                     }
            return handled
          }
  if (mFirstTouchTarget == null) 
          handled = dispatchTransformedTouchEvent(ev, canceled, null, 
                  TouchTarget.ALL_POINTER_IDS);

이것을 정리하면 아래와 같다.


Activity.dispatchTouchEvent()
  ViewGroup.dispatchTouchEvent()
    ViewGroup.onInterceptTouchEvent()
    View.dispatchTouchEvent()
      View.onTouch()
      View.onTouchEvent()
    ViewGroup.onTouch()
    ViewGroup.onTouchEvent()
  Activity.onTouchEvent()


ViewGroup.onInterceptTouchEvent() 를 시작으로,
  --> View.onTouch()
  --> View.onTouchEvent()
  --> ViewGroup.onTouch()
  --> ViewGroup.onTouchEvent()
의 순서대로 event 가 전달된다고 할 수 있다.

Event 는 Action_Down 이 어디에서 return 이 되었느냐에 따라 그 이후의 event 인 Action_Move, Action_Up 을 전달 해 준다. 이것은 [ref. 1] 의 그림을 참조하는 것이 좋을 듯 하다.

ViewGroup.OnInterceptTouchEvent() 의 의미만 명확히 해 두면 나머지는 파악하기 쉬울 듯 하다.

ViewGroup.OnInterceptTouchEvent() 를 return true 로 한다는 뜻은 Action_Down event 를 intercepted 했다는 이야기이다. 하지만 이 intercept 가 ViewGoup 의 child 인 다른 ViewGroup 이나 View 에게로 Action_Down event 를 주지 않는다는 이야기이지, 자신(ViewGroup) 의 onTouchEvent() 나 onTouch() 로 주지 않겠다는 이야기는 아니다.

그러므로 ViewGroup.onInterceptTouchEvent() 에서 return true 가 되면, View.diapatchTouchEvent() 를 거치지는 않지만, 그 다음 routine 인 ViewGroup.onTouch() 나 ViewGroup.onTouchEvent() 는 실행이 된다.

ViewGroup.onInterceptTouchEvent() 는 ref. 6 의 source code 주석에 적힌 것을 보면, event 에 대해 view group 이 어떻게 반응할지에 대해 결정만 하게 된다. 그래서 return true 는 ViewGroup 에서 event 를 사용하겠다. 라고 이야기 하는 것이고, 이 event 를 처리할 ViewGroup.onTouchEvent() 를 부른겠다는 이야기인 듯 하다.

그러니까, ViewGroup 에서 touch event 를 처리하겠다. 이 event 는 ViewGroup.onTouchEvent() 에서 처리하겠다. 라고 알려주는 역할을 한다. 고 보면 될 듯 하다.

ViewGroup 에서 drag 처리를 하려고 한다면 ref. 6 의 code 를 참고하자.


여하튼 내가 정리해도, 정리가 잘 안되는 느낌이다. ^^;;; 그래서 다른 책(Pro Android 4)의 내용을 참고해서 얘기를 해준다면, event 를 사용하고 다른 view 나 child 에게 넘기지 않으려 한다면 return true 를 하고, 다른 view 나 child 에서 사용을 해야 한다면 return false 를 하라고 한다.

쉽게 얘기하자면, 내가 event 를 intercept 한다는 true 이고, false 는 intercept 를 하지 않는다. 라는 의미이다.

Android Developer 사이트의  에서 잘 설명하는 듯 하다. 간단한 예제도 나와있다. ViewGroup 에서 touch event 를 사용해야 하는 경우와 ViewGroup 의 child view 가 touch event 를 사용해야 하는 경우를 예를 들어 이야기 해 준다.

onTouch() vs onTouchEvent()

두 개는 사실상 비슷한 녀석으로 보인다. 굳이 나눠놓은 이유를 아직 잘 모르겠지만, 구현방식은 아래와 같다.

개인적인 추측은 OnTouchListener 를 실행한후 onTouchEvent() 를 처리하지 않고 return 하도록 한 것으로 보아, OnTouchListener() 로 간단하게 Touch 에 대한 event 처리를 할 수 있도록 디자인 한 것으로 보인다. 굳이 View 를 extends 해서 override 하는 수고러움을 없애주기 위해서 말이다.

onTouch()

onTouch() 는 setOnTouchListener() 등을 통해서 View 에 binding 시켜주면 되고,

listView.setOnTouchListener(new OnTouchListener(){
      
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        android.util.Log.d("test", "onTouch");
        return false;
      }
      
    });



onTouchEvent()

onTouchEvent() 는 View 를 상속한 class 에서 onTouchEvent() 함수를 override 해주면 된다.

public class ViewFlow extends AdapterView<Adapter>{
  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    ...

    switch (action) {
    case MotionEvent.ACTION_DOWN:
      ...
      break;

    case MotionEvent.ACTION_MOVE:
      f...
      break;

    }
    return true;
  }
}



References

  1. 안드로이드의 Touch Event 디스패치 단계
  2. http://doandroids.com/blogs/tag/codeexample/
  3. http://stackoverflow.com/questions/9586032/android-difference-between-onintercepttouchevent-and-dispatchtouchevent
  4. http://stackoverflow.com/questions/7449799/how-are-android-touch-events-delivered
  5. http://www.androidadb.com/class/android/view/View/OnTouchListener.java.html
  6. http://developer.android.com/training/gestures/viewgroup.html

댓글 9개:

  1. 와우 좋은 글 감사히 퍼가겠습니다~ 출처는 당연 남길게욤

    답글삭제
    답글
    1. ^^ 좋은 글이라니 감사합니다.

      삭제
  2. 좋은글 감사합니다!
    헌데 궁금한게
    저렇게 소스를 분석하시려면 어떤걸로 하시는건가요?
    안드로이드 sdk를 타고들어가면 abstract 메소드로 감춰진애들이 있는데, 이걸 어떻게 보시는지 궁금해요

    답글삭제
    답글
    1. 아..abstract method 는 감춰진 것이라기보다는 원래 interface 와 비슷한 녀석이라 실제 구현체(implementation)가 따로 있습니다. abstract method 를 extends 받아서 실제로 만들어 놓은 class 들이 있죠. 얘기하시는 바는 이런 class 들중 어느 class 가 이용되는지를 어떻게 아시는가 물어보시는 것 같은데 간단합니다.
      debugger 를 사용하면 됩니다. ^^;;;;;
      소스 분석은 될 수 있으면 debugger 로 합니다. debugger 는 언제나 최고의 분석툴입니다. ㅋㅋ debugger 를 못쓰는 경우에 실제로 code 로만 분석을 합니다. oop 같은 녀석들은 상속관계가 복잡해지고 이러면 코드만으로는 힘들거든요.

      삭제
  3. 블로그 관리자가 댓글을 삭제했습니다.

    답글삭제
    답글
    1. 죄송합니다. 제가 실수로 지워버렸습니다. 좋은 말 써주셨는데 ㅜㅜ
      여하튼 저도 감사합니다.

      삭제
  4. 감탄했습니다.
    일단 베껴서 찬찬히 공부해야겠습니다.
    보고 나서 어려운 점 있으면 질문 올릴까 합니다.
    감사합니다.

    답글삭제
    답글
    1. 감탄씩이나..^^;;; 여하튼 글을 봐주셔서 감사합니다. 그럼 즐거운 코딩하시길 ...

      삭제
  5. 작성자가 댓글을 삭제했습니다.

    답글삭제