亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > Android > 正文

Android Touch事件分發深入了解

2019-12-12 06:41:43
字體:
來源:轉載
供稿:網友

本文帶著大家深入學習觸摸事件的分發,具體內容如下
1. 觸摸動作及事件序列

(1)觸摸事件的動作

    觸摸動作一共有三種:ACTION_DOWN、ACTION_MOVE、ACTION_UP。當用戶手指接觸屏幕時,便產生一個動作為ACTION_DOWN的觸摸事件,此時若用戶的手指立即離開屏幕,會產生一個動作為ACTION_UP的觸摸事件;若用戶手指接觸屏幕后繼續滑動,當滑動距離超過了系統中預定義的距離常數,則產生一個動作為ACTION_MOVE的觸摸事件,系統中預定義的用來判斷用戶手指在屏幕上的滑動是否是一個ACTION_MOVE動作的這個距離常量叫做TouchSlop,可通過ViewConfiguration.get(getContext()).getScaledTouchSlop()獲取。

(2)事件序列

    當用戶的手指接觸屏幕,在屏幕上滑動,又離開屏幕,這個過程會產生一系列觸摸事件:ACTION_DOWN-->若干個ACTION_MOVE-->ACTION_UP。這一系列觸摸事件即為一個事件序列。 

2. 觸摸事件的分發

(1)概述

    當產生了一個觸摸時間后,系統要負責把這個觸摸事件給一個View(TargetView)來處理,touch事件傳遞到TargetView的過程即為touch事件的分發。

    觸摸事件的分發順序:Activity-->頂級View-->頂級View的子View-->. . .-->Target View

    觸摸事件的響應順序:TargetView --> TargetView的父容器 --> . . . -->頂級View -->Activity

(2)toush事件分發的具體過程

  a. Activity對touch事件的分發

    當用戶手指接觸屏幕時,便產生了一個touch事件,封裝了touch事件的MotionEvent最先被傳遞給當前Activity,Activity的dispatchTouchEvent方法負責touch事件的分發。分發touch事件的實際工作由當前Activity的Window完成,而Window會將touch事件傳遞給DecorView(當前用戶界面頂級View)。Activity的dispatchTouchEvent方法代碼如下:

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

    根據以上代碼可以知道,touch事件會交由Window的superDispatchTouchEvent進行分發,若這個方法返回true,意味touch事件的分發過程結束,返回false則說明經過層層分發,沒有子View對這個事件進行處理,即所有子View的onTouchEvent方法都返回false(即這個touch事件沒有被“消耗”)。這時會調用Activity的onTouchEvent方法來處理這個touch事件。

    在Window的superDispatchTouchEvent方法中,首先會把touch事件分發給DecorView,因為它是當前用戶界面的頂級View。Window的superDispatchTouchEvent方法如下:

public abstract boolean superDispatchTouchEvent(MotionEvent ev);
    是個抽象方法,這個方法由Window的實現類PhoneWindow實現,PhoneWindow的superDispatchTouchEvent方法的代碼如下:

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

    由以上代碼可得,PhoneWindow的superDispatchTouchEvent方法實際上是通過DecorView的superDispatchTouchEvent方法來完成自己的工作,也就是說,當前Activity的Window直接將這個touch事件傳遞給了DecorView。也就是說,目前touch事件已經經過了如下的分發:Activity-->Window-->DecorView。

b. 頂級View對touch事件的分發

    經過Activity與Window的分發,現在touch事件已經被傳遞到了DecorView的dispatchTouchEvent方法中。DecorView本質上是一個ViewGroup(更具體的說是FrameLayout),ViewGroup的dispatchTouchEvent方法所做的工作可以分為如下幾個階段,第一個階段的主要代碼如下:

//Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {  //Throw away all previous state when starting a new touch gesture.  //The framework may have dropped the up or cancel event for the previous gesture due to an app switch, ANR, or some other state change.  cancelAndClearTouchTargets(ev);  resetTouchState();}

    第一階段的主要工作有倆:一是在第6行的resetTouchState方法中完成了對FLAG_DISALLOW_INTERCEPT標記的重置;二是第5行的cancelAndClearTouchTargets方法會清除當前MotionEvent的touch target。關于FLAG_DISALLOW_INTERCEPT標記和touch target,在下文會有相關說明。

    第二階段的主要工作是決定當前ViewGroup是否攔截本次的touch事件,主要代碼如下:

//Check for interception.final boolean intercepted;if (actionMasked == MotionEvent.ACTION_DOWM || 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.  intercept =true;}

    由以上代碼我們可以知道,當一個touch事件被傳遞到ViewGroup時,會先判斷這個touch事件的動作是否是ACTION_DOWN,如果這個事件是ACTION_DOWN或者mFirstTouchTarget不為null,就會根據FLAG_DISALLOW_INTERCEPT標記決定是否攔截這個touch事件。那么mFirstTouchTarget是什么呢?當touch事件被ViewGroup的子View成功處理時,mFirstTouchTarget就會被賦值為成功處理touch事件的View,也就是上面提高的touch target。

    總結一下上述代碼的流程:在子View不干預ViewGroup的攔截的情況下(上述代碼中的disallowIntercept為false),若當前事件為ACTION_DOWN或者mFirstTouchTarget不為空,則會調用ViewGroup的onInterceptTouchEvent方法來決定最終是否攔截此事件;否則(沒有TargetView并且此事件不是ACTION_DOWN),當前ViewGroup就攔截下此事件。 一旦ViewGroup攔截了某次touch事件,那么mFirstTouchTarget就不會被賦值,因此當再有ACTION_MOVE或是ACTION_UP傳遞到該ViewGroup時,mTouchTarget就為null,所以上述代碼第3行的條件就為false,ViewGroup會攔截下來。由此可得到的結論是:一旦ViewGroup攔截了某次事件,則同一事件序列中的剩余事件也會它默認被攔截而不會再詢問是否攔截(即不會再調用onInterceptTouchEvent)。

    這里存在一種特殊情形,就是子View通過requestDisallowInterceptTouchEvent方法設置父容器的FLAG_DISALLOW_INTERCEPT為true,這個標記指示是否不允許父容器攔截,為true表示不允許。這樣做能夠禁止父容器攔截除ACTION_DOWN以外的所有touch事件。之所以不能夠攔截ACTION_DOWN事件,是因為每當ACTION_DOWN事件到來時,都會重置FLAG_DISALLOW_INTERCEPT這個標記位為默認值(false),所以每當開始一個新touch事件序列(即到來一個ACTION_DOWN動作),都會通過調用onInterceptTouchEven詢問ViewGroup是否攔截此事件。當ACTION_DOWN事件到來時,重置標記位的工作是在上面的第一階段完成的。  

    接下來,會進入第三階段的工作:

final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {  // 不是ACTION_CANCEL并且不攔截  if (actionMasked == MotionEvent.ACTION_DOWN) {     // 若當前事件為ACTION_DOWN則去尋找這次事件新出現的touch target     final int actionIndex = ev.getActionIndex(); // always 0 for down     ...     final int childrenCount = mChildrenCount;     if (newTouchTarget == null && childrenCount != 0) {       // 根據觸摸的坐標尋找能夠接收這個事件的touch target       final float x = ev.getX(actionIndex);       final float y = ev.getY(actionIndex);       final View[] children = mChildren;       // 遍歷所有子View       for (int i = childrenCount - 1; i >= 0; i--) {         final int childIndex = i;         final View child = children[childIndex];         // 尋找可接收這個事件并且touch事件坐標在其區域內的子View         if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {           continue;         }         newTouchTarget = getTouchTarget(child); // 找到了符合條件的子View,賦值給newTouchTarget         if (newTouchTarget != null) {           //Child is already receiving touch within its bounds.           //Give it the new pointer in addition to ones it is handling.           newTouchTarget.pointerIdBits |= idBitsToAssign;           break;         }         resetCancelNextUpFlag(child);         // 把ACTION_DOWN事件傳遞給子組件進行處理         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();           //把mFirstTouchTarget賦值為newTouchTarget,此子View成為新的touch事件的起點           newTouchTarget = addTouchTarget(child, idBitsToAssign);           alreadyDispatchedToNewTouchTarget = true;           break;         }                  }     }  }}

    當ViewGroup不攔截本次事件,則touch事件會分發給它的子View進行處理,相關代碼從第21行開始:遍歷所有ViewGroup的子View,尋找能夠處理此touch事件的子View,若一個子View不在播放動畫并且touch事件坐標位于其區域內,則該子View能夠處理此touch事件,并且會把該子View賦值給newTouchTarget。

    若當前遍歷到的子View能夠處理此touch事件,就會進入第38行的dispatchTransformedTouchEvent方法,該方法實際上調用了子View的dispatchTouchEvent方法。dispatchTransformedTouchEvent方法中相關的代碼如下:

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

    若dispatchTransformedTouchEvent方法傳入的child參數不為null,則會調用child(即處理touch事件的子View)的dispatchTouchEvent方法。若該子View的dispatchTouchEvent方法返回true,則dispatchTransformedTouchEvent方法也會返回true,則表示成功找到了一個處理該事件的touch target,會在第55行把newTouchTarget賦值給mFirstTouchTarget(這一賦值過程是在addTouchTarget方法內部完成的),并跳出對子View遍歷的循環。若子View的dispatchTouchEvent方法返回false,ViewGroup就會把事件分發給下一個子View。

    若遍歷了所有子View后,touch事件都沒被處理(該ViewGroup沒有子View或是所有子View的dispatchTouchEvent返回false),ViewGroup會自己處理touch事件,相關代碼如下:

 if (mFirstTouchTarget == null) {   handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }

    由以上代碼可知,ViewGroup自己處理touch事件時,會調用dispatchTransformedTouchEvent方法,傳入的child參數為null。根據上文的分析,傳入的chid為null時,會調用super.dispatchTouchEvent方法,即調用View類的dispatchTouchEvent方法。 

c. View對touch事件的處理

    View的dispatchTouchEvent方法的主要代碼如下:

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對touch事件的處理過程如下:由于View不包含子元素,所以它只能自己處理事件。它首先會判斷是否設置了OnTouchListener,若設置了,會調用onTouch方法,若onTouch方法返回true(表示該touch事件已經被消耗),則不會再調用onTouchEvent方法;若onTouch方法返回false或沒有設置OnTouchListener,則會調用onTouchEvent方法,onTouchEvent對touch事件進行具體處理的相關代碼如下:

if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  switch (event.getAction()) {    case MotionEvent.ACTION_UP:      boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;      if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {        . . .        if (!mHasPerformedLongPress) {          //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) {              mPerformClck = new PeformClick();            }            if (!post(mPerformClick)) {              performClick();            }          }        }        . . .      }      break;  }  . . .  return true;}


    由以上代碼可知,只要View的CLICKABLE屬性和LONG_CLICKABLE屬性有一個為true(View的CLICKABLE屬性和具體View有關,LONG_CLICKABLE屬性默認為false,setOnClikListener和setOnLongClickListener會分別自動將以上倆屬性設為true),那么這個View就會消耗這個touch事件,即使這個View處于DISABLED狀態。若當前事件是ACTION_UP,還會調用performClick方法,該View若設置了OnClickListener,則performClick方法會在其內部調用onClick方法。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;}


 

以上是我學習Android中觸摸事件分發后的簡單總結,很多地方敘述的還不夠清晰準確

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
高清日韩电视剧大全免费播放在线观看| 精品亚洲国产成av人片传媒| 久久综合电影一区| 亚洲欧美www| 色先锋资源久久综合5566| 夜夜嗨av色一区二区不卡| 日韩一区二区三区在线播放| 欧美日韩黄色大片| 日本一区二三区好的精华液| 欧美理论电影在线观看| 国产午夜精品麻豆| 91黄色8090| 亚洲视频在线观看视频| 5566日本婷婷色中文字幕97| 亚洲成色www8888| 欧美性猛交xxxx免费看久久久| 中文字幕欧美日韩精品| 91av在线不卡| 国产男人精品视频| 国产色婷婷国产综合在线理论片a| 久久成人精品视频| 日韩成人av在线| 91影视免费在线观看| 国产精品白嫩初高中害羞小美女| 国产精品久久久久久久久| 国产成人涩涩涩视频在线观看| 91久久久久久久久久久| 国产精品久久一| 青青草国产精品一区二区| 国产精品主播视频| 91麻豆国产语对白在线观看| 日韩有码在线播放| 国内外成人免费激情在线视频网站| 国内精品久久久久影院 日本资源| 亚洲а∨天堂久久精品喷水| 国产视频综合在线| 亚洲成av人片在线观看香蕉| 性欧美长视频免费观看不卡| 久久亚洲精品一区二区| 久久影视三级福利片| 亚洲风情亚aⅴ在线发布| 国产ts人妖一区二区三区| 亚洲精品视频网上网址在线观看| 国产精品私拍pans大尺度在线| 欧美国产日韩免费| 欧美激情视频播放| 午夜精品视频网站| 国产成人精品在线视频| 亚洲色图狂野欧美| 国产精品一久久香蕉国产线看观看| 亚洲精品久久久久久久久久久| 成人中文字幕在线观看| 日韩精品亚洲视频| 92福利视频午夜1000合集在线观看| 欧美巨大黑人极品精男| www.亚洲人.com| 欧美美女操人视频| 亚洲一区二区免费在线| 7m精品福利视频导航| 日韩国产在线播放| 97在线观看免费高清| 91福利视频在线观看| 亚洲第一精品电影| 国产v综合ⅴ日韩v欧美大片| 亚洲石原莉奈一区二区在线观看| 亚洲精品91美女久久久久久久| 69av在线视频| 欧美巨乳美女视频| 国产午夜精品久久久| 欧美性猛交xxxx免费看久久久| 亚洲欧美日韩国产成人| 日韩精品极品视频| 色噜噜狠狠色综合网图区| 亚洲欧美自拍一区| 国产精品私拍pans大尺度在线| 国产视频在线一区二区| 91精品视频大全| 中文字幕亚洲一区二区三区五十路| 国产日韩欧美一二三区| 欧美一区二区三区精品电影| 午夜精品久久久久久久男人的天堂| 亚洲精品中文字幕av| 国产亚洲视频中文字幕视频| 精品美女久久久久久免费| 九九热精品视频在线播放| 久久久久久有精品国产| 日本一区二区不卡| 国产日韩欧美影视| 亚洲人成欧美中文字幕| 国产精品免费视频xxxx| 人妖精品videosex性欧美| 亚洲成人黄色在线| 日韩在线观看网站| 国产精品久久久久久久久免费| 色综合伊人色综合网站| 亚洲毛片在线看| 国产一区二区三区毛片| 亚洲香蕉av在线一区二区三区| 亚洲精品久久久久久久久| 亚洲剧情一区二区| 自拍偷拍亚洲在线| 91av视频导航| 夜夜躁日日躁狠狠久久88av| 国产噜噜噜噜久久久久久久久| 欧美人与性动交a欧美精品| 欧美成人精品一区| 成人黄色免费网站在线观看| 日韩欧美在线播放| 日韩激情av在线播放| 精品美女国产在线| 日韩国产高清视频在线| 久久久久久亚洲精品中文字幕| 成人乱色短篇合集| 亚洲人成在线播放| 日韩黄色高清视频| 国产欧美一区二区三区在线| 精品亚洲一区二区三区在线观看| 日韩女优人人人人射在线视频| 亚洲国产婷婷香蕉久久久久久| 精品久久久久久中文字幕一区奶水| 亚洲欧美日韩爽爽影院| 色偷偷偷亚洲综合网另类| 色吧影院999| 中文字幕亚洲欧美日韩高清| 欧美体内谢she精2性欧美| 国产一区二区美女视频| 国产精品91久久久| 日本最新高清不卡中文字幕| 精品中文字幕在线| 日韩中文字幕精品视频| 久久久免费观看视频| 欧美一级淫片aaaaaaa视频| 精品福利樱桃av导航| 国产日韩亚洲欧美| 日韩精品福利网站| 久久久久久久一区二区| 中文字幕亚洲综合久久| 久久久精品999| 欧美专区在线视频| 97久久精品在线| 最新亚洲国产精品| 欧美成人免费一级人片100| 国产精品揄拍500视频| 国产网站欧美日韩免费精品在线观看| 亚洲成人久久一区| 国产v综合ⅴ日韩v欧美大片| 精品视频在线播放| 久久成人av网站| 国产精品视频一区二区高潮| 日本精品中文字幕| 2018国产精品视频| 精品精品国产国产自在线| 亚洲加勒比久久88色综合| 2019国产精品自在线拍国产不卡| 中文字幕日韩在线视频| 91av视频在线免费观看| 久久综合免费视频影院| 久久久久久久久久久91| www国产91| 国产精品一区二区三| 国产91热爆ts人妖在线| 在线观看精品自拍私拍| 在线观看欧美成人| 日韩在线观看你懂的|