亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

 更新時(shí)間:2016年11月02日 09:19:08   投稿:lijiao  
這篇文章主要為大家詳細(xì)介紹了Android View事件分發(fā)機(jī)制,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

準(zhǔn)備了一陣子,一直想寫一篇事件分發(fā)的文章總結(jié)一下,這個(gè)知識(shí)點(diǎn)實(shí)在是太重要了。

一個(gè)應(yīng)用的布局是豐富的,有TextView,ImageView,Button等,這些子View的外層還有ViewGroup,如RelativeLayout,LinearLayout。作為一個(gè)開(kāi)發(fā)者,我們會(huì)思考,當(dāng)點(diǎn)擊一個(gè)按鈕,Android系統(tǒng)是怎樣確定我點(diǎn)的就是按鈕而不是TextView的?然后還正確的響應(yīng)了按鈕的點(diǎn)擊事件。內(nèi)部經(jīng)過(guò)了一系列什么過(guò)程呢?

先鋪墊一些知識(shí)能更加清晰的理解事件分發(fā)機(jī)制:
1. 通過(guò)setContentView設(shè)置的View就是DecorView的子view,即DecorView是父容器。
2. 點(diǎn)擊屏幕時(shí),在手指按下和抬起間,會(huì)產(chǎn)生很多事件,down…move…move…up,中間會(huì)有很多的move事件,這一系列的事件為一個(gè)事件序列
3. dispatchTouchEvent方法用于分發(fā)事件
4. onInterceptTouchEvent方法用于攔截事件
5. onTouchEvent方法用于處理事件

當(dāng)一個(gè)點(diǎn)擊事件(MotionEvent)產(chǎn)生后,事件最先傳遞給當(dāng)前的界面(Activity),這點(diǎn)是很好理解的。 Activity再將事件傳遞給窗口(Window),然后Window將事件傳遞給頂級(jí)View(DecorView)。此時(shí),事件已經(jīng)到達(dá)了View了。之后頂級(jí)View就會(huì)按照事件分發(fā)機(jī)制去分發(fā)事件。具體是這樣的:

對(duì)于一個(gè)根ViewGroup來(lái)說(shuō),點(diǎn)擊事件產(chǎn)生后,首先會(huì)傳遞給它,這時(shí)它的 dispatchTouchEvent 方法就會(huì)被調(diào)用,如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當(dāng)前事件,接著事件就會(huì)交給這個(gè)ViewGroup處理,即它的onTouchEvent方法就會(huì)被調(diào)用。如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回false,就表示它不攔截當(dāng)前事件,這時(shí)當(dāng)前事件就會(huì)繼續(xù)傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會(huì)被調(diào)用,如此反復(fù)直到事件被最終處理。
如果一個(gè)View的onTouchEvent方法返回false,那么它的父容器的onTouchEvent方法會(huì)被調(diào)用,如果它的父容器的onTouchEvent方法還是返回false,那就繼續(xù)往上拋,當(dāng)所有的元素都不處理這個(gè)事件,那么這個(gè)事件會(huì)最終傳遞給Activity處理,即Activity的onTouchEvent方法會(huì)被調(diào)用。

好了,現(xiàn)在已經(jīng)鋪墊了基礎(chǔ),那么接下來(lái)就從源碼的角度來(lái)分析事件分發(fā)機(jī)制。

當(dāng)然是從Activity的dispatchTouchEvent方法開(kāi)始分析。源碼如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
  if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    onUserInteraction();
  }
  if (getWindow().superDispatchTouchEvent(ev)) {
    return true;
  }
  return onTouchEvent(ev);
}

如果當(dāng)前事件是down的話,就調(diào)用onUserInteraction方法,onUserInteraction是一個(gè)空方法,我們可以暫時(shí)不搭理。然后調(diào)用getWindow方法獲取到當(dāng)前Activity關(guān)聯(lián)的Window,Window再調(diào)用superDispatchTouchEvent方法將事件傳入進(jìn)行分發(fā)。
如果superDispatchTouchEvent方法返回true的話, view已經(jīng)處理了事件。整個(gè)事件循環(huán)結(jié)束。如果返回false,沒(méi)有view處理這個(gè)事件。事件往上拋,那就Activity自己處理了,即Activity的onTouchEvent方法會(huì)被調(diào)用。

