View的事件分發(fā)機制小結

事件的分發(fā)原理圖:

  1. 對于一個root viewgroup來說毕莱,如果接受了一個點擊事件吧黄,那么首先會調用他dispatchTouchEvent方法.
  2. viewgroup的onInterceptTouchEvent 返回true廓八,那就代表要攔截這個事件.接下來這個事件就給viewgroup自己處理了,從而viewgroup的onTouchEvent方法就會被調用.
  3. viewgroup的onInterceptTouchEvent返回false就代表我不攔截這個事件,然后就把這個事件傳遞給自己的子元素,然后子元素的dispatchTouchEvent就會被調用简烤,就是這樣一個循環(huán)直到 事件被處理.

我們可以看下事件分發(fā)的原理圖.
圖1:

img-w400

簡單的說只要各事件不消費,返回false,分發(fā)就會一直走下去:
dispatchTouchEvent(false) -> onInterceptTouchEvent(false) -> onTouchEvent(false) - 事件結束

重要的事情說一遍:

也就是說在任何View或者ViewGrop中只要它想消費Touch事件,就直接onInterceptTouchEvent(true),這樣它就不會把事件傳下去給孩子view了,而是自己消費.

知其人先知其心,我們繼續(xù)進一步了解下其他事件分發(fā)的api.

  • dispatchTouchEvent 分發(fā)事件
    return false; //不是目標對象,則分發(fā),默認false;
    return true; // 是目標view,則不分發(fā);

dispatchTouchEvent作用是將touch事件向下傳遞直到遇到被觸發(fā)的目標view.
我們可以通過返回的boolean對touch事件的分發(fā)進行處理,是否要向下分發(fā)尋找目標view,當然這個方法也可以被重載,手動分配事件.

  • onInterceptTouchEvent 攔截事件
    return false; //表示不攔截,默認false;
    return true; // 表示攔截;

攔截是相當于它的孩子,也就是說不會攔截自己.
如果攔截,則TouchEvent會傳給他自己,而它孩子是接收不了.
如果不攔截會繼續(xù)往他的孩子遞歸是否onInterceptTouchEvent需要攔截.

  • onTouchEvent 觸摸事件
    return false; //表示不消費,默認false;
    return true; // 表示消費;

當onInterceptTouchEvent 確認攔截,會問自己是否要消費TouchEvent,如果攔截了又不消費則,那么Touch結束.

  • invalidate 重新繪制
    讓整個view失效,這樣view會被重新調用, 配合onDraw()使用.

下面是調用流程:

  1. 當invalidate時會重新調用draw方法;
  2. draw會調用onDraw,而在draw內還會調用computeScroll();
  3. 此時如果想讓computeScroll()循環(huán)被調用可以在computeScroll()內自己調用postInvaildate()重新繪制;

invalidate刷新UI步驟: draw() -> onDraw() -> computeScroll()

computeScroll() 源碼是空實現(xiàn),具體實現(xiàn)由自己來寫.

開發(fā)中事件分發(fā)的常見問題(重點)

  • view的onTouchEvent棵逊,OnClickListerner和OnTouchListener的onTouch方法 三者優(yōu)先級如何徒像?
    答:
    onTouchListener優(yōu)先級最高旁涤,也就是說如果onTouch方法返回 true ,那么事件結束,反之如果返回false,那么onTouchEvent 講會被調用,至于OnClickListerner 優(yōu)先級是最低的.
    優(yōu)先級如下:
    OnTouchListener > onTouchEvent > OnClickListerner

  • 點擊事件的傳遞順序如何?
    答:

  • Activity > Window > View.從上到下依次傳遞.

  • 如果你最低的那個view onTouchEvent返回false 那就說明他不想處理 那就再往下拋注祖,都不處理的話最終就還是讓Activity自己處理了浸卦。

  • 舉個例子靴庆,pm下發(fā)一個任務給leader,leader自己不做 給架構師a,小a也不做 給程序員b塞茅,b如果做了那就結束了這個任務鞭光。b如果發(fā)現(xiàn)自己搞不定,那就找a做佩伤,a要是也搞不定 就會不斷向上發(fā)起請求结序,最終可能還是pm做邀层。

  • 總結下流程: view的事件分發(fā)會從上往下,只要在子不消費的情況,又會接著從下往上,最后結束.

