Android 이벤트 의 배포,차단,실행 에 대한 자세 한 설명

일반적인 개발 에서 우 리 는 클릭,미끄럼 같은 사건 을 자주 만난다.때때로 서로 다른 view 사이 에 도 각종 미끄럼 충돌 이 존재 한다.예 를 들 어 구조의 안팎 두 층 이 모두 미 끄 러 지면 충돌 이 생 길 수 있다.이 럴 때 우 리 는 안 드 로 이 드 의 이벤트 배포 체 제 를 알 아야 한다.
Android 의 터치 이벤트 배포 과정 은 세 가지 중요 한 방법 으로 공동으로 이 루어 집 니 다.dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent.나 는 먼저 이 세 가지 방법 을 대체적으로 소개 하 겠 다.
 •public boolean dispatchTouchEvent(MotionEvent ev) 
사건 의 배 포 를 진행 하 다.이벤트 가 현재 View 에 전 달 될 수 있다 면 이 방법 은 반드시 호출 될 것 입 니 다.결 과 는 현재 View 의 onTouchEvent 와 하급 View 의 dispatchTouchEvent 방법의 영향 을 받 아 현재 이 벤트 를 소모 하 는 지 여 부 를 나 타 냅 니 다.ACTION_DOWN 의 dispatchTouchEvent()복귀 true,후속 이벤트(ACTIONMOVE、ACTION_UP)는 다시 전 달 됩 니 다.false 로 돌아 가면 dispatchTouchEvent()는 ACTION 을 받 지 못 합 니 다.UP、ACTION_MOVE。쉽게 말 하면 dispatchTouchEvent 가 이벤트 배 포 를 진행 할 때 이전 action 이 true 로 돌아 와 야 다음 action 을 촉발 할 수 있다 는 것 이다.
 •public boolean onInterceptTouchEvent(MotionEvent event) 
이 방법 은 dispatchTouchEvent 방법 에서 호출 되 어 어떤 사건 을 차단 하 는 데 사 용 됩 니 다.현재 View 가 어떤 사건 을 차단 했다 면 같은 사건 시퀀스 에서 이 방법 은 다시 호출 되 지 않 고 돌아 온 결 과 는 현재 사건 을 차단 할 지 여 부 를 표시 합 니 다.이것 은 ViewGroup 이 제공 하 는 방법 입 니 다.기본적으로 false 로 돌아 갑 니 다.
 •public boolean onTouchEvent(MotionEvent event) 
dispatchTouchEvent 방법 에서 호출 되 었 습 니 다.클릭 이 벤트 를 처리 하 는 데 사 용 됩 니 다.결 과 를 되 돌려 주 는 것 은 현재 이벤트 가 소모 되 었 는 지 여부 입 니 다(true 는 소 모 를 표시 하고 false 는 소모 되 지 않 음 을 표시 합 니 다).소모 되 지 않 으 면 같은 이벤트 시퀀스 에서 현재 View 는 이 벤트 를 다시 받 을 수 없습니다.View 와 View Group 은 모두 이 방법 을 가지 고 있 습 니 다.View 는 기본적으로 true 로 돌아 가 이 사건 을 소 비 했 음 을 표시 합 니 다.
View 에 두 개의 반전 함수 가 있 습 니 다.
public boolean dispatchTouchEvent(MotionEvent ev);   
public boolean onTouchEvent(MotionEvent ev);
ViewGroup 에 세 개의 반전 함수 가 있 습 니 다.
public boolean dispatchTouchEvent(MotionEvent ev);   
public boolean onInterceptTouchEvent(MotionEvent ev);   
public boolean onTouchEvent(MotionEvent ev);
상술 한 세 가지 방법 중 어떤 차이 와 관계 가 있 습 니까?다음은 의사 코드 로 표시 합 니 다.