因?yàn)橄胍朗录恼麄€(gè)分發(fā)過(guò)程,現(xiàn)在關(guān)注的是Window的superDispatchTouchEvent方法,那么就跟進(jìn)去看看:

public abstract boolean superDispatchTouchEvent(MotionEvent event);

Window是一個(gè)抽象類,superDispatchTouchEvent是一個(gè)抽象的方法,那么我們必須要找到window的實(shí)現(xiàn)類才行,可是茫茫人海怎么找呢?看到window類的說(shuō)明就明白了

* <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window

意思是Window存在唯一的實(shí)現(xiàn)是android.view.PhoneWindow

那么PhoneWindow里的superDispatchTouchEvent方法就是我們要找的信息,如下:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
  return mDecor.superDispatchTouchEvent(event);
}

直接將事件傳遞給了DecorView。這時(shí)事件已經(jīng)是到達(dá)View了哦。

那么跟進(jìn)DecorView的superDispatchTouchEvent方法看看,如下:

public boolean superDispatchTouchEvent(MotionEvent event) {
  return super.dispatchTouchEvent(event);
}

內(nèi)部調(diào)用了父類的dispatchTouchEvent方法,那么DecorView的父類是什么呢?DecorView肯定是View的,那么剛才開(kāi)篇提到,我們通過(guò)setContentView設(shè)置的View,是DecorView的子View。那么更加準(zhǔn)確的說(shuō)DecorView是一個(gè)ViewGroup。

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

可以看到DecorView是繼承自FrameLayout,F(xiàn)rameLayout是ViewGroup,也就是說(shuō)DecorView是一個(gè)ViewGroup。

那么現(xiàn)在只需要關(guān)注ViewGroup的dispatchTouchEvent方法。繼續(xù)前進(jìn)

ViewGroup的事件分發(fā)

ViewGroup的dispatchTouchEvent方法如下:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    //代碼省略

    // Check for interception.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
      final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
      if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
      } else {
        intercepted = false;
      }
    } else {
      // There are no touch targets and this action is not an initial down
      // so this view group continues to intercept touches.
      intercepted = true;
    }

    //代碼省略

    if (!canceled && !intercepted) {

    //代碼省略

        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
          final float x = ev.getX(actionIndex);
          final float y = ev.getY(actionIndex);
          // Find a child that can receive the event.
          // Scan children from front to back.
          final ArrayList<View> preorderedList = buildOrderedChildList();
          final boolean customOrder = preorderedList == null
              && isChildrenDrawingOrderEnabled();
          final View[] children = mChildren;
          for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = customOrder
                ? getChildDrawingOrder(childrenCount, i) : i;
            final View child = (preorderedList == null)
                ? children[childIndex] : preorderedList.get(childIndex);

            // If there is a view that has accessibility focus we want it
            // to get the event first and if not handled we will perform a
            // normal dispatch. We may do a double iteration but this is
            // safer given the timeframe.
            if (childWithAccessibilityFocus != null) {
              if (childWithAccessibilityFocus != child) {
                continue;
              }
              childWithAccessibilityFocus = null;
              i = childrenCount - 1;
            }

            if (!canViewReceivePointerEvents(child)
                || !isTransformedTouchPointInView(x, y, child, null)) {
              ev.setTargetAccessibilityFocus(false);
              continue;
            }

            newTouchTarget = getTouchTarget(child);
            if (newTouchTarget != null) {
              // Child is already receiving touch within its bounds.
              // Give it the new pointer in addition to the ones it is handling.
              newTouchTarget.pointerIdBits |= idBitsToAssign;
              break;
            }

            resetCancelNextUpFlag(child);
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
              // Child wants to receive touch within its bounds.
              mLastTouchDownTime = ev.getDownTime();
              if (preorderedList != null) {
                // childIndex points into presorted list, find original index
                for (int j = 0; j < childrenCount; j++) {
                  if (children[childIndex] == mChildren[j]) {
                    mLastTouchDownIndex = j;
                    break;
                  }
                }
              } else {
                mLastTouchDownIndex = childIndex;
              }
              mLastTouchDownX = ev.getX();
              mLastTouchDownY = ev.getY();
              newTouchTarget = addTouchTarget(child, idBitsToAssign);
              alreadyDispatchedToNewTouchTarget = true;
              break;
            }

            // The accessibility focus didn't handle the event, so clear
            // the flag and do a normal dispatch to all children.
            ev.setTargetAccessibilityFocus(false);
          }
          if (preorderedList != null) preorderedList.clear();
        }

        if (newTouchTarget == null && mFirstTouchTarget != null) {
          // Did not find a child to receive the event.
          // Assign the pointer to the least recently added target.
          newTouchTarget = mFirstTouchTarget;
          while (newTouchTarget.next != null) {
            newTouchTarget = newTouchTarget.next;
          }
          newTouchTarget.pointerIdBits |= idBitsToAssign;
        }
      }
    }

    // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
      // No touch targets so treat this as an ordinary view.
      handled = dispatchTransformedTouchEvent(ev, canceled, null,
          TouchTarget.ALL_POINTER_IDS);
    }

    //代碼省略

    return handled;
}