//activity的dispatchTouchEvent 方法 一開始就是交給window去處理的
//win的superDispatchTouchEvent 返回true 那就直接結束了 這個函數(shù)了秸谢。返回false就意味
//這事件沒人處理估蹄,最終還是給activity的onTouchEvent 自己處理 這里的getwindow 其實就是phonewindow
 public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
//來看phonewindow的這個函數(shù) 直接把事件傳遞給了mDecor
 @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
//devorview就是 我們的rootview了 就是那個framelayout 我們的setContentView里面?zhèn)鬟f的那個layout
//就是這個decorview的 子view了
     @Override
    public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
    }
  • enable是否影響view的onTouchEvent返回值?
    我們知道其實enable的優(yōu)先級高于cliable,當enable=false時會屏蔽view的點擊事件.而事實上enable=false并不會影響onTouchEvent返回true.

答:

  • 不影響,只要clickable和longClickable有一個為真怜庸,那么onTouchEvent就返回true嘉栓。
  • 設置了enable為false的話馋辈,onClick事件是完全屏蔽的答毫,而clickable屬性就要看設置屬性和設置OnClicListener的先后順序了.

我們可以看下面demo
xml代碼:

android:clickable="true"
android:enabled="false"

android代碼:

//我在XML布局中設置了enabled="false",雖然是屏蔽了點擊事件,但是在自定義Button中,實現(xiàn)的`onTouchEvent`方法還是會返回true.
public class MyButton extends Button {
    public MyButton(Context context) {
        super(context);
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        System.out.println("onTouchEvent:"+super.onTouchEvent(event));
        return super.onTouchEvent(event);
    }
}

輸出結果:
onTouchEvent:true

  • 滑動沖突問題如何解決思路是什么?
    答:
  • 讓誰消費滑動:
    要解決滑動沖突 其實最主要的就是有一個核心思想。你到底想在一個事件序列中,讓哪個view 來響應你的滑動?比如 從上到下滑,是哪個view來處理這個事件,從左到右呢?
  • 攔截內外滑動:
    用業(yè)務需求來想明白以后剩下的其實就很好做了。
    核心的方法就2個:
    1. 外部攔截
      也就是父攔截.(重寫父控件的onInterceptTouchEvent即可).
    2. 內部攔截
      也就是子view攔截方法.

學會這2種,基本上所有的滑動沖突.都是這2種的變種,而且核心代碼思想都一樣,下面是兩種情況是示例代碼:

  • 外部攔截法:思路就是重寫父控件的onInterceptTouchEvent即可辞槐。子元素一般不需要管。可以很容易理解,因為這和android自身的事件處理機制 邏輯是一模一樣的.
    父控件示例代碼:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
   //down事件肯定不能攔截 攔截了后面的就收不到了
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (你的業(yè)務需求) {
//如果確定攔截了 就去自己的onTouchEvent里 處理攔截之后的操作和效果 即可了
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
 //up事件 我們一般都是返回false的 一般父控件都不會攔截他。 因為up是事件的最后一步。這里返回true也沒啥意義
 //唯一的意義就是因為 父元素 up被攔截。導致子元素 收不到up事件,那子元素 就肯定沒有onClick事件觸發(fā)了,這里的
//小細節(jié) 要想明白
                intercepted = false;
                break;
            default:
                break;
        }
    return intercepted;
}
  • 內部攔截法:內部攔截法稍微復雜一點杯矩,就是事件到來的時候泌射,父控件不管拒秘,讓子元素自己來決定是否處理羹应。如果消耗了就最好煞烫,沒消耗自然就轉給父控件處理了。
    子控件示例代碼:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);//子元素自己消費,父控件不進行攔截
                break;
            case MotionEvent.ACTION_MOVE:
                if (如果父控件需要這個點擊事件) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }//否則的話 就交給自己本身view的onTouchEvent自動處理了
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return super.dispatchTouchEvent(event);
}