public boolean dispatchTouchEvent(MotionEvent ev) { 
 boolean consume = false; 
 if(onInterceptTouchEvent(ev)){ 
  consume = onTouchEvent(ev); 
 } else { 
  consume = child.dispatchTouchEvent(ev); 
 } 
 return consume; 
} 

 위의 위조 코드 를 통 해 여러분 은 클릭 이벤트 의 전달 규칙 에 대해 더욱 명확 한 인식 을 가지 게 될 것 입 니 다.즉,하나의 ViewGroup 에 있어 클릭 이벤트 가 발생 한 후에 먼저 전달 할 것 입 니 다.이때 dispatchTouchEvent 는 호출 될 것 입 니 다.만약 에 이 ViewGroup 의 onInterceptTouchEvent 방법 이 true 로 돌아 가면 이 사건 을 차단 하 겠 다 고 표시 합 니 다.이 어 이 사건 은 이 View Group 에 맡 길 것 입 니 다.즉,onTouchEvent 방법 이 호출 될 것 입 니 다.이 View Group 의 onInterceptTouchEvent 방법 이 false 로 돌아 가면 이 사건 을 막 지 않 는 다 는 뜻 입 니 다.이것 은 현재 이벤트 가 하위 요소 에 계속 전 달 될 것 입 니 다.이 어 하위 요소 의 dispatchTouchEvent 방법 이 호출 될 것 입 니 다.이 는 이벤트 가 최종 처 리 될 때 까지 반복 합 니 다.
아래 의 몇 장의 그림 은[eoe]에서 참고 한다.
 •그림 1:ACTION다운 이 가 소 비 를 안 당 했 어 요.
 
•그림 2(1):ACTION다운 이 뷰 에 소비 됐어 요.

•그림 2(2):후속 ACTIONMOVE 와 UP 은 차단 되 지 않 은 상태 에서 모두 VIEW 를 찾 아 갑 니 다.

•그림 3:후속 적 인 차단

•그림 4:ACTIONDOWN 은 처음부터 차단 당 했 어 요.

View 이벤트 배포 소스 분석:
 •dispatchTouchEvent 방법: 

public boolean dispatchTouchEvent(MotionEvent event) { 
 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { 
  return true; 
 } 
 return onTouchEvent(event); 
}

 mOnTouchListener!=null,(mViewFlags&ENABLED_MASK)==ENABLED 와 mOnTouch Listener.onTouch(this,event)라 는 세 가지 조건 이 모두 사실 이면 true 로 돌아 가 고,그렇지 않 으 면 onTouch Event(event)방법 을 실행 하고 돌아 갑 니 다.
결론 적 으로 onTouch 가 실 행 될 수 있 는 데 는 두 가지 전제 조건 이 필요 합 니 다(모두 만족 합 니 다).
 1.OnTouch Listener 설정
 2.컨트롤 은 enable 상태
 한편,onTouchEvent 는 다음 과 같은 세 가지 조건 중 하 나 를 만족 시 키 면 됩 니 다.
 1.OnTouchListener 가 설정 되 어 있 지 않 음
 2.컨트롤 은 enable 상태 가 아 닙 니 다.
 3.onTouch 반환 false
 다시 한 번 dispatchTouchEvent 의 반환 값 을 살 펴 보 겠 습 니 다.이 는 onTouch 와 onTouchEvent 함수 의 반환 값 에 의 해 제어 되 었 습 니 다.즉,touch 사건 이 성공 적 으로 소비 되 어 true 로 돌아 가면 트 루 로 돌아 가 배포 에 성공 했다 는 것 을 설명 합 니 다.그 후에 사건 서열 도 여기 서 배 포 될 것 입 니 다.만약 에 false 로 돌아 가면 배포 에 실패 했다 고 생각 하고 그 후의 사건 서열 은 더 이상 배포 되 지 않 습 니 다.
 •onTouchEvent 방법:

 if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
  ...
  return true;
 }
View 의 onTouch Event 는 기본적으로 이벤트(이 방법 은 true 로 되 돌아 갑 니 다)를 소모 합 니 다.클릭 할 수 없 는 것 이 아니라면(clickable 과 longClickable 이 동시에 false).또한 View 의 longClickable 기본 값 은 false 이 고 clickable 속성 은 상황 에 따라 달라 집 니 다.예 를 들 어 Button 기본 값 은 true 이 고 TextView,ImageView 기본 값 은 false 입 니 다.

public boolean performClick() { 
 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 
 if (mOnClickListener != null) { 
  playSoundEffect(SoundEffectConstants.CLICK); 
  mOnClickListener.onClick(this); 
  return true; 
 } 
 return false; 
}

 이것 이 바로 우리 가 잘 아 는 OnClickListener 가 아 닙 니까?그것 은 원래 onTouchEvent 에서 호출 되 었 습 니 다.mOnClickListener 가 null 이 아니라면 onClick 방법 을 호출 합 니 다.