代碼比較長(zhǎng),一點(diǎn)一點(diǎn)分析,先看到一開(kāi)始的判斷

if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)

mFirstTouchTarget != null的意義是ViewGroup不攔截事件并將事件交由子元素處理,先這樣記著,這從后面的addTouchTarget方法可以得出結(jié)論的。

然后又會(huì)來(lái)到這個(gè)if判斷。

if (!disallowIntercept) {
  intercepted = onInterceptTouchEvent(ev);
  ev.setAction(action); // restore action in case it was changed
}

那我們看看disallowIntercept。而disallowIntercept的賦值過(guò)程中,有一個(gè) FLAG_DISALLOW_INTERCEPT 標(biāo)記位

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

這個(gè) FLAG_DISALLOW_INTERCEPT 標(biāo)記位是可以通過(guò)requestDisallowInterceptTouchEvent方法來(lái)設(shè)置的。

回到if (!disallowIntercept)的判斷,進(jìn)入這個(gè)if判斷后,就會(huì)來(lái)到

intercepted = onInterceptTouchEvent(ev);

調(diào)用onInterceptTouchEvent方法,詢問(wèn)ViewGroup是否攔截事件。

讀到這里,可以回憶下開(kāi)篇時(shí)鋪墊的結(jié)論,對(duì)于ViewGroup,點(diǎn)擊事件產(chǎn)生后,首先會(huì)傳遞給它,這時(shí)它的 dispatchTouchEvent 方法就會(huì)被調(diào)用,接著會(huì)調(diào)用它的onInterceptTouchEvent方法,如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當(dāng)前事件,接著事件就會(huì)交給這個(gè)ViewGroup處理,即它的onTouchEvent方法就會(huì)被調(diào)用。如果返回false表示不攔截,通常ViewGroup也是不攔截事件的。

那現(xiàn)在先分析不攔截的情況,不攔截那就好辦了的。經(jīng)過(guò)一系列的判斷,就會(huì)來(lái)到一個(gè)for循環(huán)遍歷。

for (int i = childrenCount - 1; i >= 0; i--)

這時(shí)ViewGroup開(kāi)始分發(fā)傳遞事件,遍歷子元素了。

首先肯定需要過(guò)濾掉一些無(wú)關(guān)點(diǎn)擊事件的子元素的,判斷子元素是否能夠接收點(diǎn)擊事件,點(diǎn)擊事件的坐標(biāo)是否落在子元素區(qū)域內(nèi)。

if (!canViewReceivePointerEvents(child)
    || !isTransformedTouchPointInView(x, y, child, null)) {
  ev.setTargetAccessibilityFocus(false);
  continue;
}