PS: 父控件代碼也要修改一下,其實就是保證父控件別攔截down:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            return false;
        }
        return true;
}

事件分發(fā)的項目

需求:仿QQ側滑菜單

  1. google給我們提供了DrawerLayout控件來作為側滑菜單控件,但是側滑菜單一般都是覆蓋到主頁面頂部的.
  2. 我們需求在向右拖拉滑動時可以側滑出菜單,并且要求菜單不可覆蓋到主頁面上,需求主頁面跟著側滑菜單的滑動和位移.

圖2:


img-w230

上面紅色框區(qū)域的結構可以這樣設計ScrollView+多TextView.使用scrollView的原因是當菜單的item增加時可以滾動,當然listview,rv也可以做到.

問題分析:

  1. 當點擊它任意一個孩子(TextView)時,如果ScrollView不進行onInterceptTouchEvent ,則它就不可以在菜單上進行左右滑動.
  2. 但是如果攔截了全部,則它的孩子又會消費不了TouchEvent.

問題解決:

  1. 只有左右移動的時候進行攔截,這樣父控件就擁有了TouchEvent,可在菜單上繼續(xù)左右滑動.
  2. 上下移動或靜止的時候就不攔截,這樣孩子又有了TouchEvent,那么孩子就可以點擊了.

實例代碼:

/**
 * 當滑動的時候,需要攔截TouchEvent時間,讓scrollView消化,否則會分發(fā)到孩子去;
 * 當不滑動的停止的時候,不攔截,則會分發(fā)到孩子去,也就是TexView;
 */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
    // 只有水平滑動時才攔截touch
    case MotionEvent.ACTION_DOWN:
        startX = (int) (ev.getRawX() + 0.5f);
        startY = (int) (ev.getRawY() + 0.5f);
        break;
    case MotionEvent.ACTION_MOVE:
        int newX = (int) (ev.getRawX() + 0.5f);
        int newY = (int) (ev.getRawY() + 0.5f);
        int dx = Math.abs(startX - newX);
        int dy = Math.abs(startY - newY);
        if (dx > dy) {
            // 水平滑動,只有水平滑動才會攔截事件
            return true;
        }
        startX = (int) ev.getRawX();// 初始化當前位置
    case MotionEvent.ACTION_UP:
        break;
    }
    return super.onInterceptTouchEvent(ev);
}  

參考來源:希爾瓦娜斯女神

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市强戴,隨后出現(xiàn)的幾起案子陵刹,更是在濱河造成了極大的恐慌羡宙,老刑警劉巖僧凰,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異萍启,居然都是意外死亡驳遵,警方通過查閱死者的電腦和手機唐责,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門允蚣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事蚪缀〗鹗瘢” “怎么了含衔?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵因妙,是天一觀的道長。 經(jīng)常有香客問我鳄乏,道長水援,這世上最難降的妖魔是什么奕扣? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘轰绵。我一直安慰自己振亮,他們只是感情好阶牍,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布生宛。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪誊酌。 梳的紋絲不亂的頭發(fā)上比勉,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音,去河邊找鬼屎慢。 笑死悔雹,一個胖子當著我的面吹牛,可吹牛的內容都是我干的臼隔。 我是一名探鬼主播伊磺,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼峻黍!你這毒婦竟也來了轻局?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎克饶,沒想到半個月后奈偏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡秒赤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了游沿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出躲查,到底是詐尸還是另有隱情镣煮,我是刑警寧澤介衔,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布寒波,位于F島的核電站页屠,受9級特大地震影響牢贸,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一伍俘、第九天 我趴在偏房一處隱蔽的房頂上張望邪锌。 院中可真熱鬧,春花似錦癌瘾、人聲如沸觅丰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妇萄。三九已至蜕企,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嚣伐,已是汗流浹背糖赔。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留轩端,地道東北人放典。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像基茵,于是被迫代替她去往敵國和親奋构。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容