Android事件分發(fā)機制 & 面試解析

先總結(jié)一下事件分發(fā)機制的流程

  • 事件分發(fā)從Action_Down開始购岗,最初由Activity的dispatchTouchEvent()方法接收徐紧,不攔截不中斷的正常分發(fā)流程:Activity的disPatchTouchEvent()方法到PhoneWindow的superDispatchTouchEvent方法沙峻,再到DecorView的superDispatchTouchEvent方法办绝,再到ViewGroup的dispatchTouchEvent方法辜王,在ViewGroup的dispatchTouchEvent方法中判斷是否攔截级乐,若攔截調(diào)用ViewGroup的onTouchEvent方法议薪,該ViewGroup消費掉尤蛮;若不攔截,該ViewGroup遍歷子View根據(jù)點擊的位置等條件判斷是否為接收事件的子View斯议,是产捞,則分發(fā)給該子View的dispatchTouchEvent()方法,然后會調(diào)用View的onTouchEvent方法哼御,在onTouchEvent方法中會判斷該子View是否可點擊坯临,是焊唬,則事件最終傳遞到View的onClick方法消費;否則看靠,事件返回向上傳遞赶促,直到消費或者終止。
  • 在dispatchTouchEvent()方法中返回true或者false挟炬,事件不向下傳遞鸥滨,只用調(diào)用super.dispatchTouchEvent方法,事件才會向下傳遞谤祖。
  • 在onTouchEvent()方法中返回true爵赵,事件在該方法中消費,不會向下或者向上傳遞泊脐;返回super.onTouchEvent方法空幻,將會調(diào)用View onTouchEvent方法,判斷長按事件和點擊事件的執(zhí)行條件存不存在容客,存在則會在點擊事件中消費秕铛。
  • 在onInterceptTouchEvent()方法中返回true表示攔截事件,事件可能會在該ViewGroup中消費掉缩挑;返回false表示事件繼續(xù)往下傳遞税课。

ViewGroup 默認攔截事件嗎?

答:默認不攔截任何事件渔嚷,onInterceptTouchEvent返回的是false谚鄙。

一旦有事件傳遞給view,view的onTouchEvent一定會被調(diào)用嗎芥丧?

答:是的紧阔,因為view 本身沒有onInterceptTouchEvent方法,所以只要事件來到view這里 就一定會走onTouchEvent方法续担。
并且默認都是消耗掉擅耽,返回true的。除非這個view是不可點擊的物遇,所謂不可點擊就是clickable和longgclikable同時為fale
Button的clickable就是true 但是textview是false乖仇。

enable是否影響view的onTouchEvent返回值?

答:不影響询兴,只要clickable和longClickable有一個為真乃沙,那么onTouchEvent就返回true。

requestDisallowInterceptTouchEvent 可以在子元素中干擾父元素的事件分發(fā)嗎诗舰?如果可以警儒,是全部都可以干擾嗎?

答:肯定可以始衅,但是down事件干擾不了冷蚂。

dispatchTouchEvent每次都會被調(diào)用嗎缭保?

答:是的,onInterceptTouchEvent則不會蝙茶。

滑動沖突問題如何解決 思路是什么艺骂?

要解決滑動沖突核心的方法就是2個 外部攔截也就是父親攔截,另外就是內(nèi)部攔截隆夯,也就是子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è)務(wù)需求) {
                    //如果確定攔截了 就去自己的onTouchEvent里 處理攔截之后的操作和效果 即可了
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                //up事件 我們一般都是返回false的 一般父容器都不會攔截他耍属。 因為up是事件的最后一步托嚣。這里返回true也沒啥意義
                //唯一的意義就是因為 父元素 up被攔截。導(dǎo)致子元素 收不到up事件厚骗,那子元素 就肯定沒有onClick事件觸發(fā)了示启,這里的
                //小細節(jié) 要想明白
                intercepted = false;
                break;
            default:
                break;
        }
        return intercepted;
    }
  • 內(nèi)部攔截法:內(nèi)部攔截法稍微復(fù)雜一點,就是事件到來的時候领舰,父容器不管夫嗓,讓子元素自己來決定是否處理。如果消耗了 就最好冲秽,沒消耗 自然就轉(zhuǎn)給父容器處理了舍咖。

