Android滑動沖突學習心得

本文主要參照郭霖大神的博客坊夫,配合食用效果更佳哦

最近在寫學長布置的作業(yè)的時候,為了模仿QQ的滑動菜單功能荷腊,走了點彎路自己寫了個slidingmenu出來,結(jié)果一運行急凰,爆炸了女仰。外層的HorizontalScrollView對觸摸事件異常敏感,為了解決這個問題抡锈,花了幾天的時間研究了下Android的事件分發(fā)過程疾忍,下面是我的一點心得,歡迎大家指正~

注:本文主要是從Android源碼角度解釋滑動沖突的產(chǎn)生原因床三,想要直接得到滑動沖突的解決辦法的一罩,請出門左拐,百度谷歌


View與ViewGroup

首先撇簿,滑動事件必須有個載體聂渊,你得有個給你滑你才可以滑對不差购?那么Android里,這個東西就是View和ViewGroup
因此在分析滑動沖突之前汉嗽,我們先對View與ViewGroup涉及到的有關(guān)滑動方面的方法做以下列舉

View涉及到的方法:
  • public boolean dispatchTouchEvent(MotionEvent ev)

用于對觸摸事件的分發(fā)欲逃,一個觸摸事件到底交給哪一個View來處理,由這個方法分配饼暑,源碼是

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}  

  • public boolean onTouch(View v, MotionEvent event)

如果想讓該View優(yōu)先執(zhí)行某一個觸摸事件稳析,可以用view.setOnTouchListenrer()重寫onTouch方法就行了。

