View的事件分發(fā)及滑動(dòng)沖突的解決

一、觸摸事件的類(lèi)型

ACTION_DOWN:用戶(hù)手指按下操作遍希,一個(gè)按下操作標(biāo)志著一次觸摸事件的開(kāi)始
ACTION_UP:用戶(hù)手指抬起操作矩距,一次抬起標(biāo)志著一次事件的結(jié)束
ACTION_MOVE:手指按下抬起前浩蓉,如果移動(dòng)的距離超過(guò)一定的閾值,就會(huì)觸發(fā)ACTION_MOVE

一次觸摸事件排监,ACTION_DOWN和ACTION_UP是必須存在的,ACTION_MOVE視情況而定杰捂。

二舆床、事件傳遞的三個(gè)階段

分發(fā)(dispatch) dispatchTouchEvent
  • public boolean dispatchTouchEvent(MotionEvent event)
  • 根據(jù)當(dāng)前視圖的具體實(shí)現(xiàn)邏輯,來(lái)決定是直接消費(fèi)這個(gè)事件還是將這個(gè)事件繼續(xù)分發(fā)給子視圖進(jìn)行處理
  • true 表示事件被當(dāng)前視圖消費(fèi)掉,不在繼續(xù)分發(fā)事件
  • super.dispatchEvent表示繼續(xù)分發(fā)改事件挨队,如果當(dāng)前視圖是viewGroup及其子類(lèi)谷暮,則會(huì)調(diào)用onInterceptTouchEvent方法判斷是否攔截該事件
攔截(intercept) onInterceptTouchEvent
  • 事件的攔截對(duì)應(yīng)著onInterceptTouchEvent方法,這個(gè)方法只在viewGroup及其子類(lèi)中存在盛垦,不在activity和view中存成
  • public boolean onInterceptTouchEvent(MotionEvent event)
  • true 表示攔截這個(gè)事件湿弦,不繼續(xù)分發(fā)給子視圖,并調(diào)用自身的onTouchEvent進(jìn)行消費(fèi)
  • false或者super.onInterceptEvent表示不對(duì)事件進(jìn)行攔截腾夯,需要繼續(xù)傳遞給子視圖
消費(fèi)(consume) onTouchEvent
  • public boolean onTouchEvent(MotionEvent event)
  • true 表示當(dāng)前視圖處理對(duì)應(yīng)的事件颊埃,事件將不會(huì)向上傳遞給父視圖
  • false 表示當(dāng)前視圖不處理對(duì)應(yīng)的事件,事件將會(huì)向上傳遞給父視圖的onTouchEvent進(jìn)行處理
在A(yíng)ndroid中擁有事件傳遞的類(lèi)有三種 activity view 和viewGroup
  • activity:擁有dispatchTouchEvent和onTouchEvent方法
  • view:擁有dispatchTouchEvent和onTouchEvent方法
  • viewGroup:永遠(yuǎn)dispatchTouchEvent蝶俱、onInterceptEvent和onTouchEvent方法

三班利、view的事件傳遞

雖然viewGroup是view的子類(lèi),這里的view指除去viewGroup的view控件榨呆,例如textView,button,imageView等控件
寫(xiě)個(gè)簡(jiǎn)單的demo,分析view的事件傳遞

3.1罗标、自定義一個(gè)view繼承textView,并重寫(xiě)onTouchEvent和dispatchTouchEvent方法
class MyTextView : androidx.appcompat.widget.AppCompatTextView {

    constructor(context: Context):super(context){

    }
    constructor(context: Context, attributeSet: AttributeSet): super(context, attributeSet){

    }

    constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int): super(context, attributeSet, defStyleAttr){

    }



    override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("MyTextView","dispatchTouchEvent ACTION_DOWN")
            }
            MotionEvent.ACTION_UP -> {
                Log.e("MyTextView","dispatchTouchEvent ACTION_UP")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("MyTextView","dispatchTouchEvent ACTION_MOVE")
            }
        }

        return super.dispatchTouchEvent(event)
    }


    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("MyTextView","onTouchEvent ACTION_DOWN")
            }
            MotionEvent.ACTION_UP -> {
                Log.e("MyTextView","onTouchEvent ACTION_UP")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("MyTextView","onTouchEvent ACTION_MOVE")
            }
        }


        return super.onTouchEvent(event)
    }


}
3.2积蜻、在activity的xml中添加MyTextView闯割,給MyTextView設(shè)置setOnTouchListener和setOnClickListener監(jiān)聽(tīng),并重寫(xiě)activity的onTouchEvent和dispatchTouchEvent方法
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var mTextView = findViewById<MyTextView>(R.id.mTextView)

        mTextView.setOnClickListener {
            Log.e("ysl","mTextView Click")
        }




        mTextView.setOnTouchListener { v, event ->
            when (event.action) {
                MotionEvent.ACTION_DOWN -> {
                    Log.e("mTextView","OnTouch ACTION_DOWN")
                }
                MotionEvent.ACTION_UP -> {
                    Log.e("mTextView","OnTouch ACTION_UP")
                }
                MotionEvent.ACTION_MOVE -> {
                    Log.e("mTextView","OnTouch ACTION_MOVE")
                }
            }
            return@setOnTouchListener super.onTouchEvent(event)
        }




    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("MainActivity","dispatchTouchEvent ACTION_DOWN")
            }
            MotionEvent.ACTION_UP -> {
                Log.e("MainActivity","dispatchTouchEvent ACTION_UP")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("MainActivity","dispatchTouchEvent ACTION_MOVE")
            }
        }


        return super.dispatchTouchEvent(ev)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("MainActivity","onTouchEvent ACTION_DOWN")
            }
            MotionEvent.ACTION_UP -> {
                Log.e("MainActivity","onTouchEvent ACTION_UP")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("MainActivity","onTouchEvent ACTION_MOVE")
            }
        }

        return super.onTouchEvent(event)
    }
}
3.3浅侨、日志打印結(jié)果
2021-03-30 18:07:14.880 23744-23744/com.ysl.dispatchstudy E/MainActivity: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:07:14.880 23744-23744/com.ysl.dispatchstudy E/MyTextView: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:07:14.880 23744-23744/com.ysl.dispatchstudy E/mTextView: OnTouch ACTION_DOWN
2021-03-30 18:07:14.880 23744-23744/com.ysl.dispatchstudy E/MyTextView: onTouchEvent ACTION_DOWN
2021-03-30 18:07:14.960 23744-23744/com.ysl.dispatchstudy E/MainActivity: dispatchTouchEvent ACTION_UP
2021-03-30 18:07:14.960 23744-23744/com.ysl.dispatchstudy E/MyTextView: dispatchTouchEvent ACTION_UP
2021-03-30 18:07:14.960 23744-23744/com.ysl.dispatchstudy E/mTextView: OnTouch ACTION_UP
2021-03-30 18:07:14.960 23744-23744/com.ysl.dispatchstudy E/MyTextView: onTouchEvent ACTION_UP
2021-03-30 18:07:14.961 23744-23744/com.ysl.dispatchstudy E/ysl: mTextView Click
3.4纽谒、view事件分發(fā)的分析

view的事件傳遞 根據(jù)結(jié)果顯示
1、觸摸事件的傳遞流程是從dispatchTouchEvent開(kāi)始的如输,如果沒(méi)有人為干預(yù)(也就是默認(rèn)返回父類(lèi)的同名函數(shù))鼓黔,則事件將會(huì)按照嵌套層次有外向內(nèi)傳遞,到達(dá)最內(nèi)層的view時(shí)不见,就由最內(nèi)層的onTouchEvent進(jìn)行處理澳化,如果能處理就返回true消費(fèi)掉,如果不能處理就返回false稳吮,這時(shí)事件會(huì)重新向外層傳遞缎谷,并由外層的onTouchEvent進(jìn)行處理,依次類(lèi)推
2灶似、如果事件在向內(nèi)層傳遞過(guò)程中被人為干預(yù)列林,事件處理函數(shù)返回true,事件將會(huì)被提前消費(fèi)掉酪惭,內(nèi)層view將不會(huì)收到這個(gè)事件
3希痴、view的事件觸發(fā)是先執(zhí)行onTouch方法,在最后執(zhí)行onClick方法春感,如果onTouch返回true砌创,事件將不會(huì)繼續(xù)傳遞虏缸,最后也不會(huì)調(diào)用onClick方法,如果返回false嫩实,事件繼續(xù)傳遞

