Android 는 소스 코드 의 측면 에서 이벤트 배포 메커니즘 의 해석 을 철저히 이해 합 니 다(하)

앞의 글 에서 저 는 여러분 을 데 리 고 소스 코드 의 측면 에서 안 드 로 이 드 에서 View 의 사건 배포 체 제 를 분 석 했 습 니 다.읽 은 친구 들 이 View 의 사건 배포 에 대해 비교적 깊이 이해 했다 고 믿 습 니 다.
아직 읽 지 못 한 친 구 는 먼저 참고 하 세 요Android 는 소스 코드 의 측면 에서 사건 배포 메커니즘 의 해석 을 철저히 이해 합 니 다.
그러면 오늘 우 리 는 지난번 에 완성 되 지 않 은 화 제 를 계속 하고 소스 코드 의 측면 에서 View Group 의 사건 을 분석 하여 배포 할 것 이다.
우선 뷰 그룹 이 무엇 인지 논의 해 볼 까요?그것 은 일반적인 View 와 어떤 차이 가 있 습 니까?
말 그대로 ViewGroup 은 View 의 집합 으로 하위 View 와 하위 Vew Group 이 많이 포함 되 어 있 으 며 Android 의 모든 레이아웃 의 부모 클래스 나 간접 부모 클래스 로 LinearLayout,RelativeLayout 등 은 ViewGroup 에서 계승 되 었 다.그러나 ViewGroup 은 실제 적 으로 View 이기 도 합 니 다.다만 View 보다 하위 View 와 레이아웃 매개 변 수 를 정의 할 수 있 는 기능 이 많 습 니 다.ViewGroup 계승 구조 설명 도 는 다음 과 같다.
 
우리 가 평소에 프로젝트 에서 자주 사용 하 는 여러 가지 구 조 는 모두 View Group 의 하위 클래스 에 속 하 는 것 을 볼 수 있다.
뷰 그룹 에 대한 간단 한 소 개 를 마 쳤 습 니 다.안 드 로 이 드 에서 Vew Group 의 이벤트 배포 절 차 를 데모 로 보 여 드 리 겠 습 니 다.
먼저 저 희 는 하나의 레이아웃 을 정의 하고 MyLayout 라 고 명명 하 며 LinearLayout 에서 계승 합 니 다.다음 과 같 습 니 다.

public class MyLayout extends LinearLayout { 
 public MyLayout(Context context, AttributeSet attrs) { 
 super(context, attrs); 
 } 
} 
그리고 메 인 레이아웃 파일 activity 열기main.xml,사용자 정의 레이아웃 을 추가 합 니 다:

<com.example.viewgrouptouchevent.MyLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 xmlns:tools="http://schemas.android.com/tools" 
 android:id="@+id/my_layout" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 android:orientation="vertical" > 
 <Button 
 android:id="@+id/button1" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 android:text="Button1" /> 
 <Button 
 android:id="@+id/button2" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 android:text="Button2" /> 
 
</com.example.viewgrouptouchevent.MyLayout> 
MyLayout 에 두 개의 단 추 를 추 가 했 습 니 다.이 어 MainActivity 에서 이 두 개의 단추 와 MyLayout 에 감청 이 벤트 를 등록 하 였 습 니 다.

myLayout.setOnTouchListener(new OnTouchListener() { 
 @Override 
 public boolean onTouch(View v, MotionEvent event) { 
 Log.d("TAG", "myLayout on touch"); 
 return false; 
 } 
}); 
button1.setOnClickListener(new OnClickListener() { 
 @Override 
 public void onClick(View v) { 
 Log.d("TAG", "You clicked button1"); 
 } 
}); 
button2.setOnClickListener(new OnClickListener() { 
 @Override 
 public void onClick(View v) { 
 Log.d("TAG", "You clicked button2"); 
 } 
}); 
우 리 는 MyLayout 의 onTouch 방법 과 Button 1,Button 2 의 onClick 방법 에서 한 마디 를 인쇄 했다.현재 항목 을 실행 합 니 다.효과 도 는 다음 과 같 습 니 다.
 
