Android事件分發(fā)詳解以及需要注意的細(xì)節(jié)

關(guān)于android中的事件分發(fā) 可以查看這篇文章:
http://www.reibang.com/p/38015afcdb58

在文章中 用一張圖片總結(jié)了android事件分發(fā)的主要過程:

android事件分發(fā)

從這張圖上 我們可以說已經(jīng)把android的事件分發(fā)過程看了個大概 但是實際上 上述的過程只是android事件分發(fā)的一個部分 在實際的事件處理中 已經(jīng)遇見更復(fù)雜的情況 那時 事件不一定按這張圖的過程走 也就是我們需要在事件分發(fā)過程中注意的“細(xì)節(jié)”(在上面提到的文章中也說起過這部分的事 也就是所謂的“事件后續(xù)” 這里我們會詳細(xì)的解釋下這部分的知識)

先設(shè)置以下代碼:
1.MyGroupView 我們的 外層-父布局

public class MyGroupView extends ViewGroup {

    public MyGroupView(Context context) {
        super(context);
    }

    public MyGroupView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyGroupView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        getChildAt(0).layout(l, t, r, b);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("測試用", "MyGroupView" + ";" + "dispatchTouchEvent | action:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("測試用", "MyGroupView" + ";" + "onInterceptTouchEvent | action:" + ev.getAction());
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyGroupView" + ";" + "onTouchEvent | action:" + event.getAction());
        return false;
    }
}

2.MyView 我們的子view

public class MyView extends View {

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyView" + ";" + "dispatchTouchEvent | action:" + event.getAction());
            return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyView" + ";" + "onTouchEvent | action:" + event.getAction());
        return false;
    }
}

3.在activity的xml文件中

    <com.example.godru.demozhurui.MyGroupView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.example.godru.demozhurui.MyView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </com.example.godru.demozhurui.MyGroupView>

現(xiàn)在 我們有了用于測試的代碼 現(xiàn)在來談?wù)勱P(guān)于事件的細(xì)節(jié):
ps:在android中 事件以int類型表示 0-DOWN 1-UP 2-MOVE 3-CANCEL 具體可以點擊進android的MotionEvent類中詳細(xì)查看

1.只要View在onTouch中返回false 就會將事件向上傳遞到ViewGroup嗎扬蕊?

從上述的事件分發(fā)圖中好像是這么一回事 現(xiàn)在我們用上文代碼測試一下
先假設(shè)事情會按圖上走 那么我們想看到的log應(yīng)該是:
MyGroupView dispatchTouchEvent DOWN
MyGroupView onInterceptTouchEvent DOWN
MyView dispatchTouchEvent DOWN
MyView onTouchEvent DOWN
MyGroupView onTouchEvent DOWN

MyGroupView dispatchTouchEvent MOVE
MyGroupView onInterceptTouchEvent MOVE
MyView dispatchTouchEvent MOVE
MyView onTouchEvent MOVE
MyGroupView onTouchEvent MOVE
....

現(xiàn)在看看實際結(jié)果:

只有DOWN事件走過了流程

我在測試中實際動作并不只有點擊 而是有劃動的 但是圖上的結(jié)果卻很奇怪 不但是沒有move事件 連up事件都沒有出現(xiàn) 這是為什么圆丹?

這就是Android事件分發(fā)中關(guān)于事件的一個細(xì)節(jié):android對手勢事件是如何定性的谤专?

Android對事件的定義

在android中 一個完整的事件是由DOWN事件“開頭” 以UP或CANCEL(這個事件后面會說到)“結(jié)束” 這整個過程被認(rèn)為是一個“完整”的事件

而重點就在于這個“開頭” android的事件不是時時刻刻都在分發(fā)中 為了性能 為了不必要的浪費

---只有那些”表示“需要這個事件的控件才會被分發(fā)后續(xù)的事件---

而這個”需要“就是指在onTouchEvent 或者 dispatchTouchEvent中對DOWN事件返回ture(只有對DOWN返回true才有這個功能 因為DOWN事件是一切事件的開始 其它事件 如:MOVE 還是會按照流程圖的路線行走) 也就是向android表示本控件需要本次事件以及后續(xù)的其它事件 這樣才會接收到后續(xù)MOVE和UP
如果在一個事件走完了整個流程 還是沒有任何控件”消費“DOWN事件 android就認(rèn)為這個事件沒有任何控件想要 事件也就被取消了

2.ViewGroup在dispatchTouchEvent中返回默認(rèn)值 且在onInterceptTouchEvent 返回false時 一定會將事件傳遞到View嗎华望?

我們將上代碼中的ViewGroup改為:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("測試用", "MyGroupView" + ";" + "dispatchTouchEvent | action:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("測試用", "MyGroupView" + ";" + "onInterceptTouchEvent | action:" + ev.getAction());
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyGroupView" + ";" + "onTouchEvent | action:" + event.getAction());
        return true;
    }

View的代碼改為:

   @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyView" + ";" + "dispatchTouchEvent | action:" + event.getAction());
            return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyView" + ";" + "onTouchEvent | action:" + event.getAction());
        return false;
    }