如果不能夠接收點(diǎn)擊事件或者點(diǎn)擊事件的坐標(biāo)沒(méi)有落在子元素區(qū)域,就會(huì)跳出當(dāng)前循環(huán),繼續(xù)遍歷下一個(gè)子元素。這下就知道了Android系統(tǒng)為什么能夠知道點(diǎn)擊的是Button而不是TextView,其實(shí)內(nèi)部就只是做了一個(gè)判斷嘛。

那么繼續(xù)分析,子元素符合以上兩個(gè)條件后,就將事件傳遞給這個(gè)子元素。會(huì)來(lái)到了這個(gè)判斷。

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))

執(zhí)行dispatchTransformedTouchEvent方法,將子元素傳進(jìn)去。這個(gè)方法很重要,那么跟進(jìn)看看

/**
 * Transforms a motion event into the coordinate space of a particular child view,
 * filters out irrelevant pointer ids, and overrides its action if necessary.
 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
 */
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
    View child, int desiredPointerIdBits) {
  final boolean handled;

  // Canceling motions is a special case. We don't need to perform any transformations
  // or filtering. The important part is the action, not the contents.
  final int oldAction = event.getAction();
  if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
    event.setAction(MotionEvent.ACTION_CANCEL);
    if (child == null) {
      handled = super.dispatchTouchEvent(event);
    } else {
      handled = child.dispatchTouchEvent(event);
    }
    event.setAction(oldAction);
    return handled;
  }

  //代碼省略
}

我們看到child!=null的情況,如果子元素不為空,調(diào)用子元素的dispatchTouchEvent方法繼續(xù)分發(fā)事件,同時(shí)返回處理結(jié)果布爾值,這時(shí)就將事件傳遞到了子View處理。完成了一輪的事件分發(fā)。這個(gè)方法先到這里就好。

再看回ViewGroup的dispatchTouchEvent方法,如果dispatchTransformedTouchEvent方法返回true的話,這時(shí)事件已經(jīng)傳遞給子元素處理,ViewGroup已經(jīng)不管這個(gè)事件了。
那么就會(huì)進(jìn)入if語(yǔ)句,最后會(huì)來(lái)到addTouchTarget方法,這個(gè)方法之前是提到過(guò)的,用于mFirstTouchTarget標(biāo)記位的賦值。

那跟進(jìn)這個(gè)方法看看

/**
 * Adds a touch target for specified child to the beginning of the list.
 * Assumes the target child is not already present.
 */
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
  TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
  target.next = mFirstTouchTarget;
  mFirstTouchTarget = target;
  return target;
}

其實(shí)就是讓mFirstTouchTarget指向子元素。

執(zhí)行完這個(gè)addTouchTarget方法后,最終會(huì)到break語(yǔ)句,那么就會(huì)跳出整個(gè)for循環(huán)體。ViewGroup結(jié)束分發(fā)過(guò)程!

又回到dispatchTransformedTouchEvent方法,如果dispatchTransformedTouchEvent方法返回false,那么if語(yǔ)句的一大段代碼都不執(zhí)行了,而是回到for循環(huán)繼續(xù)遍歷子元素進(jìn)行分發(fā)。如此重復(fù)完成事件的傳遞過(guò)程。

現(xiàn)在分析ViewGroup攔截事件的情況,如果ViewGroup攔截事件的話,那么就會(huì)進(jìn)入以下這個(gè)判斷

if (mFirstTouchTarget == null) {
  // No touch targets so treat this as an ordinary view.
  handled = dispatchTransformedTouchEvent(ev, canceled, null,
  TouchTarget.ALL_POINTER_IDS);
}

注意到dispatchTransformedTouchEvent方法的第三個(gè)參數(shù)child傳入的是null,那么就是在dispatchTransformedTouchEvent方法中走以下的語(yǔ)句

if (child == null) {
  handled = super.dispatchTouchEvent(event);
}

而ViewGroup是繼承自View的,那么就是ViewGroup自己處理事件了。這點(diǎn)我們以下分析了View的事件分發(fā)過(guò)程就能搞明白了。

以上就是ViewGroup的事件分發(fā)