각각 Button 1,Button 2 와 공백 구역 을 클릭 하고 인쇄 결 과 는 다음 과 같 습 니 다.
 
버튼 을 눌 렀 을 때 MyLayout 에 등 록 된 onTouch 방법 은 실행 되 지 않 고 빈 영역 을 눌 렀 을 때 만 실행 되 는 것 을 발견 할 수 있 습 니 다.Button 의 onClick 방법 으로 사건 을 소비 하 는 것 으로 이해 할 수 있 기 때문에 사건 은 더 이상 아래로 전달 되 지 않 습 니 다.
그러면 안 드 로 이 드 의 터치 이 벤트 는 뷰 에 먼저 전달 되 고 뷰 그룹 에 전달 되 는 것 입 니까?지금 결론 을 내리 기 에는 아직 이르다.우리 다시 실험 을 하 자. 
문 서 를 보면 ViewGroup 에 onInterceptTouchEvent 방법 이 있 습 니 다.이 방법의 소스 코드 를 살 펴 보 겠 습 니 다.

/** 
 * Implement this method to intercept all touch screen motion events. This 
 * allows you to watch events as they are dispatched to your children, and 
 * take ownership of the current gesture at any point. 
 * 
 * <p>Using this function takes some care, as it has a fairly complicated 
 * interaction with {@link View#onTouchEvent(MotionEvent) 
 * View.onTouchEvent(MotionEvent)}, and using it requires implementing 
 * that method as well as this one in the correct way. Events will be 
 * received in the following order: 
 * 
 * <ol> 
 * <li> You will receive the down event here. 
 * <li> The down event will be handled either by a child of this view 
 * group, or given to your own onTouchEvent() method to handle; this means 
 * you should implement onTouchEvent() to return true, so you will 
 * continue to see the rest of the gesture (instead of looking for 
 * a parent view to handle it). Also, by returning true from 
 * onTouchEvent(), you will not receive any following 
 * events in onInterceptTouchEvent() and all touch processing must 
 * happen in onTouchEvent() like normal. 
 * <li> For as long as you return false from this function, each following 
 * event (up to and including the final up) will be delivered first here 
 * and then to the target's onTouchEvent(). 
 * <li> If you return true from here, you will not receive any 
 * following events: the target view will receive the same event but 
 * with the action {@link MotionEvent#ACTION_CANCEL}, and all further 
 * events will be delivered to your onTouchEvent() method and no longer 
 * appear here. 
 * </ol> 
 * 
 * @param ev The motion event being dispatched down the hierarchy. 
 * @return Return true to steal motion events from the children and have 
 * them dispatched to this ViewGroup through onTouchEvent(). 
 * The current target will receive an ACTION_CANCEL event, and no further 
 * messages will be delivered here. 
 */ 
public boolean onInterceptTouchEvent(MotionEvent ev) { 
 return false; 
} 
만약 원본 코드 를 보지 않 는 다 면 너 는 정말 이 주석 에 놀 랐 을 것 이다.이렇게 긴 영어 주석 을 보 니 머리 가 크다.그런데 소스 코드 가 이렇게 간단 하 다 니!코드 가 한 줄 밖 에 없어 서 false 를 되 돌려 주 었 습 니 다!
좋 습 니 다.불 형의 귀환 이 라면 두 가지 가능성 만 있 습 니 다.우 리 는 MyLayout 에서 이 방법 을 다시 쓴 다음 에 true 로 돌아 가 보 겠 습 니 다.코드 는 다음 과 같 습 니 다.

public class MyLayout extends LinearLayout { 
 public MyLayout(Context context, AttributeSet attrs) { 
 super(context, attrs); 
 } 
 @Override 
 public boolean onInterceptTouchEvent(MotionEvent ev) { 
 return true; 
 } 
 
} 
현재 항목 을 다시 실행 한 다음 에 각각 Button 1,Button 2 와 공백 구역 을 각각 실행 합 니 다.인쇄 결 과 는 다음 과 같 습 니 다.
 