從dispatchTouchEvent的源碼里我們可以看到
If(view實現(xiàn)了touchListener0 && 該控件是可以點的 && onTouch的返回值是true

該事件就返回true分發(fā)完畢弓叛,不會再執(zhí)行onTouchEvent中的代碼彰居,而如果onTouch返回值為false,則將執(zhí)行onTouchEvent中的代碼
那么我們可以將onTouch返回true理解為已經(jīng)消費了該事件邪码,返回false理解為還沒有消費該事件裕菠,該次觸摸事件可以被繼續(xù)響應
因此我們可以把這個方法看做谷歌暴露給用戶的一個接口,可以借此屏蔽該控件自帶的觸摸事件的邏輯判斷闭专。


  • public boolean onTouchEvent(MotionEvent event)

    當觸摸事件被分配給某個View處理時奴潘,如果這個View沒有實現(xiàn)onTouch,則將該事件交給這個事件的onTouchEvent處理影钉,這個方法可以理解為該View對觸摸事件的默認處理方法
    這里涉及到一個touch事件的層級傳遞的知識點画髓。我們知道,一個touch事件至少由一次Down事件平委,任意個Move事件奈虾,還有一次Up事件,順序組成廉赔,只有當前面的事件返回true時肉微,后面的事件才能正常運行,否則終止執(zhí)行蜡塌。所以我們一般只在Up中返回false碉纳,其它的情況下返回true
    注意:千萬不要把onTouchEvent返回值的作用和onTouch返回值的作用混淆了。如果你去百度搜onTouchEvent返回值馏艾,那些大佬們都說什么true就是消費了false就是沒消費劳曹,那都是對onTouch的解釋。兩者并沒有任何關(guān)系....
    關(guān)于這一點我是看郭霖大神的博客才徹底弄懂琅摩,之前困擾了我好久好久铁孵,還是郭霖大法好啊~

ViewGroup涉及到的方法:

view的三個方法ViewGroup都有涉及,onTouchEvent和onTouch兩個方法與View的差不多房资,ondispatchTouchEvent與View的并不相同蜕劝,代碼量多而且涉及到了Android的事件分發(fā)機制,分析它的源碼上郭霖大神已經(jīng)講的很詳細了。不過它還有一個獨特的方法也是我們解決滑動沖突的一個重要方法

  • public boolean onInterceptTouchEvent(MotionEvent ev)

你去看源碼的話會發(fā)現(xiàn)它有著大段大段的注釋和只返回了一個false的奇葩特性熙宇,我們可以把它理解為谷歌留給我們用來幫助ondispatchTouchEvent處理分發(fā)事件的一個輔助方法鳖擒,它具有攔截觸摸事件的功能,使其它的View不能響應該事件烫止,默認返回false
由于在這個方法里我們可以拿到觸摸事件的event蒋荚,它的寫法跟onTouchEvent很相似。
因此我們可以通過判斷手勢的邏輯操作來決定
是++return true++攔截這個觸摸事件交給自己的onTouchEvent處理好呢
還是++return false++不攔截交給子view處理

Android事件分發(fā)機制

  • 相關(guān)源碼將在文章最后貼出來*

再從Android的事件分發(fā)機制說起吧馆蠕,當用戶對手機進行了一次觸摸事件時期升,Android會執(zhí)行各種方法,最終將該事件交給Activity互躬,Activity又交給它的布局ViewGroup處理播赁。想了解這其中奧秘的,請點這里

ViewGroup拿到這個事件之后吼渡,它會交給ondispatchTouchEvent去分發(fā)該事件容为,通過讀源碼我們可以看到在判斷一切觸摸事件的 ”起手招式” Down手勢時會進行如下判斷

if (disallowIntercept || !onInterceptTouchEvent(ev))

disallowIntercept這個參數(shù)可以用 requestDisallowInterceptTouchEvent 進行修改,默認為false寺酪,因此坎背,我們重點關(guān)注后一個參數(shù)。誒寄雀?這不就是上節(jié)介紹的ViewGroup的獨特方法嗎得滤?
通過讀源碼我們可以看到,當 onInterceptTouchEvent(ev) 返回true時,ViewGroup的ondispatchTouchEvent 將不再進行事件傳遞,而是攔截該事件自行處理它
返回false時纺腊,將判斷用戶所觸摸的是哪一個子View,并調(diào)用該View的ondispatchTouchEvent秧廉,讓子View再分發(fā)一次,如果子View是繼承自VIew的控件并且沒有實現(xiàn)onTouch 方法,聯(lián)系上文可以知道將由子View的 onTouchEvent 去處理該事件。
下面是源碼調(diào)用子view的 dispatchTouchEvent(ev) 方法的部分

if (child.dispatchTouchEvent(ev))  {  
                            mMotionTarget = child;  
                            return true;  
                            }

根據(jù)子view的分發(fā)結(jié)果來判斷該次事件分發(fā)是否結(jié)束


那么皂股,整個Android事件分發(fā)流程大致就是,Activity拿到TouchEvent命黔,交給最外層布局的dispatchTouchEvent去判斷該事件是由誰來處理。期間會調(diào)用onInterceptTouchEvent判斷自己該不該攔截這個觸摸事件
如果是該布局自己處理就斤,則調(diào)用onTouchEvent處理該事件
如果是子view處理悍募,則交給子view的dispatchTouchEvent繼續(xù)判斷


現(xiàn)在再來考慮如何解決滑動沖突是不是就有頭緒了?由于我當初是HorizontalScrollView嵌套RecyclerView洋机,一個橫著滑一個豎著滑坠宴。在沒有重寫 onInterceptTouchEvent(ev) 這個方法時,由于默認的返回值為false绷旗,Android先給HorizontalScollView處理滑動事件喜鼓,再給RecyclerView處理滑動事件副砍,相當于一個滑動事件被處理了兩次,只要在豎著滑動的時候稍微往橫軸偏移一點點就會觸發(fā)橫向滑動庄岖,那么我們在 onInterceptTouchEvent(ev) 里處理ACTION_MOVE的情況時添加一個判斷

case MotionEvent.ACTION_MOVE:
                final int deltaX = lastX - currentX;
                final int deltaY = lastY - currentY;
                if (Math.abs(deltaX) > Math.abs(deltaY)){
                    return true;
                }else {
                    //既然已經(jīng)交給子view處理這次事件了豁翎,那之后就不要再攔截了吧
                    requestDisallowInterceptTouchEvent(true);
                    return false;
                }

世界就變得和諧了~


源碼

  • ViewGroup中的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {  
    final int action = ev.getAction();  
    final float xf = ev.getX();  
    final float yf = ev.getY();  
    final float scrolledXFloat = xf + mScrollX;  
    final float scrolledYFloat = yf + mScrollY;  
    final Rect frame = mTempRect;  
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;  
    if (action == MotionEvent.ACTION_DOWN) {  
        if (mMotionTarget != null) {  
            mMotionTarget = null;  
        }  
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
            ev.setAction(MotionEvent.ACTION_DOWN);  
            final int scrolledXInt = (int) scrolledXFloat;  
            final int scrolledYInt = (int) scrolledYFloat;  
            final View[] children = mChildren;  
            final int count = mChildrenCount;  
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
                        if (child.dispatchTouchEvent(ev))  {  
                            mMotionTarget = child;  
                            return true;  
                        }  
                    }  
                }  
            }  
        }  
    }  
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
            (action == MotionEvent.ACTION_CANCEL);  
    if (isUpOrCancel) {  
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
    }  
    final View target = mMotionTarget;  
    if (target == null) {  
        ev.setLocation(xf, yf);  
        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
            ev.setAction(MotionEvent.ACTION_CANCEL);  
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        }  
        return super.dispatchTouchEvent(ev);  
    }  
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {  
        final float xc = scrolledXFloat - (float) target.mLeft;  
        final float yc = scrolledYFloat - (float) target.mTop;  
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        ev.setLocation(xc, yc);  
        if (!target.dispatchTouchEvent(ev)) {  
        }  
        mMotionTarget = null;  
        return true;  
    }  
    if (isUpOrCancel) {  
        mMotionTarget = null;  
    }  
    final float xc = scrolledXFloat - (float) target.mLeft;  
    final float yc = scrolledYFloat - (float) target.mTop;  
    ev.setLocation(xc, yc);  
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
        ev.setAction(MotionEvent.ACTION_CANCEL);  
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
        mMotionTarget = null;  
    }  
    return target.dispatchTouchEvent(ev);  
}  

