Android 自定義View事件分發(fā)機制

一、前言:

1. MotionEvent事件

手指接觸屏幕后所產(chǎn)生的事件乔宿,主要有如下幾種:

ACTION_DOWN:手指接觸屏幕
ACTION_MOVE:手指在屏幕上移動
ACTION_UP:手指從屏幕上松開

2. 通常有如下的一系列事件:

1描验、點擊屏幕后離開松開栏豺,事件序列為DOWN->UP
2牌废、點擊屏幕滑動一會再松開章贞,事件序列為DOWN->MOVE->MOVE->…->MOVE->UP

3. 通過 MotionEvent 對象獲得點擊事件的x和 y 坐標:

  • getX()/getY() 返回的是相對于當前 View 左上角的x 和 y 坐標肖方;
  • getRawX()/getRawY() 返回的是相對于手機屏幕左上角的x 和 y 坐標闺魏;

二、View的事件分發(fā)機制

1. 事件傳遞的順序

Activity-->Window-->decor view-->我們的layout俯画,ViewGroup-->我們布局中被點擊的子View

如果我們的子View沒有處理事件析桥,那事件就會反向向上傳遞回來:

我們布局中被點擊的子View-->上層的ViewGroup-->decor view-->Window-->Activity

如果所有的View都沒有消耗事件,那最后事件會傳回到Activity艰垂,由Activity處理(Activity的onTouchEvent()方法被調(diào)用)

2. View事件分發(fā)的三大方法:

ViewGroup中有3個跟事件分發(fā)有關(guān)的方法泡仗,分別是 dispatchTouchEvent、 onInterceptTouchEvent猜憎、onTouchEvent娩怎。

(1)dispatchTouchEvent方法

  1. dispatchTouchEvent方法用來進行事件的分發(fā)。
  2. 事件傳遞到當前View時胰柑,這個方法就會被調(diào)用截亦。
  3. dispatchTouchEvent方法里面包含了具體的事件分發(fā)邏輯,返回結(jié)果受當前View的onTouchEvent方法和下級View的dispatchTouchEvent方法的影響柬讨。

(2)onInterceptTouchEvent方法

  1. onInterceptTouchEvent方法在dispatchTouchEvent方法內(nèi)部被調(diào)用崩瓤,用來判斷是否攔截某個事件。
  2. 如果當前View攔截了某個事件踩官,那么在同一個事件序列當中却桶,此方法不會被再次調(diào)用,返回結(jié)果表示是否攔截當前事件。
  3. 這個方法只有VewGroup中有颖系,View中沒有嗅剖。

(3)onTouchEvent方法

在dispatchTouchEvent方法中調(diào)用,用來處理點擊事件嘁扼,返回結(jié)果表示是否消耗當前事件信粮,如果不消耗,則在同一個事件序列中偷拔,當前View無法再次接收到事件蒋院。

3. onTouchListener亏钩、onTouchEvent莲绰、onClickListener的優(yōu)先級:

(1)onTouchListener和onTouchEvent都在dispatchTouchEvent方法中被調(diào)用,onClickListener在onTouchEvent方法中被調(diào)用

(2)onTouchListener的優(yōu)先級高于onTouchEvent方法姑丑,如果onTouchListener的onTouch方法返回true蛤签,則onTouchEvent方法不會被調(diào)用,當然onClickListener就更不會被調(diào)用了

(3)在onTouchEvent方法中栅哀,如果當前View設(shè)置了onClickListener震肮,那么onClickListener的onClick方法會被調(diào)用

(4)只要View的CLICKABLE和LONKG_CLICKABLE有一個為true,View就會消耗當前事件留拾,也就是說onTouchEvent方法最后會返回true戳晌。

(5)View的LONG_CLICKABLE屬性默認為false,而CLICKABLE屬性和具體的View有關(guān)痴柔,可點擊的View的CLICKABLE屬性為true沦偎,不可點擊的View的CLICKABLE屬性為false。

4. ViewGroup中的事件分發(fā)邏輯

ViewGroup中的事件分發(fā)邏輯可以用一段偽代碼來表述

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

從上述的偽代碼中我們可以總結(jié)出ViewGroup中的事件分發(fā)流程:
(1)事件傳遞到ViewGroup時咳蔚,dispatchTouchEvent方法會被調(diào)用豪嚎。如果這個ViewGroup的onInterceptTouchEvent方法返回true,則表示它要攔截事件谈火,事件就會交給當前ViewGroup的onTouchEvent方法處理侈询。

(2)如果當前ViewGroup的onInterceptTouchEvent返回false,即不攔截事件糯耍,則會調(diào)用子元素的dispatchTouchEvent方法扔字,這樣就把事件傳遞給了子元素。

