Android 에서 onTouch 이벤트 전달 메커니즘 상세 분석

onTach 소개
ontach 는 Android 시스템 의 전체 이벤트 메커니즘 의 기초 입 니 다.Android 의 다른 이벤트,예 를 들 어 onClick,onLongClick 등 은 모두 onTach 를 바탕 으로 합 니 다.
onTach 는 손가락 으로 눌 러 서 핸드폰 화면 을 떠 나 는 전체 과정 을 포함 하고 미시적 인 형식 에서 action 로 구체 적 으로 나타난다.down、action_move 와 actionup 등 과정.
onTach 두 가지 주요 정의 형식 은 다음 과 같다.
1.사용자 정의 컨트롤 에서 흔히 볼 수 있 는 재 작성onTouchEvent(MotionEvent ev)방법 이 있 습 니 다.개발 중 에 재 작성 한 onTouchEvent 방법 을 자주 볼 수 있다 면,
그리고 그 중 에는 서로 다른 미시적 표현(actiondown、action_move 와 actionup 등)상응하는 판단 을 내 려 논 리 를 집행 하고 서로 다른 불 값 을 되 돌려 줄 수 있다.
2.코드 에서 기 존 컨트롤 에 setOnTouchListener 모니터 를 직접 설정 합 니 다.모니터 의 onTouch 방법 을 다시 씁 니 다.onTouch 리 셋 함수 에는 view 와 MotionEvent 가 있 습 니 다.
onTouch 이벤트 전달 메커니즘
일반적으로 우리 가 사용 하 는 UI 컨트롤 은 공 통 된 부류 인 View 를 계승 하 는 것 으로 알려 져 있다.그래서 View 라 는 종 류 는 onTouch 사건 의 관련 처 리 를 관리 해 야 합 니 다.그러면 View 에서 Touch 와 관련 된 방법 을 찾 아 보 자.그 중 하 나 는 우리 의 주 의 를 끌 기 쉽다. dispatchTouchEvent(MotionEvent event) 방법 명 에 따 르 면 터치 사건 을 배포 하 는 것 을 책임 져 야 한 다 는 뜻 으로 소스 코드 를 제시 했다.

/**
 * Pass the touch screen motion event down to the target view, or this
 * view if it is the target.
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
 public boolean dispatchTouchEvent(MotionEvent event) {
 // If the event should be handled by accessibility focus first.
 if (event.isTargetAccessibilityFocus()) {
 // We don't have focus or no virtual descendant has it, do not handle the event.
 if (!isAccessibilityFocusedViewOrHost()) {
  return false;
 }
 // We have focus and got the event, then use normal event dispatch.
 event.setTargetAccessibilityFocus(false);
 }

 boolean result = false;

 if (mInputEventConsistencyVerifier != null) {
 mInputEventConsistencyVerifier.onTouchEvent(event, 0);
 }

 final int actionMasked = event.getActionMasked();
 if (actionMasked == MotionEvent.ACTION_DOWN) {
 // Defensive cleanup for new gesture
 stopNestedScroll();
 }

 if (onFilterTouchEventForSecurity(event)) {
 //noinspection SimplifiableIfStatement
 ListenerInfo li = mListenerInfo;
 if (li != null && li.mOnTouchListener != null
  && (mViewFlags & ENABLED_MASK) == ENABLED
  && li.mOnTouchListener.onTouch(this, event)) {
  result = true;
 }

 if (!result && onTouchEvent(event)) {
  result = true;
 }
 }

 if (!result && mInputEventConsistencyVerifier != null) {
 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
 }

 // Clean up after nested scrolls if this is the end of a gesture;
 // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
 // of the gesture.
 if (actionMasked == MotionEvent.ACTION_UP ||
  actionMasked == MotionEvent.ACTION_CANCEL ||
  (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
 stopNestedScroll();
 }

 return result;
}
소스 코드 가 좀 길 지만,우 리 는 모든 줄 을 다 볼 필요 가 없다.먼저 dispatchTouchEvent 의 반환 값 이 boolean 형식 이라는 것 을 알 게 되 었 습 니 다.주석 에 있 는 설명:@return True if the event was handled by the view, false otherwise.은 이 터치 이벤트 가 이 View 에 소비 되면 true 로 돌아 가 고 그렇지 않 으 면 false 로 돌아 가 는 것 입 니 다.방법 에 서 는 이 이벤트 가 초점 을 맞 췄 는 지 여 부 를 먼저 판단 하고 초점 을 받 지 못 하면 false 로 되 돌아 갑 니 다.그 다음 에 우 리 는if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event))이 부분 에 시선 을 돌 렸 습 니 다.여기 에는 li 라 는 부분 변수 가 있 는데 Listener Info 류 에 속 하고 mListener Info 부 를 통 해 얻 을 수 있 습 니 다.Listener Info 는 포장 류 일 뿐 이 고 그 안에 대량의 모니터 가 밀봉 되 어 있다.
View 클래스 에서 mListener Info 를 찾 으 면 다음 코드 를 볼 수 있 습 니 다.

ListenerInfo getListenerInfo() {
 if (mListenerInfo != null) {
 return mListenerInfo;
 }
 mListenerInfo = new ListenerInfo();
 return mListenerInfo;
}
따라서 우 리 는 mListener Info 가 비어 있 지 않다 는 것 을 알 수 있 습 니 다.그래서 li 도 비어 있 지 않 습 니 다.첫 번 째 판단 은 true 입 니 다.그리고 li.mOnTouch Listener 를 보 았 습 니 다.앞에서 Listener Info 는 모니터 의 패 키 징 류 라 고 말 했 기 때문에 우 리 는 mOnTouch Listener 를 추적 합 니 다.

/**
 * Register a callback to be invoked when a touch event is sent to this view.
 * @param l the touch listener to attach to this view
 */