당신 은 어디 를 클릭 하 든 MyLayout 의 touch 사건 만 촉발 할 수 있 습 니 다.버튼 의 클릭 사건 이 완전히 차단 되 었 습 니 다!왜 그 럴 까요?Android 의 touch 이벤트 가 View 에 먼저 전달 되 고 View Group 에 전달 된다 면 MyLayout 는 Button 의 클릭 이 벤트 를 어떻게 차단 할 수 있 습 니까? 
원본 코드 를 읽 고 Android 에서 View Group 의 사건 배포 체 제 를 파악 해 야 우리 마음속 의 의혹 을 해결 할 수 있 을 것 같 습 니 다.하지만 여기 서 먼저 말씀 드 리 고 싶 습 니 다.Android 에서 touch 사건 의 전달 은 View Group 에 먼저 전달 되 고 View 에 전달 되 는 것 입 니 다.Android 는 소스 코드 의 측면 에서 사건 배포 메커니즘 의 해석 을 철저히 이해 합 니 다.에서 제 가 설명 한 적 이 있 습 니 다.모든 컨트롤 을 만 지면 이 컨트롤 의 dispatch Touch Event 방법 을 반드시 호출 할 것 입 니 다.이 견 해 는 틀림없다.단지 아직 완전 하지 않 을 뿐이다.실제 상황 은 컨트롤 을 클릭 하면 먼저 이 컨트롤 이 있 는 레이아웃 의 dispatchTouchEvent 방법 을 호출 한 다음 에 레이아웃 의 dispatchTouchEvent 방법 에서 클릭 된 해당 컨트롤 을 찾 은 다음 에 이 컨트롤 의 dispatchTouchEvent 방법 을 호출 합 니 다.만약 우리 가 MyLayout 의 단 추 를 눌 렀 다 면,먼저 MyLayout 의 dispatch TouchEvent 방법 을 호출 할 것 입 니 다.그러나 MyLayout 에는 이 방법 이 없다 는 것 을 알 게 될 것 입 니 다.아버지 류 인 리 니 어 라 이 엇 에서 찾 아 보 니 방법 이 없 었 다.LinearLayout 의 부모 클래스 인 View Group 을 계속 찾 을 수 밖 에 없습니다.드디어 View Group 에서 이 방법 을 보 았 습 니 다.버튼 의 dispatch Touch Event 방법 은 바로 여기 서 호출 된 것 입 니 다.수 정 된 설명도 는 다음 과 같다.
   
그럼 뭘 더 기 다 려?View Group 의 dispatch TouchEvent 방법의 소스 코드 를 보 세 요!코드 는 다음 과 같다.