(3)如果子元素沒有消耗事件温技,也就是子元素的dispatchTouchEvent方法返回false啦租,那事件會由當前ViewGroup自己處理,當前ViewGroup的onTouchEvent會被調(diào)用荒揣。如果當前ViewGroup的dispatchTouchEvent方法也返回false篷角,最后就會一層層往上,如果事件一直沒有被消耗系任,那么最后Activity的onTouchEvent方法會被調(diào)用恳蹲。

(4)這里需要理解一下的是ViewGroup繼承自View虐块,ViewGroup中并沒有onTouchEvent方法。在所有子元素沒有消耗事件時嘉蕾,ViewGroup會調(diào)用父類贺奠,也就是View的dispatchTouchEvent方法,從而調(diào)用到onTouchEvent方法來自己處理事件错忱,如果自己沒有消耗事件儡率,dispatchTouchEvent方法就會返回false,從而將事件反向往上層傳遞以清。

(5)如果ACTION_DOWN事件子元素沒處理(onTouchEvent返回false)儿普,那這個事件序列的其他事件(MOVE和UP事件)都不會再分派給子元素處理。

(6)ViewGroup默認不攔截任何事件掷倔。

(7)對于ACTION_DOWN事件眉孩,ViewGroup每次都會調(diào)用onInterceptTouchEvent方法來判斷是否需要攔截事件,一旦確定要攔截事件勒葱,后續(xù)的ACTION_MOVE和ACTION_UP事件都ViewGroup自己處理浪汪,不會傳遞給子View,也不會再調(diào)用onInterceptTouchEvent方法凛虽。所以onInterceptTouchEvent方法不是每次事件都會被調(diào)用的死遭。

(8)子View可以通過requestDisallowInterceptTouchEvent方法來干預父元素的除了ACTION_DOWN意外的事件分發(fā)過程

5. View中的事件分發(fā)邏輯

requestDisallowInterceptTouchEvent方法

requestDisallowInterceptTouchEvent方法用于影響父元素的事件攔截策略,requestDisallowInterceptTouchEvent(true)凯旋,表示不允許父元素攔截事件呀潭,這樣事件就會傳遞給子View。一般這個方法子View用的多瓦阐,可以用來處理滑動沖突問題蜗侈。

事件分發(fā)邏輯
(1)View中沒有onInterceptTouchEvent方法,所以一旦事件傳遞到View睡蟋,那么View的dispatchTouchEvent方法就會被調(diào)用踏幻。

(2)dispatchTouchEvent方法中處理事件的邏輯順序是onTouchListener-->onTouchEvent-->onClickListener。

(3)也就是說如果View設(shè)置了onTouchListener戳杀,那onTouchListener的onTouch方法會被調(diào)用该面,如果onTouch方法返回true,那事件就被消耗了信卡,事件分發(fā)結(jié)束隔缀,onTouchEvent不會被調(diào)用。

(4)如果onTouch方法返回false傍菇,那么onTouchEvent就會被調(diào)用猾瘸。如果View設(shè)置了onClickListener,當ACTION_UP事件到來時,onTouchEvent中的onClickListener的onClick方法也會被調(diào)用牵触。

(5)View一般都會消耗事件淮悼,如果View沒有消耗ACTION_DOWN事件,那后面ACTION_MOVE和ACTION_UP就都不會傳遞給View揽思。

三袜腥、View的滑動沖突解決方式

具體解決參考:http://www.reibang.com/p/7f929fae4772

1. 外部攔截法:

所謂外部攔截法是指點擊事件都經(jīng)過父容器的攔截處理,如果父容器需要此事件就攔截钉汗,如果不需要此事件就不攔截羹令,這樣就可以解決滑動沖突的問題,這種比較符合點擊事件的分發(fā)機制损痰。外部攔截法需要重寫父類容器的 onInterceptTouchEvent 方法福侈,在內(nèi)部做相應的攔截處理即可,代碼如下:

public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted = false;
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN: {
        intercepted = false;
        break;
      }
      case MotionEvent.ACTION_MOVE: {
        if (滿足父容器的攔截要求) {
          intercepted = true;
        } else {
          intercepted = false;
        }
        break;
      }
      case MotionEvent.ACTION_UP: {
        intercepted = false;
        break;
      }
      default:
        break;
    }
    mLastXIntercept = x;
    mLastYIntercept = y;
    return intercepted;
  }
  • 根據(jù)實際的業(yè)務需求徐钠,判斷是否需要處理 ACTION_MOVE 事件癌刽,如果父 View 需要處理則返回 true役首,否則返回 false 并交由子 View 去處理
  • ACTION_DOWN 事件需要返回 false尝丐,父容器不能進行攔截,否則根據(jù) View 的事件分發(fā)機制衡奥,后續(xù)的 ACTION_MOVE 與 ACTION_UP 事件都將默認交由父容器進行處理
  • 原則上 ACTION_UP 事件也需要返回 false爹袁,如果返回 true,那么子 View 將接收不到 ACTION_UP 事件矮固,子 View 的onClick 事件也無法觸發(fā)

