一、觸摸事件的類(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)方法钻洒,讓父容器去攔截事件