這是我第一次寫博客,肯定有很多不足的地方隅忿,歡迎大家替我指點指點心剥,小生先行謝過。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末背桐,一起剝皮案震驚了整個濱河市优烧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌链峭,老刑警劉巖畦娄,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異弊仪,居然都是意外死亡熙卡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門撼短,熙熙樓的掌柜王于貴愁眉苦臉地迎上來再膳,“玉大人,你說我怎么就攤上這事曲横∥蛊猓” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵禾嫉,是天一觀的道長灾杰。 經(jīng)常有香客問我,道長熙参,這世上最難降的妖魔是什么艳吠? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮孽椰,結(jié)果婚禮上昭娩,老公的妹妹穿的比我還像新娘。我一直安慰自己黍匾,他們只是感情好栏渺,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锐涯,像睡著了一般磕诊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天霎终,我揣著相機與錄音滞磺,去河邊找鬼。 笑死莱褒,一個胖子當著我的面吹牛击困,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播保礼,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼沛励,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炮障?” 一聲冷哼從身側(cè)響起目派,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胁赢,沒想到半個月后企蹭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡智末,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年谅摄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片系馆。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡送漠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出由蘑,到底是詐尸還是另有隱情闽寡,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布尼酿,位于F島的核電站爷狈,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏裳擎。R本人自食惡果不足惜涎永,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鹿响。 院中可真熱鬧羡微,春花似錦、人聲如沸惶我。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽指孤。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恃轩,已是汗流浹背结洼。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叉跛,地道東北人松忍。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像筷厘,于是被迫代替她去往敵國和親鸣峭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

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