這次有控件在onTouchEvent中返回了true 所以事件應(yīng)該會有后續(xù)傳遞來 我們理想中的情況是:

MyGroupView dispatchTouchEvent DOWN
MyGroupView onInterceptTouchEvent DOWN
MyView dispatchTouchEvent DOWN
MyView onTouchEvent DOWN
MyGroupView onTouchEvent DOWN

MyGroupView dispatchTouchEvent MOVE
MyGroupView onInterceptTouchEvent MOVE
MyView dispatchTouchEvent MOVE
MyView onTouchEvent MOVE
MyGroupView onTouchEvent MOVE
....

但是實際情況卻是:
DOWN事件后沒有再向下傳遞事件

從上圖可以看出 事件并非是什么時候到在向下傳遞的 這也和上文中說到的”事件需求者“有關(guān)

Android中事件傳遞的意義

我們發(fā)現(xiàn)事件并不總是在向下傳遞的 這是因為android向下傳遞后又向上返回的這整個過程的本質(zhì)就是在尋求”需求者“ 需要事件的控件返回 要不在dispatchTouchEvent中返回true 要不在onTouchEvent中返回true 以確定是否由自己來處理這個事件
而在android中 它們的設(shè)計是一個事件面向一個對象 不會出現(xiàn)一個事件由多個控件來處理的情況(你可以通過代碼來實現(xiàn) 但是事件分發(fā)機制本身只會以一個控件為目標(biāo)來傳遞事件)所以在已經(jīng)明確了處理者后 再繼續(xù)向下傳遞事件是沒有意義的

---誰需求 誰處理 即使還存在子控件---

也就是說 android不但只在確定有人”需求“事件后才傳遞后續(xù)事件 且控件本身在明確了由自己處理后也只接收后續(xù)事件了 不再繼續(xù)傳遞給自己的子控件

3.ViewGroup是否一定可以通過onInterceptTouchEvent返回true的方式來獲取事件?

從上文中我們已經(jīng)知道了事件分發(fā)的流程 并且應(yīng)該也可以發(fā)現(xiàn) 這個流程中 其實子view是非常被動的 因為無論DOWN事件是否是被子view消費 ViewGroup都可以用onInterceptTouchEvent來攔截事件 因為事件一定會經(jīng)過它 那是否意味著 我們可以無限制的使用onInterpecTouchEventt來獲取事件呢厂镇? 答案是 不是的 因為ViewGroup有一個公共方法:
requestDisallowInterceptTouchEvent(true);

這個方法的描述是鎖定ViewGroup的攔截方法 事實上就是確保事件一定不會被攔截 和一定會被攔截

我們修改下代碼來測試一下

MyGroupView:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("測試用", "MyGroupView" + ";" + "dispatchTouchEvent | action:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("測試用", "MyGroupView" + ";" + "onInterceptTouchEvent | action:" + ev.getAction());
        return ev.getAction() != MotionEvent.ACTION_DOWN;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyGroupView" + ";" + "onTouchEvent | action:" + event.getAction());
        return true;
    }

MyView:

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyView" + ";" + "dispatchTouchEvent | action:" + event.getAction());
            return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyView" + ";" + "onTouchEvent | action:" + event.getAction());
        if (event.getAction() == MotionEvent.ACTION_DOWN)
            getParent().requestDisallowInterceptTouchEvent(true);
        return true;
    }

這次我們在攔截方法中除了DOWN事件以外都返回true 在View里 我們在接收到DOWN事件后 對父布局使用一次requestDisallowInterceptTouchEvent() 鎖定攔截方法

一般理想過程應(yīng)該是:
MyGroupView dispatchTouchEvent DOWN
MyGroupView onInterceptTouchEvent DOWN
MyView dispatchTouchEvent DOWN
MyView onTouchEvent DOWN

MyGroupView dispatchTouchEvent MOVE
MyGroupView onInterceptTouchEvent MOVE
MyGroupView onTouchEvent MOVE
MyGroupView dispatchTouchEvent MOVE
MyGroupView onTouchEvent MOVE
MyGroupView dispatchTouchEvent MOVE
MyGroupView onTouchEvent MOVE
....

實際運行情況:


攔截方法沒有起到作用

如上 攔截方法甚至都沒有走到過 因為requestDisallowInterceptTouchEvent方法會影響ViewGroup的鎖定判斷 這個判斷的益在onInterceptTouchEvent方法調(diào)用前 所以鎖定開啟后 甚至都不會走這個方法

這個方法的作用是避免本View獲取的事件被其它View攔截
本方法的存在也可以解釋為什么有的view的事件無法被攔截的問題

但是這個方法說到底還是子view對父view的調(diào)用 所以如果DOWN事件都被直接攔截 這個方法也就沒有任何作用了

4.CANCEL事件的意義 以及 使用了攔截方法就會直接調(diào)用其onTouchEvent方法嗎

CANCEL事件在有的文章中介紹是手指滑動到超過手機屏幕時出現(xiàn) 其實這個說法是錯誤的 因為實際在這種情況下 出現(xiàn)的仍然是UP事件
要再現(xiàn)CANCEL事件可以直播修改代碼:

