<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      安卓筆記俠

      專(zhuān)注安卓開(kāi)發(fā)

      導(dǎo)航

      Android View體系(五)從源碼解析View的事件分發(fā)機(jī)制

      1.處理點(diǎn)擊事件的方法

      View的層級(jí)

      我們知道View的結(jié)構(gòu)是樹(shù)形的結(jié)構(gòu),View可以放在ViewGroup中,這個(gè)ViewGroup也可以放到另一個(gè)ViewGroup中,這樣層層的嵌套就組成了View的層級(jí)。

      什么是點(diǎn)擊事件分發(fā)

      當(dāng)我們點(diǎn)擊屏幕,就產(chǎn)生了觸摸事件,這個(gè)事件被封裝成了一個(gè)類(lèi):MotionEvent。而當(dāng)這個(gè)MotionEvent產(chǎn)生后,那么系統(tǒng)就會(huì)將這個(gè)MotionEvent傳遞給View的層級(jí),MotionEvent在View的層級(jí)傳遞的過(guò)程就是點(diǎn)擊事件分發(fā)。

      點(diǎn)擊事件分發(fā)的重要方法

      點(diǎn)擊事件有三個(gè)重要的方法它們分別是:

      • dispatchTouchEvent(MotionEvent ev):用來(lái)進(jìn)行事件的分發(fā)
      • onInterceptTouchEvent(MotionEvent ev):用來(lái)進(jìn)行事件的攔截,在dispatchTouchEvent()中調(diào)用,需要注意的是View沒(méi)有提供該方法
      • onTouchEvent(MotionEvent ev):用來(lái)處理點(diǎn)擊事件,在dispatchTouchEvent()方法中進(jìn)行調(diào)用

      為了了解這三個(gè)方法的關(guān)系,我們先來(lái)看看ViewGroup的dispatchTouchEvent()方法的部分源碼:

      @Override
      public boolean dispatchTouchEvent(MotionEvent ev) {
            ...省略
                 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;
                 }
      
                ...省略
             return handled;
      }
      View Code

      很明顯在dispatchTouchEvent()方法中調(diào)用了onInterceptTouchEvent()方法來(lái)判斷是否攔截事件,來(lái)看看onInterceptTouchEvent()方法:

      public boolean onInterceptTouchEvent(MotionEvent ev) {
             return false;
         }
      View Code

      onInterceptTouchEvent()方法默認(rèn)返回false,不進(jìn)行攔截,接著來(lái)看看dispatchTouchEvent()方法剩余的部分源碼:

      public boolean dispatchTouchEvent(MotionEvent ev) {
       ...省略
                    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);
                              }
      
       ...省略
      
      }
      View Code

      我們看到了for循環(huán),首先遍歷ViewGroup的子元素,判斷子元素是否能夠接收到點(diǎn)擊事件,如果子元素能夠接收到則交由子元素來(lái)處理。接下來(lái)看看37行的dispatchTransformedTouchEvent()方法中實(shí)現(xiàn)了什么:

      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 = dispatchTouchEvent(event);
                 } else {
                     handled = child.dispatchTouchEvent(event);
                 }
                 event.setAction(oldAction);
                 return handled;
             }
       ...省略      
      }
      View Code

      如果有子View則調(diào)用子View的dispatchTouchEvent(event)方法。如果ViewGroup沒(méi)有子View則調(diào)用super.dispatchTouchEvent(event),ViewGroup是繼承View的,我們?cè)賮?lái)看看View的dispatchTouchEvent(event):

      public boolean dispatchTouchEvent(MotionEvent event) {
            ...省略
             boolean result = false;
             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;
                 }
             }
          ...省略
             return result;
         }
      View Code

      我們看到如果OnTouchListener不為null并且onTouch()方法返回true,則表示事件被消費(fèi),就不會(huì)執(zhí)行onTouchEvent(event),否則就會(huì)執(zhí)行onTouchEvent(event)。再來(lái)看看onTouchEvent()方法的部分源碼

      public boolean onTouchEvent(MotionEvent event) {
           ...省略
             final int action = event.getAction();
             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 (!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();
                                     }
                                 }
                             }
            ...省略    
             }
             return true;
            }          
            return false;
         }
      View Code

      上面可以看到只要View的CLICKABLE和LONG_CLICKABLE一個(gè)為true,那么onTouchEvent就會(huì)返回true消耗這個(gè)事件。CLICKABLE和LONG_CLICKABLE代表View可以被點(diǎn)擊和長(zhǎng)按點(diǎn)擊,可以通過(guò)View的setClickable和setLongClickable方法來(lái)設(shè)置,也可以通過(guò)View的setOnClickListenter和setOnLongClickListener來(lái)設(shè)置,他們會(huì)自動(dòng)將View的設(shè)置為CLICKABLE和LONG_CLICKABLE。
      接著在ACTION_UP事件會(huì)調(diào)用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;
      }
      View Code

      如果View設(shè)置了點(diǎn)擊事件OnClickListener,那么它的onClick()方法就會(huì)被執(zhí)行。

      2.點(diǎn)擊事件分發(fā)的傳遞規(guī)則

      看到這里我們就可以知道點(diǎn)擊事件分發(fā)的這三個(gè)重要方法的關(guān)系,用偽代碼來(lái)簡(jiǎn)單表示就是:

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

      點(diǎn)擊事件由上而下的傳遞規(guī)則

      當(dāng)點(diǎn)擊事件產(chǎn)生后會(huì)由Activity來(lái)處理在傳遞給Window再傳遞給頂層的ViewGroup,一般在事件傳遞中只考慮ViewGroup的onInterceptTouchEvent()方法,因?yàn)橐话闱闆r我們不會(huì)去重寫(xiě)dispatchTouchEvent()方法。
      對(duì)于根ViewGroup,點(diǎn)擊事件首先傳遞給它的dispatchTouchEvent()方法,如果該ViewGroup的onInterceptTouchEvent()方法返回true,則表示它要攔截這個(gè)事件,這個(gè)事件就會(huì)交給它的onTouchEvent()方法處理,如果onInterceptTouchEvent()方法返回false,則表示它不攔截這個(gè)事件,則交給它的子元素的dispatchTouchEvent()來(lái)處理,如此的反復(fù)下去。如果傳遞給最底層的View,View是沒(méi)有子View的,就會(huì)調(diào)用View的dispatchTouchEvent()方法,一般情況下最終會(huì)調(diào)用View的onTouchEvent()方法。

      舉個(gè)現(xiàn)實(shí)的例子,就是我們的應(yīng)用產(chǎn)生了重大的bug,這個(gè)bug首先會(huì)匯報(bào)給技術(shù)總監(jiān)那:

      技術(shù)總監(jiān)(頂層ViewGroup)→技術(shù)經(jīng)理(中層ViewGroup)→工程師(底層View)
      技術(shù)總監(jiān)不攔截,把bug分給了技術(shù)經(jīng)理,技術(shù)經(jīng)理不攔截把bug分給了工程師,工程師沒(méi)有下屬只有自己處理了。
      事件由上而下傳遞返回值規(guī)則為:true,攔截,不繼續(xù)向下傳遞;false,不攔截,繼續(xù)向下傳遞。

      點(diǎn)擊事件由下而上的傳遞規(guī)則

      點(diǎn)擊事件傳給最底層的View,如果他的onTouchEvent()方法返回true,則事件由最底層的View消耗并處理了,如果返回false則表示該View不做處理,則傳遞給父View的onTouchEvent()處理,如果父View的onTouchEvent()仍舊返回返回false,則繼續(xù)傳遞給改父View的父View處理,如此的反復(fù)下去。

      再返回我們現(xiàn)實(shí)的例子,工程師發(fā)現(xiàn)這個(gè)bug太難搞不定(onTouchEvent()返回false),他只能交給上級(jí)技術(shù)經(jīng)理處理,如果技術(shù)經(jīng)理也搞不定(onTouchEvent()返回false),那就把bug傳給技術(shù)總監(jiān),技術(shù)總監(jiān)一看bug很簡(jiǎn)單就解決了(onTouchEvent()返回true)。

      事件由下而上傳遞返回值規(guī)則為:true,處理了,不繼續(xù)向上傳遞;false,不處理,繼續(xù)向上傳遞。

      點(diǎn)擊事件傳遞時(shí)的其他問(wèn)題

      • 上面源碼我們看到:如果我們?cè)O(shè)置了OnTouchListener并且onTouch()方法返回true,則onTouchEvent()方法不會(huì)被調(diào)用,否則則會(huì)調(diào)用onTouchEvent()方法,可見(jiàn)OnTouchListener的優(yōu)先級(jí)要比onTouchEvent()要高。在OnTouchEvent()方法中,如果當(dāng)前設(shè)置了OnClickListener則會(huì)執(zhí)行它的onClick()方法。
      • View的OnTouchEvent()方法默認(rèn)都會(huì)返回true,除非它是不可點(diǎn)擊的也就是CLICKABLE和LONG_CLICKABLE都為false。

      posted on 2016-11-06 12:27  安卓筆記俠  閱讀(550)  評(píng)論(0)    收藏  舉報(bào)

      主站蜘蛛池模板: 精品久久精品久久精品久久| 日本人妻巨大乳挤奶水免费| 亚洲精品综合网二三区| 久久99精品国产麻豆婷婷| 久久婷婷五月综合色99啪ak| 天干天干啦夜天干天2017| 日韩av一区二区三区精品| 国产精品任我爽爆在线播放6080| 国产无遮挡又黄又爽高潮| 中文字幕一区二区久久综合| 日韩中文字幕人妻一区| 色欲综合久久中文字幕网| 久久天堂无码av网站| 亚洲国产精久久久久久久春色| 精品一区二区成人码动漫| 狠狠五月深爱婷婷网| 国内外精品激情刺激在线| 中国少妇嫖妓BBWBBW| 亚洲东京色一区二区三区| 野花在线观看免费观看高清| 亚洲男人的天堂久久香蕉| 国产馆在线精品极品粉嫩| 日本不卡三区| 国产美女直播亚洲一区色| 亚洲欧美综合中文| 邢台县| 日日碰狠狠添天天爽五月婷 | 国产最大的福利精品自拍| 国产女高清在线看免费观看| 国产午夜福利视频在线| 欧美大片va欧美在线播放 | 成人午夜视频一区二区无码| 国产老女人免费观看黄A∨片 | 熟妇人妻av中文字幕老熟妇 | 免费看黄片一区二区三区| 色综合久久中文综合网| 日韩一区二区三区精品区| 日本道精品一区二区三区| 免费A级毛片樱桃视频| 国产美女精品一区二区三区| 亚洲少妇人妻无码视频|