요약 하면 onClick 이 실 행 될 수 있 는 데 는 두 가지 전제 조건 이 필요 합 니 다(모두 만족 합 니 다).
 1.onTouchEvent 까지 실행 가능
 2.OnClickListener 설정
 전체 View 의 이벤트 전송 절 차 는:
dispatchEvent->setOnTouchListener->onTouchEvent->setOnClickListener
마지막 으로 질문 이 하나 더 있 습 니 다.setOnLongClickListener 와 setOnClickListener 는 하나만 실행 할 수 있 습 니까?
답:아 닙 니 다.setOnLongClickListener 의 onClick 이 false 로 돌아 가면 둘 다 실 행 됩 니 다.트 루 로 돌아 가면 setOnClickListener 를 차단 합 니 다.
ViewGroup 이벤트 배포 소스 분석:
 •dispatchTouchEvent 방법:

 ...
   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)) { 
        // Event handled, we have a target now. 
        mMotionTarget = child; 
        return true; 
       } 
      } 
     } 
    } 
   }

 두 가지 가 if 코드 세그먼트 에 들 어 갈 수 있 습 니 다(즉,이벤트 가 하위 View 에 배 포 됩 니 다).
1.현재 차단 을 허용 하지 않 습 니 다.즉,disallowIntercept=true 입 니 다.
2.현재 차단 되 지 않 았 습 니 다.즉,onInterceptTouchEvent(ev)는 false 로 돌아 갑 니 다.
 주:disallow Intercept 은 이벤트 차단 기능 을 사용 하지 않 을 지 여부 입 니 다.기본 값 은 false 입 니 다.ViewGroup.requestDisallow Intercept TouchEvent(boolean)를 통 해 설정 할 수 있 습 니 다.onInterceptTouchEvent(ev)는 복사 할 수 있 습 니 다.
if 코드 세그먼트 에 들 어간 후 하나의 for 순환 을 통 해 현재 View Group 의 모든 하위 View 를 옮 겨 다 니 며 현재 옮 겨 다 니 는 View 가 클릭 하고 있 는 View 인지 판단 합 니 다.그렇다면 이 View 의 dispatchTouchEvent 를 호출 하여 View 의 이벤트 배포 절차 에 들 어 갑 니 다.child.dispatchTouchEvent(ev)가 true 로 돌아 오 면 mMotionTarget=child 입 니 다.그리고 return true 는 ViewGroup 의 dispatchTouchEvent 반환 값 이 childView 의 dispatchTouchEvent 반환 값 의 영향 을 받 아 하위 view 이벤트 가 성공 적 으로 배포 되 었 고 ViewGroup 의 이벤트 배포 가 성공 적 이 었 다 는 것 을 설명 합 니 다.그 다음 에 이벤트 시퀀스 도 여기 서 배 포 됩 니 다(위 에서 알 수 있 듯 이 하위 view 의 clickable 또는 longClickable 은 true 로 모두 배 포 될 수 있 습 니 다).한편,ViewGroup 이벤트 배포 에 실 패 했 거나 하위 View(빈 위 치 를 클릭)를 찾 지 못 하면 onTouchEvent 로 갑 니 다.이후 이벤트 시퀀스 도 배포 되 지 않 고 onTouchEvent 로 갑 니 다.
전체 View Group 의 이벤트 전송 절 차 는:
dispatchEvent->onInterceptTouchEvent->child.dispatchEvent->(setOnTouchListener->onTouchEvent)
위의 총 결 은 모두 다음 과 같다.만약 차단 하지 않 았 다 면.그럼 어떻게 차단 하나 요?
 •onInterceptTouchEvent

 public boolean onInterceptTouchEvent(MotionEvent ev) { 
 return false; 
}
 코드 는 간단 합 니 다.한 마디 만 false 로 돌아 갑 니 다.ViewGroup 은 기본적으로 차단 하지 않 습 니 다.차단 이 필요 하 다 면 return true 만 있 으 면 됩 니 다.그러면 이 사건 은 하위 View 에 전달 되 지 않 습 니 다.그리고 DOWN return true 에 있 으 면 DOWN,MOVE,UP 서브 View 는 사건 을 포착 하지 못 합 니 다.만약 당신 이 MOVE return true 에 있다 면,서브 뷰 는 MOVE 와 UP 에서 사건 을 포착 하지 못 할 것 입 니 다.