四刽辙、viewGroup的事件分發(fā)

viewGroup作為view控件的容器存在,Android系統(tǒng)默認(rèn)提供了一系列viewGroup,例如LinearLayout,FrameLayout,RelativeLayout,ListView等

4.1甲献、自定義一個(gè)簡(jiǎn)單的MyRelativeLayout繼承RelativeLayout宰缤,重寫(xiě)dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法
class MyRelativeLayout :RelativeLayout{

    constructor(context: Context):super(context){

    }
    constructor(context: Context, attributeSet: AttributeSet): super(context, attributeSet){

    }

    constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int): super(context, attributeSet, defStyleAttr){

    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("MyRelativeLayout","dispatchTouchEvent ACTION_DOWN")
            }
            MotionEvent.ACTION_UP -> {
                Log.e("MyRelativeLayout","dispatchTouchEvent ACTION_UP")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("MyRelativeLayout","dispatchTouchEvent ACTION_MOVE")
            }
        }
        return super.dispatchTouchEvent(ev)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("MyRelativeLayout","onInterceptTouchEvent ACTION_DOWN")
            }
            MotionEvent.ACTION_UP -> {
                Log.e("MyRelativeLayout","onInterceptTouchEvent ACTION_UP")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("MyRelativeLayout","onInterceptTouchEvent ACTION_MOVE")
            }
        }
        return super.onInterceptTouchEvent(ev)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("MyRelativeLayout","onTouchEvent ACTION_DOWN")
            }
            MotionEvent.ACTION_UP -> {
                Log.e("MyRelativeLayout","onTouchEvent ACTION_UP")
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("MyRelativeLayout","onTouchEvent ACTION_MOVE")
            }
        }
        return super.onTouchEvent(event)
    }

}
4.2竟纳、在activity的xml中撵溃,MyTextView外面嵌套一層MyRelativeLayout
4.3、日志打印結(jié)果
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MainActivity: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MyTextView: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/mTextView: OnTouch ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MyTextView: onTouchEvent ACTION_DOWN
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MainActivity: dispatchTouchEvent ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MyRelativeLayout: dispatchTouchEvent ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MyTextView: dispatchTouchEvent ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/mTextView: OnTouch ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MyTextView: onTouchEvent ACTION_UP
2021-03-30 18:17:56.761 24022-24022/com.ysl.dispatchstudy E/ysl: mTextView Click
4.4锥累、 *viewGroup的事件流程

根據(jù)日志打印結(jié)果可以看出
1缘挑、觸摸事件的傳遞順序是activity->viewGroup->view
2、viewGroup通過(guò)onInterceptTouchEvent方法對(duì)事件進(jìn)行攔截
true 則事件不會(huì)傳遞給子view
false貨super.onInterceptTouchEvent桶略,事件會(huì)繼續(xù)傳遞給子view
3语淘、在子view中對(duì)事件進(jìn)行了消費(fèi),viewGroup將接受不到任何事件



以上就是我理解的事件分發(fā)(方便記憶及復(fù)習(xí))

五际歼、滑動(dòng)沖突

5.1惶翻、滑動(dòng)沖突產(chǎn)生的原因

當(dāng)我們內(nèi)外兩層View都可以滑動(dòng)時(shí)候,就會(huì)產(chǎn)生滑動(dòng)沖突鹅心。

5.2吕粗、滑動(dòng)沖突的結(jié)局方法
1、外部攔截法
重寫(xiě)父viewGroup的onInterceptTouchEvent,根據(jù)邏輯在MotionEvent.ACTION_MOVE中進(jìn)行攔截
//偽代碼
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        var intercepted = false
        when (ev?.getAction()) {
            MotionEvent.ACTION_DOWN -> {
                intercepted = false
            }
            MotionEvent.ACTION_MOVE -> {
                intercepted = 滿(mǎn)足父容器的攔截要求
            }
            MotionEvent.ACTION_UP -> {
                intercepted = false
            }
            else -> {
            }
        }
        return intercepted
    }