public void setOnTouchListener(OnTouchListener l) {
 getListenerInfo().mOnTouchListener = l;
}
바로 위의 방법 을 통 해 mOnTouchListener 를 설정 한 것 입 니 다.위의 방법 은 모두 가 잘 알 고 있 을 것 이 라 고 생각 합 니 다.바로 우리 가 평소에 자주 사용 하 는 xxx.setOnTouchListener 입 니 다.자,이 를 통 해 OnTouchListener 를 설정 하면 두 번 째 판단 도 true 이 고 세 번 째 판단 은 이 View 가 enable 인지,기본 값 은 enable 인지 알 수 있 기 때문에 똑 같이 true 입 니 다.마지막 이 남 았 습 니 다.li.mOnTouchListener.onTouch(this, event) 두 번 째 판단 에서 모니터 의onTouch()방법 을 바 꾼 것 이 분명 합 니 다.onTouch()방법 이 true 로 돌아 가면 위의 네 가지 판단 이 모두 true 이 고dispatchTouchEvent()방법 은 true 로 돌아 가 며if (!result && onTouchEvent(event))이 판단 을 집행 하지 않 습 니 다.이 판단 에서 우 리 는 익숙 한 방법 을 보 았 다.onTouchEvent()그래서 onTouchEvent 를 실행 하려 면 위의 네 가지 판단 에서 최소한 false 가 있어 야 합 니 다.
그러면 우리 가onTouch()방법 에서 false 로 돌 아 왔 다 고 가정 하면 onTouch Event 를 순조롭게 실 행 했 습 니 다.그러면 onTouch Event 의 소스 코드 를 보 세 요.

/**
 * Implement this method to handle touch screen motion events.
 * <p>
 * If this method is used to detect click actions, it is recommended that
 * the actions be performed by implementing and calling
 * {@link #performClick()}. This will ensure consistent system behavior,
 * including:
 * <ul>
 * <li>obeying click sound preferences
 * <li>dispatching OnClickListener calls
 * <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
 * accessibility features are enabled
 * </ul>
 *
 * @param event The motion event.
 * @return True if the event was handled, false otherwise.
 */
public boolean onTouchEvent(MotionEvent event) {
 final float x = event.getX();
 final float y = event.getY();
 final int viewFlags = mViewFlags;
 final int action = event.getAction();

 if ((viewFlags & ENABLED_MASK) == DISABLED) {
 if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
  setPressed(false);
 }
 // A disabled view that is clickable still consumes the touch
 // events, it just doesn't respond to them.
 return (((viewFlags & CLICKABLE) == CLICKABLE
  || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
  || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
 }

 if (mTouchDelegate != null) {
 if (mTouchDelegate.onTouchEvent(event)) {
  return true;
 }
 }

 if (((viewFlags & CLICKABLE) == CLICKABLE ||
  (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
  (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
 switch (action) {
  case MotionEvent.ACTION_UP:
  boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
  if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
   // take focus if we don't have it already and we should in
   // touch mode.
   boolean focusTaken = false;
   if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
   focusTaken = requestFocus();
   }

   if (prepressed) {
   // The button is being released before we actually
   // showed it as pressed. Make it show the pressed
   // state now (before scheduling the click) to ensure
   // the user sees it.
   setPressed(true, x, y);
   }

   if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
   // This is a tap, so remove the longpress check
   removeLongPressCallback();

   // Only perform take click actions if we were in the pressed state
   if (!focusTaken) {
    // Use a Runnable and post this rather than calling
    // performClick directly. This lets other visual state
    // of the view update before click actions start.
    if (mPerformClick == null) {
    mPerformClick = new PerformClick();
    }
    if (!post(mPerformClick)) {
    performClick();
    }
   }
   }

   if (mUnsetPressedState == null) {
   mUnsetPressedState = new UnsetPressedState();
   }

   if (prepressed) {
   postDelayed(mUnsetPressedState,
    ViewConfiguration.getPressedStateDuration());
   } else if (!post(mUnsetPressedState)) {
   // If the post failed, unpress right now
   mUnsetPressedState.run();
   }

   removeTapCallback();
  }
  mIgnoreNextUpEvent = false;
  break;

  case MotionEvent.ACTION_DOWN:
  mHasPerformedLongPress = false;

  if (performButtonActionOnTouchDown(event)) {
   break;
  }

  // Walk up the hierarchy to determine if we're inside a scrolling container.
  boolean isInScrollingContainer = isInScrollingContainer();

  // For views inside a scrolling container, delay the pressed feedback for
  // a short period in case this is a scroll.
  if (isInScrollingContainer) {
   mPrivateFlags |= PFLAG_PREPRESSED;
   if (mPendingCheckForTap == null) {
   mPendingCheckForTap = new CheckForTap();
   }
   mPendingCheckForTap.x = event.getX();
   mPendingCheckForTap.y = event.getY();
   postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  } else {
   // Not inside a scrolling container, so show the feedback right away
   setPressed(true, x, y);
   checkForLongClick(0);
  }
  break;

  case MotionEvent.ACTION_CANCEL:
  setPressed(false);
  removeTapCallback();
  removeLongPressCallback();
  mInContextButtonPress = false;
  mHasPerformedLongPress = false;
  mIgnoreNextUpEvent = false;
  break;

  case MotionEvent.ACTION_MOVE:
  drawableHotspotChanged(x, y);

  // Be lenient about moving outside of buttons
  if (!pointInView(x, y, mTouchSlop)) {
   // Outside button
   removeTapCallback();
   if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
   // Remove any future long press/tap checks
   removeLongPressCallback();

   setPressed(false);
   }
  }
  break;
 }

 return true;
 }

 return false;
}
이 소스 코드 는 dispatch Touch Event 보다 더 길 지만 똑 같이 우리 가 중점 을 두 고 보 자.if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE)
이 말 을 보면 이 view 가 클릭 가능 한 지 여 부 를 판단 하 는 것 임 을 알 수 있 습 니 다.클릭 할 수 있 으 면 계속 실행 합 니 다.그렇지 않 으 면 false 로 돌아 갑 니 다.if 에서 switch 로 어떤 터치 사건 인지 판단 할 수 있 지만 마지막 에는 true 로 돌아 갑 니 다.
그리고 주의해 야 할 것 은:ACTION 에서UP 에서 실행performClick() 방법:

public boolean performClick() {
 final boolean result;
 final ListenerInfo li = mListenerInfo;
 if (li != null && li.mOnClickListener != null) {
 playSoundEffect(SoundEffectConstants.CLICK);
 li.mOnClickListener.onClick(this);
 result = true;
 } else {
 result = false;
 }

 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
 return result;
}
위의li.mOnClickListener.onClick(this); 를 볼 수 있 습 니 다.맞아요.우 리 는 또 새로운 발견 을 한 것 같 습 니 다.위의 경험 에 따 르 면 이 코드 는 우리 가 설정 한 클릭 이벤트 모니터 를 되 돌려 줍 니 다.우리 가 평소에 쓰 는 거 야xxx.setOnClickListener(listener);

/**
 * Register a callback to be invoked when this view is clicked. If this view is not
 * clickable, it becomes clickable.
 *
 * @param l The callback that will run
 *
 * @see #setClickable(boolean)
 */
public void setOnClickListener(@Nullable OnClickListener l) {
 if (!isClickable()) {
 setClickable(true);
 }
 getListenerInfo().mOnClickListener = l;
}
위의 방법 설정 이 바로 mListener Info 의 클릭 모니터 로 위의 추측 을 검증 한 것 을 볼 수 있 습 니 다.여기까지 온 터치 사건 의 전달 메커니즘 은 기본적으로 분석 이 끝 났 고 일 단락 된 셈 이다.
자,이제 시작 문 제 를 해결 할 수 있 습 니 다.그리고 다시 한 번 요약 하 겠 습 니 다.dispatchTouchEvent 에서 OnTouchListener 가 설정 되 어 있 고 View 가 enable 이 라면 먼저 OnTouchListener 중의onTouch(View v, MotionEvent event) 가 실 행 됩 니 다.onTouch 가 true 로 돌아 가면 dispatchTouch 이벤트 가 더 이상 실행 되 지 않 고 true 로 돌아 갑 니 다.그렇지 않 으 면 onTouchEvent 를 실행 합 니 다.onTouchEvent 에서 View 가 클릭 할 수 있 으 면 true 로 돌아 갑 니 다.그렇지 않 으 면 false 입 니 다.그리고 onTouchEvent 에서 View 가 클릭 가능 하고 현재 터치 이벤트 가 ACTION 이면UP,실행performClick() ,OnClickListener 의 onClick 방법 을 되 돌려 줍 니 다.
다음은 내 가 그린 스케치 이다.

또 한 가지 주의 할 점 은 현재 사건 이 ACTION 이 라면DOWN,dispatchTouchEvent 가 true 로 돌아 와 야 다음 ACTION 을 받 을 수 있 습 니 다.MOVE,ACTION_UP 사건,즉 사건 이 소 비 돼 야 다음 사건 을 받 아들 일 수 있다 는 것 이다.
총결산
이상 은 안 드 로 이 드 에서 onTouch 이벤트 전달 체제 에 대한 상세 한 분석 입 니 다.안 드 로 이 드 개발 자 여러분 의 학습 이나 업무 에 어느 정도 도움 이 되 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 십시오.

좋은 웹페이지 즐겨찾기