어떻게 차단 되 지 않 습 니까?
ViewGroup 의 onInterceptTouchEvent(ev)가 ACTIONMOVE 시 return true,즉 하위 View 의 MOVE 및 UP 사건 을 차단 합 니 다.이때 서브 뷰 가 MOVE 와 UP 에 호응 할 수 있 기 를 바 랄 때 어떻게 해 야 하나 요?
답:onInterceptTouchEvent 는 ViewGroup 에 정 의 된 것 으로 하위 View 는 수정 할 수 없습니다.Android 는 우리 에 게 차단 을 허용 하 는 지 여 부 를 설정 하 는 방법 을 제공 합 니 다.우 리 는 하위 View 의 dispatchTouchEvent 에 직접 이렇게 씁 니 다.

 @Override 
  public boolean dispatchTouchEvent(MotionEvent event) 
  { 
   getParent().requestDisallowInterceptTouchEvent(true); 
   int action = event.getAction();  
   switch (action) { 
   case MotionEvent.ACTION_DOWN: 
    Log.e(TAG, "dispatchTouchEvent ACTION_DOWN"); 
    break; 
   case MotionEvent.ACTION_MOVE: 
    Log.e(TAG, "dispatchTouchEvent ACTION_MOVE"); 
    break; 
   case MotionEvent.ACTION_UP: 
    Log.e(TAG, "dispatchTouchEvent ACTION_UP"); 
    break;  
   default: 
    break; 
   } 
   return super.dispatchTouchEvent(event); 
  } 

 getParent().requestDisallowInterceptTouchEvent(true); 이렇게 하면 ViewGroup 이 MOVE 에서 return true 를 하 더 라 도 서브 뷰 는 MOVE 와 UP 사건 을 포착 할 수 있다.
주:ViewGroup 이 onInterceptTouchEvent(ev)ACTION 에 있다 면DOWN 안에 return true 가 있 습 니 다.그러면 자 뷰 는 어 쩔 수 없 이 사건 을 포착 합 니 다!
총결산
코드 프로 세 스에 대해 서 는 이미 정 리 했 습 니 다.
1.ViewGroup 이 이 사건 을 처리 할 수 있 는 View 를 찾 으 면 하위 View 에 직접 맡 기 고 자신의 onTouchEvent 는 촉발 되 지 않 습 니 다.
2.onInterceptTouchEvent(ev)방법 을 복사 하여 하위 View 의 이벤트(즉 return true)를 차단 하고 사건 을 자신 에 게 맡 기 면 자신 이 대응 하 는 onTouchEvent 방법 을 실행 할 수 있 습 니 다.
3.하위 뷰 는 getParent().requestDisallowInterceptTouchEvent(true)를 호출 할 수 있 습 니 다.ViewGroup 이 MOVE 나 UP 사건 을 차단 하 는 것 을 막 습 니 다.
자,그럼 실제 응용 에서 어떤 문 제 를 해결 할 수 있 습 니까?
예 를 들 어 ScrollView 에 EditText 를 끼 워 넣 었 습 니 다.EditText 에 텍스트 내용 이 너무 많 고 범 위 를 초과 할 때 위 에서 아래로 미끄러져 EditText 에 있 는 문 자 를 굴 리 려 고 했 지만 스크롤 하 는 것 이 ScrollView 라 는 것 을 알 게 되 었 습 니 다.이 때 우 리 는 EditText 의 onTouch 이 벤트 를 설정 합 니 다.onTouch 에서 ScrollView 가 내 이 벤트 를 차단 하지 못 하도록 설정 하고 마지막 으로 UP 에서 상 태 를 바 꿉 니 다.

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
  if ((view.getId() == R.id.tousuContentEditText && canVerticalScroll(tousuContentEditText))) {
   view.getParent().requestDisallowInterceptTouchEvent(true);
   if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
    view.getParent().requestDisallowInterceptTouchEvent(false);
   }
  }
  return false;
 }

private boolean canVerticalScroll(EditText editText) {
  int scrollY = editText.getScrollY();
  int scrollRange = editText.getLayout().getHeight();
  int scrollExtent = editText.getHeight() - editText.getCompoundPaddingTop() - editText.getCompoundPaddingBottom();
  int scrollDifference = scrollRange - scrollExtent;
  if (scrollDifference == 0) {
   return false;
  }
  return (scrollY > 0) || (scrollY < scrollDifference - 1);
 }

이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기