MyGroupView:

   @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("測試用", "MyGroupView" + ";" + "dispatchTouchEvent | action:" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("測試用", "MyGroupView" + ";" + "onInterceptTouchEvent | action:" + ev.getAction());
        return ev.getAction() != MotionEvent.ACTION_DOWN;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyGroupView" + ";" + "onTouchEvent | action:" + event.getAction());
        return true;
    }

MyView:

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyView" + ";" + "dispatchTouchEvent | action:" + event.getAction());
            return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("測試用", "MyView" + ";" + "onTouchEvent | action:" + event.getAction());
        return true;
    }

運行效果:


CANCEL事件出現(xiàn)

從圖中 我們就可以解答一開始的兩個問題
CANCEL事件其實是在事件被view攔截時 由被攔截的事件轉(zhuǎn)化而來 是一個代碼性質(zhì)上的事件
攔截方法在攔截后并不是會直接調(diào)用onTouchEvent方法 而是向下傳遞了一個CANCEL事件

這是因為android中事件本身是有目標(biāo)的(也就是需求者)在事件傳遞的過程中 決定是否向下和向上傳遞的實際是view自己
如果一個事件沒有傳遞到目標(biāo)view 而是被攔截了 android不會讓目標(biāo)view一無所知 而是向他傳遞一個CANCEL事件 表示你已經(jīng)不是需求者了 之后才會繼續(xù)向新的需求者傳遞事件

這本質(zhì)上也符合之前所說的事件-控件一一對應(yīng)的原則

總結(jié)

1.文章最上方的事件傳遞圖只對事件的開始-DOWN事件完全適用 后續(xù)事件的情況與其不一樣

2.android中事件的開始是DOWN事件 只有在有控件確定自己為需求者(消費了DOWN事件)的情況下 后續(xù)事件才會出現(xiàn) 不然就沒有后續(xù)事件

對DOWN事件的消費有兩種:
在dispatchTouchEvent里返回true or 在onTouchEvent里返回true

3.除DOWN事件外 后續(xù)事件傳遞的對象都是消費了DOWN事件的控件 傳遞到這個控件后 無論其是否在onTouchEvent或dispatchTouchEvent里消費與否 都會繼續(xù)向這個控件傳遞后續(xù)事件(直到UP事件或CANCEL事件出現(xiàn) 意味著本次事件的結(jié)束)

4.在已經(jīng)確定了“需求者”的情況下控件不再會因為像文章頂部的圖中一樣 在onTouchEvent或dispatchTouchEvent里返回了false 而將事件向上返回 因為已經(jīng)確定了需求者

同理 在確定了“需求者”的情況下 事件也不會因為控件在dispatchTouchEvent里返回默認(rèn)值 和在攔截方法onInterceptTouchEvent里返回false 而把事件向下傳遞

5.攔截事件onInterceptTouchEvent可以讓ViewGroup強行確定自己“需求者”的地位 這個方法在返回了一次true后 就會讓后續(xù)事件向它傳遞 之后不會再調(diào)用 而在攔截開始時 被攔截的事件并不是直接就返回到ViewGroup的onTouchEvent方法中 而是將這個事件轉(zhuǎn)化為一個CANCEL事件 向下傳遞到了之前的“需求者”控件 之后的事件才會傳遞到ViewGroup

6.可以對一個viewGroup調(diào)用requestDisallowInterceptTouchEvent來使其攔截方法失效或開啟 這個方法可以用于讓子view避免事件被父view攔截 設(shè)置為true會讓ViewGroup不經(jīng)過攔截方法 直接按false的路線傳遞事件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沦偎,一起剝皮案震驚了整個濱河市迁筛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌端三,老刑警劉巖舷礼,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異郊闯,居然都是意外死亡妻献,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門团赁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來育拨,“玉大人,你說我怎么就攤上這事欢摄“旧ィ” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵怀挠,是天一觀的道長析蝴。 經(jīng)常有香客問我害捕,道長,這世上最難降的妖魔是什么闷畸? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任尝盼,我火速辦了婚禮,結(jié)果婚禮上腾啥,老公的妹妹穿的比我還像新娘东涡。我一直安慰自己,他們只是感情好倘待,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布疮跑。 她就那樣靜靜地躺著,像睡著了一般凸舵。 火紅的嫁衣襯著肌膚如雪祖娘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天啊奄,我揣著相機與錄音渐苏,去河邊找鬼。 笑死菇夸,一個胖子當(dāng)著我的面吹牛琼富,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庄新,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼鞠眉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了择诈?” 一聲冷哼從身側(cè)響起械蹋,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羞芍,沒想到半個月后哗戈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡荷科,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年唯咬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畏浆。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡副渴,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出全度,到底是詐尸還是另有隱情煮剧,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站勉盅,受9級特大地震影響佑颇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜草娜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一挑胸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宰闰,春花似錦茬贵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至葡盗,卻和暖如春螟左,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背觅够。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工胶背, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喘先。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓钳吟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親窘拯。 傳聞我的和親對象是個殘疾皇子红且,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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