那么現(xiàn)在分析已經(jīng)將事件傳遞給了子View的情況,View繼續(xù)調(diào)用dispatchTouchEvent方法,那我們看看View的dispatchTouchEvent方法。

View的事件分發(fā)

View的dispatchTouchEvent方法源碼如下:

/**
 * 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 (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;
}

相比于ViewGroup的dispatchTouchEvent方法,View的dispatchTouchEvent方法代碼量少了。也相對(duì)簡(jiǎn)單些了。
首先會(huì)來(lái)到如下判斷:

if (li != null && li.mOnTouchListener != null
    && (mViewFlags & ENABLED_MASK) == ENABLED
    && li.mOnTouchListener.onTouch(this, event))

li變量在哪里被賦值的呢?通常是在setOnClickListener方法或setOnTouchListener方法的時(shí)候。

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

public void setOnTouchListener(OnTouchListener l) {
  getListenerInfo().mOnTouchListener = l;
}

而這個(gè)getListenerInfo()如下:

ListenerInfo getListenerInfo() {
  if (mListenerInfo != null) {
    return mListenerInfo;
  }
  mListenerInfo = new ListenerInfo();
  return mListenerInfo;
}

ListenerInfo是一個(gè)內(nèi)部類,里面存放的是各種監(jiān)聽(tīng)事件的引用。

之后會(huì)判斷如下條件:

li.mOnTouchListener != null

同理只要setOnTouchListener方法設(shè)置了,這個(gè)引用就不空。這些都是好理解的。那關(guān)鍵到了,

li.mOnTouchListener.onTouch(this, event)

到了最后一個(gè)條件。這個(gè)onTouch方法是我們?nèi)?shí)現(xiàn)的,它也返回一個(gè)布爾值,如果返回true的話,那么就會(huì)進(jìn)入這個(gè)if判斷最終返回true,跳出整個(gè)方法,那么我們可以看到接下來(lái)的onTouchEvent方法是不會(huì)得到執(zhí)行的。
也就是onTouch的執(zhí)行在onTouchEvent之前。那么如果我們也調(diào)用了setOnClickListener方法監(jiān)聽(tīng)點(diǎn)擊事件的話,onClick方法是在哪里調(diào)用的呢?我們有理由相信是在onTouchEvent方法里調(diào)用的。那么就跟進(jìn)看看。

public boolean onTouchEvent(MotionEvent event) {

  //代碼省略

  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 (!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();
              }
            }
          }

       //代碼省略 

        break;
    }

    return true;
  }

  return false;
}

只要CLICKABLE或LONG_CLICKABLE不空, 就會(huì)處理這個(gè)事件,然而怎么保證CLICKABLE或LONG_CLICKABLE不空呢?其實(shí)細(xì)心的你會(huì)發(fā)現(xiàn),剛才上面貼出的setOnClickListener源代碼中,會(huì)將CLICKABL屬性設(shè)置會(huì)true

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

這樣就能進(jìn)入if判斷去處理這個(gè)事件了,之后就會(huì)來(lái)到performClick()方法,應(yīng)該就是它了,跟進(jìn)去看看吧。

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

熟悉接口回調(diào)機(jī)制的你,一定也讀懂了performClick()方法的源碼,

li.mOnClickListener.onClick(this);

是在執(zhí)行這行代碼時(shí),調(diào)用了我們熟悉的onClick方法

以上就是View的事件分發(fā)機(jī)制。

此時(shí)已經(jīng)將事件分發(fā)機(jī)制分析完了,由于我的技術(shù)的原因,駕馭的不好,有些關(guān)鍵點(diǎn)還是沒(méi)分析清楚,但我相信學(xué)完了這篇文章能讓我和你都對(duì)事件分發(fā)機(jī)制的實(shí)現(xiàn)有一個(gè)大致的認(rèn)識(shí),有這個(gè)已經(jīng)可以了,之后還可以一點(diǎn)點(diǎn)去強(qiáng)化鍛煉,深入理解事件分發(fā)機(jī)制。才能為自定義控件鋪墊良好的基礎(chǔ)。

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

相關(guān)文章

最新評(píng)論