注意

a旭愧、根據(jù)業(yè)務(wù)邏輯需要颅筋,在A(yíng)CTION_MOVE方法中進(jìn)行判斷,如果需要父View處理則返回true输枯,否則返回false议泵,事件分發(fā)給子View去處理
b、ACTION_DOWN 一定返回false桃熄,不要攔截它先口,否則根據(jù)View事件分發(fā)機(jī)制,后續(xù)ACTION_MOVE 與 ACTION_UP事件都將默認(rèn)交給父View去處理
c瞳收、原則上ACTION_UP也需要返回false碉京,如果返回true,并且滑動(dòng)事件交給子View處理螟深,那么子View將接收不到ACTION_UP事件谐宙,子View的onClick事件也無(wú)法觸發(fā)。而父View不一樣血崭,如果父View在A(yíng)CTION_MOVE中開(kāi)始攔截事件卧惜,那么后續(xù)ACTION_UP也將默認(rèn)交給父View處理

2、內(nèi)部攔截法
子view重寫(xiě)dispatchTouchEvent夹纫,根據(jù)邏輯在MotionEvent.ACTION_MOVE中進(jìn)行攔截咽瓷,父view需要重寫(xiě)onInterceptTouchEvent
//偽代碼
//子view重寫(xiě)dispatchTouchEvent
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {

        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(true)
            }
            MotionEvent.ACTION_MOVE -> {
                if (父容器需要此類(lèi)點(diǎn)擊事件) {
                    parent.requestDisallowInterceptTouchEvent(false)
                }
            }
            MotionEvent.ACTION_UP -> {
            }
            else -> {
            }
        }

        return super.dispatchTouchEvent(ev)
    }
//父view重寫(xiě)onInterceptTouchEvent
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        val action: Int = ev!!.action
        return action != MotionEvent.ACTION_DOWN
    }
注意

a、內(nèi)部攔截法要求父View不能攔截ACTION_DOWN事件舰讹,由于A(yíng)CTION_DOWN不受FLAG_DISALLOW_INTERCEPT標(biāo)志位控制茅姜,一旦父容器攔截ACTION_DOWN那么所有的事件都不會(huì)傳遞給子View
b、滑動(dòng)策略的邏輯放在子View的dispatchTouchEvent方法的ACTION_MOVE中月匣,如果父容器需要獲取點(diǎn)擊事件則調(diào)用 parent.requestDisallowInterceptTouchEvent(false)方法钻洒,讓父容器去攔截事件

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锄开,隨后出現(xiàn)的幾起案子素标,更是在濱河造成了極大的恐慌,老刑警劉巖萍悴,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件头遭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡癣诱,警方通過(guò)查閱死者的電腦和手機(jī)计维,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)撕予,“玉大人鲫惶,你說(shuō)我怎么就攤上這事∈德眨” “怎么了欠母?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)澜术。 經(jīng)常有香客問(wèn)我艺蝴,道長(zhǎng),這世上最難降的妖魔是什么鸟废? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任猜敢,我火速辦了婚禮,結(jié)果婚禮上盒延,老公的妹妹穿的比我還像新娘缩擂。我一直安慰自己,他們只是感情好添寺,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布胯盯。 她就那樣靜靜地躺著,像睡著了一般计露。 火紅的嫁衣襯著肌膚如雪博脑。 梳的紋絲不亂的頭發(fā)上憎乙,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音叉趣,去河邊找鬼泞边。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疗杉,可吹牛的內(nèi)容都是我干的阵谚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼烟具,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梢什!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起朝聋,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嗡午,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后冀痕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體翼馆,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年金度,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了应媚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡猜极,死狀恐怖中姜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跟伏,我是刑警寧澤丢胚,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站受扳,受9級(jí)特大地震影響携龟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勘高,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一峡蟋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧华望,春花似錦蕊蝗、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宾抓,卻和暖如春子漩,著一層夾襖步出監(jiān)牢的瞬間豫喧,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工幢泼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘿棘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓旭绒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親焦人。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挥吵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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