子元素代碼:

@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);
    }

父親容器代碼也要修改一下,其實就是保證父親別攔截down:

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            return false;

        }
        return true;
    }

怎么解決 ScrollView 嵌套 ScrollView 后內(nèi)部 ScrollView 無法滑動的問題劳跃?

答:根本原因就是因為用戶的滑動操作都被外部 ScrollView 攔截并消費了谎仲,導(dǎo)致內(nèi)部 ScrollView 一直無法響應(yīng)滑動事件浙垫。

這里選擇使用內(nèi)部攔截法來解決問題刨仑。首先需要讓外部 ScrollView 攔截 ACTION_DOWN 之外的任何事件

class ExternalScrollView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

    override fun onInterceptTouchEvent(motionEvent: MotionEvent): Boolean {
        val intercepted: Boolean
        when (motionEvent.action) {
            MotionEvent.ACTION_DOWN -> {
                intercepted = false
                super.onInterceptTouchEvent(motionEvent)
            }
            else -> {
                intercepted = true
            }
        }
        return intercepted
    }

}

內(nèi)部 ScrollView 判斷自身是否還處于可滑動狀態(tài),如果滑動到了最頂部還想再往下滑動夹姥,或者是滑動到了最底部還想再往上滑動杉武,那么就將事件都交由外部 ScrollView 處理,其它情況都直接攔截并消費掉事件辙售,這樣內(nèi)部 ScrollView 就可以實現(xiàn)內(nèi)部滑動了

class InsideScrollView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ScrollView(context, attrs, defStyleAttr) {

    private var lastX = 0f

    private var lastY = 0f

    override fun dispatchTouchEvent(motionEvent: MotionEvent): Boolean {
        val x = motionEvent.x
        val y = motionEvent.y
        when (motionEvent.action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(true)
            }
            MotionEvent.ACTION_MOVE -> {
                val deltaX = x - lastX
                val deltaY = y - lastY
                if (abs(deltaX) < abs(deltaY)) { //上下滑動的操作
                    if (deltaY > 0) { //向下滑動
                        if (scrollY == 0) { //滑動到頂部了
                            parent.requestDisallowInterceptTouchEvent(false)
                        }
                    } else { //向上滑動
                        if (height + scrollY >= computeVerticalScrollRange()) { //滑動到底部了
                            parent.requestDisallowInterceptTouchEvent(false)
                        }
                    }
                }
            }
            MotionEvent.ACTION_UP -> {
            }
        }
        lastX = x
        lastY = y
        return super.dispatchTouchEvent(motionEvent)
    }

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轻抱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子旦部,更是在濱河造成了極大的恐慌祈搜,老刑警劉巖较店,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異容燕,居然都是意外死亡梁呈,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門蘸秘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來官卡,“玉大人,你說我怎么就攤上這事醋虏⊙爸洌” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵颈嚼,是天一觀的道長毛秘。 經(jīng)常有香客問我,道長阻课,這世上最難降的妖魔是什么熔脂? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮柑肴,結(jié)果婚禮上霞揉,老公的妹妹穿的比我還像新娘。我一直安慰自己晰骑,他們只是感情好适秩,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著硕舆,像睡著了一般秽荞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抚官,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天扬跋,我揣著相機與錄音,去河邊找鬼凌节。 笑死钦听,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的倍奢。 我是一名探鬼主播朴上,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卒煞!你這毒婦竟也來了痪宰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衣撬,沒想到半個月后乖订,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡具练,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年垢粮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片靠粪。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜡吧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出占键,到底是詐尸還是另有隱情昔善,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布畔乙,位于F島的核電站君仆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏牲距。R本人自食惡果不足惜返咱,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牍鞠。 院中可真熱鬧咖摹,春花似錦、人聲如沸难述。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胁后。三九已至店读,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間攀芯,已是汗流浹背屯断。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留侣诺,地道東北人殖演。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像紧武,于是被迫代替她去往敵國和親剃氧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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