欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android事件的分發(fā)機(jī)制詳解

 更新時(shí)間:2016年06月17日 12:07:28   作者:彬~  
這篇文章主要為大家詳細(xì)介紹了Android事件的分發(fā)機(jī)制,感興趣的小伙伴們可以參考一下

在分析Android事件分發(fā)機(jī)制前,明確android的兩大基礎(chǔ)控件類(lèi)型:View和ViewGroup。View即普通的控件,沒(méi)有子布局的,如Button、TextView. ViewGroup繼承自View,表示可以有子控件,如Linearlayout、Listview這些。今天我們先來(lái)了解View的事件分發(fā)機(jī)制。
先看下代碼,非常簡(jiǎn)單,只有一個(gè)Button,分別給它注冊(cè)了OnClick和OnTouch的點(diǎn)擊事件。

btn.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        Log.i("Tag", "This is button onClick event");
      }
    });
    btn.setOnTouchListener(new View.OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent event) {
        Log.i("Tag", "This is button onTouch action" + event.getAction());
        return false;
      }
    });

運(yùn)行一下項(xiàng)目,結(jié)果如下:
 I/Tag: This is button onTouch action0
 I/Tag: This is button onTouch action2
 I/Tag: This is button onTouch action2
 I/Tag: This is button onTouch action1
 I/Tag: This is button onClick event 
可以看到,onTouch是有先于onClick執(zhí)行的,因此事件的傳遞順序是先onTouch,在到OnClick。具體為什么這樣,下面會(huì)通過(guò)源碼來(lái)說(shuō)明。這時(shí),我們可能注意到了,onTouch的方法是有返回值,這里是返回false,我們將它改為true再運(yùn)行一次,結(jié)果如下:
 I/Tag: This is button onTouch action0
 I/Tag: This is button onTouch action2
 I/Tag: This is button onTouch action2
 I/Tag: This is button onTouch action2
 I/Tag: This is button onTouch action1

對(duì)比兩次結(jié)果,我們發(fā)現(xiàn)onClick方法不再執(zhí)行,為什么會(huì)這樣,下面我將通過(guò)源碼給大家一步步理清這個(gè)思路。
查看源碼時(shí),首先要知道所有View類(lèi)型控件事件入口都是dispatchTouchEvent(),所以我們直接進(jìn)入到View這個(gè)類(lèi)里面的dispatchTouchEvent()方法看一下。 

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;
  }

從源碼第25行處可以看到,mOnTouchListener.onTouch()的方法首先被執(zhí)行,如果li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)都為真的話(huà),result賦值為true,否則就執(zhí)行onTouchEvent(event)方法。

從上面可以看到要符合條件有四個(gè),
 1、ListenerInfo li,它是view中的一個(gè)靜態(tài)類(lèi),里面定義view的事件的監(jiān)聽(tīng)等等,所以有涉及到view的事件,ListenerInfo都會(huì)被實(shí)例化,因此li不為null
 2、mOnTouchiListener是在setOnTouchListener方法里面賦值的,只要touch事件被注冊(cè),mOnTouchiListener一定不會(huì)null
 3、 (mViewFlags & ENABLED_MASK) == ENABLED,是判斷當(dāng)前點(diǎn)擊的控件是否是enable的,button默認(rèn)為enable,這個(gè)條件也恒定為true,
 4、重點(diǎn)來(lái)了,li.mOnTouchListener.onTouch(this, event)就是回調(diào)控件onTouch方法,當(dāng)這個(gè)條件也為true時(shí),result=true,onTouchEvent(event)將不會(huì)被執(zhí)行。如果onTouch返回false,就會(huì)再執(zhí)行onTouchEvent(event)方法。
我們接著再進(jìn)入到onTouchEvent方法查看源碼。

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;
  }

從源碼的21行我們可以看出,該控件可點(diǎn)擊就會(huì)進(jìn)入到switch判斷中,當(dāng)我們觸發(fā)了手指離開(kāi)的實(shí)際,則會(huì)進(jìn)入到MotionEvent.ACTION_UP這個(gè)case當(dāng)中。我們接著往下看,在源碼的50行,調(diào)用到了mPerformClick()方法,我們繼續(xù)進(jìn)入到這個(gè)方法的源碼看看。 

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;
  }

現(xiàn)在我們可以看到,只要ListenerInfo和mOnClickListener不為null就會(huì)調(diào)用onClick這個(gè)方法,之前說(shuō)過(guò),只要有監(jiān)聽(tīng)事件,ListenerInfo就不為null,帶mOnClickListener又是在哪里賦值呢?我們?cè)倮^續(xù)看下它的源碼。

public void setOnClickListener(@Nullable OnClickListener l) {
    if (!isClickable()) {
      setClickable(true);
    }
    getListenerInfo().mOnClickListener = l;
  }

看到這里一切就清楚了,當(dāng)我們調(diào)用setOnClickListener方法來(lái)給按鈕注冊(cè)一個(gè)點(diǎn)擊事件時(shí),就會(huì)給mOnClickListener賦值。整個(gè)分發(fā)事件的順序是onTouch()-->onTouchEvent(event)-->performClick()-->OnClick()。
 現(xiàn)在我們可以解決之前的問(wèn)題。
1、onTouch方法是優(yōu)先于OnClick,所以是執(zhí)行了onTouch,再執(zhí)行onClick。 
2、無(wú)論是dispatchTouchEvent還是onTouchEvent,如果返回true表示這個(gè)事件已經(jīng)被消費(fèi)、處理了,不再往下傳了。在dispathTouchEvent的源碼里可以看到,如果onTouchEvent返回了true,那么它也返回true。如果dispatchTouchEvent在執(zhí)行onTouch監(jiān)聽(tīng)的時(shí)候,onTouch返回了true,那么它也返回true,這個(gè)事件提前被onTouch消費(fèi)掉了。就不再執(zhí)行onTouchEvent了,更別說(shuō)onClick監(jiān)聽(tīng)了。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論