本文原地址:https://blog.csdn.net/zhcswlp0625/article/details/68930797
也是我寫(xiě)的,但是沒(méi)有轉(zhuǎn)移過(guò)來(lái),尷尬着倾。(本人放棄csdn分享戒职,轉(zhuǎn)向簡(jiǎn)書(shū))。
下篇文章地址
完全理解Android TouchEvent事件分發(fā)機(jī)制(二)
本文能給你帶來(lái)和解決一些你模糊的Touch事件概念及用法
- 1.掌握View及ViewGroup的TouchEvent事件分發(fā)機(jī)制
- 2.為解決View滑動(dòng)沖突及點(diǎn)擊事件消費(fèi)提供支持
- 3.為你解決面試中的一些問(wèn)題巨双。
Touch事件分發(fā)中只有兩個(gè)主角:ViewGroup和View。
Activity的Touch事件事實(shí)上是調(diào)用它內(nèi)部的ViewGroup的Touch事件霉祸,可以直接當(dāng)成ViewGroup處理筑累。
Activity、ViewGroup丝蹭、View都關(guān)心Touch事件慢宗,其中ViewGroup的關(guān)心的事件有三個(gè):onInterceptTouchEvent、dispatchTouchEvent奔穿、onTouchEvent镜沽。
Activity和View關(guān)心的事件只有兩個(gè):dispatchTouchEvent、onTouchEvent贱田。
只有ViewGroup可以對(duì)事件進(jìn)行攔截缅茉。
在Android中Touch觸摸事件主要包括點(diǎn)擊(onClick)、長(zhǎng)按(onLongClick)湘换、拖拽(onDrag)宾舅、滑動(dòng)(onScroll)等,
其中Touch的第一個(gè)狀態(tài)是 ACTION_DOWN彩倚,表示按下了屏幕后筹我,touch將會(huì)有后續(xù)事件,比如移動(dòng)帆离、抬起等蔬蕊。
一個(gè)Action_DOWN,一個(gè)ACTION_UP哥谷,許多個(gè)ACTION_MOVE岸夯,構(gòu)成了Android中眾多的Touch交互事件。
安卓里經(jīng)常會(huì)有多個(gè)布局嵌套们妥,View重疊猜扮,View的Visibility設(shè)置等等,還有ViewGroup包含View的情況监婶。
這個(gè)時(shí)候點(diǎn)擊到子View時(shí)旅赢,其實(shí)也是同時(shí)點(diǎn)到ViewGroup這個(gè)父控件的齿桃,那是把這個(gè)點(diǎn)擊事件應(yīng)該是怎么分發(fā)的呢(有沒(méi)有遇到過(guò)listview或recyclerview的item事件或者是item中的控件是不是沒(méi)反應(yīng)撒)?
觸摸事件分發(fā)機(jī)制涉及的三個(gè)重要方法:
public boolean dispatchTouchEvent(MotionEvent event)
dispatchTouchEvent用來(lái)進(jìn)行事件的分發(fā)煮盼。如果事件能夠傳遞給當(dāng)前的View短纵,那么此方法一定會(huì)被調(diào)用,
返回結(jié)果受當(dāng)前View或者是ViewGroup的onTouchEvent和下級(jí)View的dispatchTouchEvent方法的影響僵控,表示是否消耗當(dāng)前事件香到。
public boolean onInterceptTouchEvent(MotionEvent event)
onInterceptTouchEvent是ViewGroup提供的方法,用來(lái)判斷是否攔截某個(gè)事件报破,如果當(dāng)前View攔截了某個(gè)事件悠就,
那么在同一個(gè)事件序列當(dāng)中,此方法不會(huì)被再次調(diào)用充易,返回結(jié)果表示是否攔截當(dāng)前事件理卑。默認(rèn)返回false,返回true表示攔截蔽氨。
public boolean onTouchEvent(MotionEvent event)
onTouchEvent在dispatchTouchEvent方法中調(diào)用,用來(lái)處理點(diǎn)擊事件帆疟,返回結(jié)果表示是否消耗當(dāng)前的事件鹉究,如果不消耗,
則在同一個(gè)事件序列中踪宠,當(dāng)前View無(wú)法再次接受到事件自赔。view中默認(rèn)返回true,表示消費(fèi)了這個(gè)事件柳琢。
今天所使用的Demo目錄結(jié)構(gòu)及Activity如圖所示:
[外鏈圖片轉(zhuǎn)存失敗(img-dxeeN54q-1566869743609)(http://oc5bjv3gr.bkt.clouddn.com/touchdetailsesdgdf.png)]
首先我們來(lái)看一下dispatchTouchEvent(MotionEvent event)
布局activity_touch_test.xml
<?xml version="1.0" encoding="utf-8"?>
<com.shanlovana.rcimageview.touchviews.GrandPaViewGroup
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_touch_test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.shanlovana.rcimageview.TouchTestActivity">
<com.shanlovana.rcimageview.touchviews.FatherViewGroup
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="match_parent">
<com.shanlovana.rcimageview.touchviews.LogImageView
android:layout_width="300dp"
android:layout_height="200dp"
android:src="@drawable/damimi"/>
</com.shanlovana.rcimageview.touchviews.FatherViewGroup>
</com.shanlovana.rcimageview.touchviews.GrandPaViewGroup>
下面是三層布局及預(yù)覽情況:
[外鏈圖片轉(zhuǎn)存失敗(img-RjazP8Pr-1566869743610)(http://oc5bjv3gr.bkt.clouddn.com/sancengbujujiyulan.png)]
點(diǎn)擊一下圖片:看一下打由芊痢:
03-31 09:02:40.554 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup dispatchTouchEvent Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup onInterceptTouchEvent Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup dispatchTouchEvent Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup onInterceptTouchEvent Event 0
03-31 09:02:40.555 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: LogImageView dispatchTouchEvent Event 0
03-31 09:02:40.556 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: LogImageView onTouchEvent Event 0
03-31 09:02:40.558 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup onTouchEvent Event 0
03-31 09:02:40.559 10898-10898/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup onTouchEvent Event 0
源碼中0,1柬脸,2他去,3,4所代表的Action
public static final int ACTION_DOWN = 0;
public static final int ACTION_UP = 1;
public static final int ACTION_MOVE = 2;
public static final int ACTION_CANCEL = 3;
public static final int ACTION_OUTSIDE = 4;
為什么是這樣一個(gè)從父級(jí)到子級(jí)再到父級(jí)的順序呢倒堕?
來(lái)灾测,follow me進(jìn)入源碼查看,所有的核心在于ViewGroup的dispatchTouchEvent方法:
boolean dispatchTouchEvent() {
// 是否攔截
final boolean intercepted;
intercepted = onInterceptTouchEvent(ev);
// final boolean canceled = resetCancelNextUpFlag(this)
// || actionMasked == MotionEvent.ACTION_CANCEL;
if( !intercepted) {
// 如果不攔截遍歷所有child垦巴,判斷是否有分發(fā)
boolean handled;
if (child == null) {
// 等同于handled = onTouchEvent()
handled = super.dispatchTouchEvent();
} else {
// 如果有child媳搪,再調(diào)用child的分發(fā)方法
handled = child.dispatchTouchEvent();
}
if(handled) {
touchTarget = child;
break;
}
}
if(touchTarget == null) {
// 如果所有child中都沒(méi)有消費(fèi)掉事件
// 那么就把自己作為沒(méi)child的普通View
handled = super.dispatchTouchEvent();
}
return handled;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
dispatchTouchEvent方法的作用是將屏幕點(diǎn)擊事件進(jìn)行向下分發(fā)(子一級(jí))傳遞到目標(biāo)控件上,或者傳遞給自己骤宣。
如果事件被(自己或者下面某一層的子控件)處理掉了的話秦爆,就返回true,否則返回false
那問(wèn)題來(lái)了憔披,如果我沒(méi)有child了等限,或者我就是一個(gè)View,那我的dispatchTouchEvent返回值要如何獲取呢?
這種情況下就會(huì)使用父類的dispatchTouchEvent方法精刷,
也就是調(diào)用View類中的實(shí)現(xiàn)拗胜,簡(jiǎn)化代碼如下:
boolean dispatchTouchEvent() {
// 實(shí)質(zhì)上就是調(diào)用onTouchEvent用其返回值
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
}
由此可見(jiàn),只要是enable=false或者沒(méi)有設(shè)置過(guò)touchListener, 那么他一定會(huì)調(diào)用onTouchEvent怒允,且dispatchTouchEvent的返回值就是onTouchEvent的返回值埂软。
ViewGroup進(jìn)行事件的分發(fā),一直到自己或者是最底層的View纫事,邏輯圖如下勘畔。
[外鏈圖片轉(zhuǎn)存失敗(img-K0iwEqb4-1566869743610)(http://oc5bjv3gr.bkt.clouddn.com/luojiouxiangtu.png)]
現(xiàn)在我們基本知道了事件的分發(fā)dispatchTouchEvent,最終調(diào)用了onTouchEvent方法
接著我們來(lái)理解和講解onInterceptTouchEvent攔截方法
該方法用于攔截事件向下分發(fā)
當(dāng)返回值為true時(shí)丽惶,就會(huì)攔截TouchEvent不再向下傳遞炫七,直接交給自己的onTouchEvent方法處理。返回false則不攔截钾唬。
把Demo中的Parent層的onInterceptTouchEvent返回值改為true万哪。
運(yùn)行一下,點(diǎn)View抡秆,看下輸出結(jié)果:
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup dispatchTouchEvent Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup onInterceptTouchEvent Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup dispatchTouchEvent Event 0
03-31 11:45:39.953 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup onInterceptTouchEvent Event 0
03-31 11:45:39.954 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup onTouchEvent Event 0
03-31 11:45:39.955 23170-23170/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup onTouchEvent Event 0
即當(dāng)事件一層層向下傳遞到parent時(shí)奕巍,被他就攔截了下來(lái)然后自己消費(fèi)使用。
intercepted為true,沒(méi)有進(jìn)入FatherViewGroup的條件儒士,就跳過(guò)了child.dispatchTouchEvent的向下事件分發(fā)(結(jié)合我的demo看比較直觀)的止。
最后我們來(lái)講解 onTouchEvent方法
方法的主體內(nèi)容其實(shí)是處理具體操作邏輯的,是產(chǎn)生一次點(diǎn)擊還是一次橫縱向的滑動(dòng)等
而他的返回值才會(huì)影響整個(gè)事件分發(fā)機(jī)制着撩,
其意義在于通知父級(jí)的ViewGroup們是否已經(jīng)消費(fèi)找到目標(biāo)Target了诅福。
把示例中的Parent的TouchEvent返回值改為true。攔截方法不變
點(diǎn)一下View(小伙子拖叙,如果你不是點(diǎn)一下氓润,會(huì)出現(xiàn)不同的結(jié)果哦),則輸出日志為:
03-31 12:01:21.661 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup dispatchTouchEvent Event 0
03-31 12:01:21.674 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup onInterceptTouchEvent Event 0
03-31 12:01:21.676 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup dispatchTouchEvent Event 0
03-31 12:01:21.677 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup onInterceptTouchEvent Event 0
03-31 12:01:21.677 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: LogImageView dispatchTouchEvent Event 0
03-31 12:01:21.678 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: LogImageView onTouchEvent Event 0
03-31 12:01:21.681 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup onTouchEvent Event 0
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup dispatchTouchEvent Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: GrandPaViewGroup onInterceptTouchEvent Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup dispatchTouchEvent Event 1
03-31 12:01:21.723 2596-2596/com.shanlovana.rcimageview E/ShanCanCan: FatherViewGroup onTouchEvent Event 1
先看Down的邏輯憋沿,對(duì)應(yīng)的源碼執(zhí)行順序如下
Father調(diào)用super.dispatchTouchEvent實(shí)際上是調(diào)用了onTouchEvent方法旺芽,
這里因?yàn)槲覀冃薷某闪藅rue,所以dispatchTouchEvent最終也返回true辐啄。
所以返回到GrandPa中采章,touchTarget 就非空了,
因此GrandPa的onTouchEvent也沒(méi)有執(zhí)行~
可以看出來(lái),事件一旦被某一層消費(fèi)掉壶辜,其它層就不會(huì)再消費(fèi)了
到這里其實(shí)對(duì)事件分發(fā)的機(jī)制就有個(gè)大概了解了看了源碼也知道里面的原理是怎么回事悯舟。
突然有事,明天接著更新砸民。