public boolean dispatchTouchEvent(MotionEvent ev) { 
 final int action = ev.getAction(); 
 final float xf = ev.getX(); 
 final float yf = ev.getY(); 
 final float scrolledXFloat = xf + mScrollX; 
 final float scrolledYFloat = yf + mScrollY; 
 final Rect frame = mTempRect; 
 boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 
 if (action == MotionEvent.ACTION_DOWN) { 
 if (mMotionTarget != null) { 
  mMotionTarget = null; 
 } 
 if (disallowIntercept || !onInterceptTouchEvent(ev)) { 
  ev.setAction(MotionEvent.ACTION_DOWN); 
  final int scrolledXInt = (int) scrolledXFloat; 
  final int scrolledYInt = (int) scrolledYFloat; 
  final View[] children = mChildren; 
  final int count = mChildrenCount; 
  for (int i = count - 1; i >= 0; i--) { 
  final View child = children[i]; 
  if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE 
   || child.getAnimation() != null) { 
   child.getHitRect(frame); 
   if (frame.contains(scrolledXInt, scrolledYInt)) { 
   final float xc = scrolledXFloat - child.mLeft; 
   final float yc = scrolledYFloat - child.mTop; 
   ev.setLocation(xc, yc); 
   child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
   if (child.dispatchTouchEvent(ev)) { 
    mMotionTarget = child; 
    return true; 
   } 
   } 
  } 
  } 
 } 
 } 
 boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || 
  (action == MotionEvent.ACTION_CANCEL); 
 if (isUpOrCancel) { 
 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; 
 } 
 final View target = mMotionTarget; 
 if (target == null) { 
 ev.setLocation(xf, yf); 
 if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { 
  ev.setAction(MotionEvent.ACTION_CANCEL); 
  mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
 } 
 return super.dispatchTouchEvent(ev); 
 } 
 if (!disallowIntercept && onInterceptTouchEvent(ev)) { 
 final float xc = scrolledXFloat - (float) target.mLeft; 
 final float yc = scrolledYFloat - (float) target.mTop; 
 mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
 ev.setAction(MotionEvent.ACTION_CANCEL); 
 ev.setLocation(xc, yc); 
 if (!target.dispatchTouchEvent(ev)) { 
 } 
 mMotionTarget = null; 
 return true; 
 } 
 if (isUpOrCancel) { 
 mMotionTarget = null; 
 } 
 final float xc = scrolledXFloat - (float) target.mLeft; 
 final float yc = scrolledYFloat - (float) target.mTop; 
 ev.setLocation(xc, yc); 
 if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { 
 ev.setAction(MotionEvent.ACTION_CANCEL); 
 target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; 
 mMotionTarget = null; 
 } 
 return target.dispatchTouchEvent(ev); 
} 
이 방법 은 코드 가 비교적 길 어서 우 리 는 중점 만 골 라 본다.우선 13 줄 에서 조건 판단 을 볼 수 있 습 니 다.disallow Intercept 과!onIntercept TouchEvent(ev)둘 중 하 나 는 true 이 고 이 조건 판단 에 들 어 갑 니 다.disallow Intercept 은 이벤트 차단 기능 을 사용 하지 않 을 지 여부 입 니 다.기본 값 은 false 이 며,requestDisallow Intercept TouchEvent 방법 을 호출 하여 이 값 을 수정 할 수 있 습 니 다.그러면 첫 번 째 값 이 false 일 때 두 번 째 값 에 전적으로 의존 하여 조건 판단 의 내부 에 들 어 갈 수 있 는 지,두 번 째 값 은 무엇 입 니까?onInterceptTouchEvent 방법 에 대한 반환 값 을 반대 하 다 니!즉,우리 가 onIntercept TouchEvent 방법 에서 false 를 되 돌려 주면 두 번 째 값 을 true 로 하여 금 조건 판단 의 내부 로 들 어가 게 하고,만약 우리 가 onIntercept TouchEvent 방법 에서 true 로 되 돌아 가면 두 번 째 값 을 false 로 하여 금 이 판단 에서 벗 어 나 게 할 것 이다.
이 럴 때 생각해 보 세 요.우리 가 방금 MyLayout 에서 onIntercept TouchEvent 방법 을 다시 썼 기 때문에 이 방법 을 true 로 되 돌려 주 고 모든 버튼 의 클릭 사건 이 차단 되 었 습 니 다.그러면 우 리 는 버튼 을 누 르 면 사건 의 처리 가 바로 제1 3 행 조건 판단 내부 에서 이 루어 진 것 이 라 고 믿 을 이유 가 있 습 니 다!
그럼 조건 판단 의 내부 가 어떻게 이 루어 졌 는 지 에 중점 을 두 자.19 번 째 줄 에서 하나의 for 순환 을 통 해 현재 View Group 의 모든 하위 View 를 옮 겨 다 니 고 24 번 째 줄 에서 현재 옮 겨 다 니 는 View 가 클릭 하고 있 는 View 인지 아 닌 지 를 판단 합 니 다.그렇다면 이 조건 이 판단 하 는 내부 로 들 어가 29 번 째 줄 에서 이 View 의 dispatch Touch Event 를 호출 했 습 니 다.그 후의 절 차 는Android 는 소스 코드 의 측면 에서 사건 배포 메커니즘 의 해석 을 철저히 이해 합 니 다.에서 설명 한 것 과 같 습 니 다.버튼 을 눌 러 사건 을 처리 하 는 것 이 여기 서 이 루어 진 것 이 확실 하 다 는 것 도 확인 됐다.
그리고 하위 View 의 dispatchTouchEvent 를 호출 하면 반환 값 이 있 습 니 다.하나의 컨트롤 이 클릭 할 수 있다 면 이 컨트롤 을 클릭 할 때 dispatchTouchEvent 의 반환 값 은 반드시 true 라 는 것 을 알 고 있 습 니 다.따라서 29 번 째 줄 의 조건 판단 이 성립 될 수 있 기 때문에 31 번 째 줄 에서 View Group 의 dispatch Touch Event 방법 을 트 루 로 되 돌려 주 었 다.이 로 인해 뒤의 코드 가 실행 되 지 못 하 게 되 었 고 우리 앞의 Demo 가 인쇄 한 결과 이기 도 합 니 다.버튼 의 클릭 이벤트 가 실행 되면 MyLayout 의 touch 이 벤트 를 차단 합 니 다.
그럼 우리 가 누 른 것 이 버튼 이 아니 라 공백 구역 이 라면?이 경우 31 줄 에서 트 루 로 돌아 가지 않 고 뒤의 코드 를 계속 실행 할 것 이다.그러면 우 리 는 계속 뒤 를 돌아 보 겠 습 니 다.44 번 째 줄 에서 target 이 null 과 같 으 면 이 조건 으로 내부 판단 에 들 어 갑 니 다.여 기 는 일반적인 상황 에서 target 이 null 이기 때문에 50 번 째 줄 에서 슈퍼 dispatchTouchEvent(ev)를 호출 합 니 다.이 코드 는 어디로 호출 됩 니까?당연히 View 의 dispatchTouchEvent 방법 입 니 다.View Group 의 부 류 는 View 이기 때 문 입 니 다.이후 의 처리 논 리 는 앞에서 말 한 것 과 같 기 때문에 MyLayout 에 등 록 된 onTouch 방법 이 실 행 됩 니 다.그 후의 코드 는 일반적인 상황 에서 갈 수 없 기 때문에 우 리 는 더 이상 분석 하지 않 을 것 이다.
전체 View Group 이벤트 배포 과정의 흐름 도 를 다시 한 번 살 펴 보 세 요.여러분 들 이 더 잘 이해 할 수 있 을 거 라 고 믿 습 니 다.
 
이제 전체 View Group 의 사건 배포 절차 에 대한 분석 도 여기 서 끝 났 습 니 다.마지막 으로 간단하게 정리 하 겠 습 니 다. 
1.안 드 로 이 드 이벤트 배 포 는 뷰 그룹 에 먼저 전달 되 고 뷰 그룹 에서 뷰 로 전 달 됩 니 다.
2.ViewGroup 에서 onInterceptTouchEvent 방법 을 통 해 사건 전달 을 차단 할 수 있 습 니 다.onInterceptTouchEvent 방법 은 true 대표 에 게 사건 이 계속 하위 View 에 전달 되 는 것 을 허락 하지 않 습 니 다.false 대표 로 돌아 가 사건 을 차단 하지 않 고 기본적으로 false 로 돌아 갑 니 다.
3.서브 뷰 에서 전 달 된 이 벤트 를 소비 하면 ViewGroup 에 서 는 이 벤트 를 받 아들 일 수 없습니다. 
자,안 드 로 이 드 사건 배포 체 제 는 여기까지 완전히 해석 되 었 습 니 다.상하 두 편 과 결합 하여 여러분 들 이 사건 배포 에 대한 이해 가 매우 깊 어 졌 다 고 믿 습 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기