2. 內(nèi)部攔截法:

內(nèi)部攔截法是指父容器不攔截任何事件失息,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉档址,否則就交由父類容器進行處理盹兢,這種方法和 Android 中的事件分發(fā)機制不一致,需要配合 requestDisallowInterceptTouchEvent 方法才能正常工作守伸。

它的代碼如下绎秒,我們需要重寫子元素的 dispatchTouchEvent 方法:

public boolean dispatchTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN: {
        parent.requestDisallowInterceptTouchEvent(true);
        break;
      }
      case MotionEvent.ACTION_MOVE: {
        int deltaX = x - mLastX;
        int deltaY = y - mLastY;
        if (父容器需要此類點擊事件) {
          parent.requestDisallowInterceptTouchEvent(false);
        }
        break;
      }
      case MotionEvent.ACTION_UP: {
        break;
      }
      default:
        break;
    }
    mLastX = x;
    mLastY = y;
    return super.dispatchTouchEvent(event);
  }

父容器修改其 onInterceptTouchEvent 方法

public boolean onInterceptTouchEvent(MotionEvent event) {
    int action = event.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
      return false;
    } else {
      return true;
    }
  }
  • 內(nèi)部攔截法要求父容器不能攔截 ACTION_DOWN 事件,否則一旦父容器攔截 ACTION_DOWN 事件尼摹,那么后續(xù)的觸摸事件都不會傳遞給子View
  • 滑動策略的邏輯放在子 View 的 dispatchTouchEvent 方法的 ACTION_MOVE 事件中见芹,如果父容器需要處理事件則調(diào)用 parent.requestDisallowInterceptTouchEvent(false) 方法讓父容器去攔截事件

注意點:

  • 同樣的對于ACTION_DOWN事件,onInterceptTouchEvent方法必須返回false蠢涝,其他事件默認返回true
  • 在子View的dispatchTouchEvent方法中玄呛,對于ACTION_DOWN事件,通過調(diào)用requestDisallowInterceptTouchEvent(true)默認不允許父布局攔截事件和二,這樣后續(xù)事件都交給子View處理
  • 在子View的dispatchTouchEvent方法中徘铝,對于ACTION_MOVE事件,默認是子View處理,在需要父布局處理時惕它,調(diào)用requestDisallowInterceptTouchEvent(false)方法來讓父布局攔截事件场晶,交給父布局處理。

作者:xxq2dream
鏈接:http://www.reibang.com/p/7d50a6b0b6af
鏈接:https://www.jb51.net/article/143844.htm
原文鏈接:https://blog.csdn.net/j675620982/article/details/81775593

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怠缸,一起剝皮案震驚了整個濱河市诗轻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌揭北,老刑警劉巖扳炬,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異搔体,居然都是意外死亡恨樟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門疚俱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劝术,“玉大人,你說我怎么就攤上這事呆奕⊙” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵梁钾,是天一觀的道長绳泉。 經(jīng)常有香客問我,道長姆泻,這世上最難降的妖魔是什么零酪? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮拇勃,結(jié)果婚禮上四苇,老公的妹妹穿的比我還像新娘。我一直安慰自己方咆,他們只是感情好月腋,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著峻呛,像睡著了一般罗售。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钩述,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天寨躁,我揣著相機與錄音,去河邊找鬼牙勘。 笑死职恳,一個胖子當著我的面吹牛所禀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播放钦,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼色徘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了操禀?” 一聲冷哼從身側(cè)響起褂策,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎颓屑,沒想到半個月后斤寂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡揪惦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年遍搞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片器腋。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡溪猿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纫塌,到底是詐尸還是另有隱情诊县,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布护戳,位于F島的核電站翎冲,受9級特大地震影響垂睬,放射性物質(zhì)發(fā)生泄漏媳荒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一驹饺、第九天 我趴在偏房一處隱蔽的房頂上張望钳枕。 院中可真熱鬧,春花似錦赏壹、人聲如沸鱼炒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昔瞧。三九已至,卻和暖如春菩佑,著一層夾襖步出監(jiān)牢的瞬間自晰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工稍坯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酬荞,地道東北人搓劫。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像混巧,于是被迫代替她去往敵國和親枪向。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容