Touch事件傳遞和攔截機制
關(guān)于touch事件的主要方法介紹
1. ViewGroup的子類(容器類,如:LinearLayout,RelativeLayout等)
1. dispatchTouchEvent(MotionEvent ev) 事件的分發(fā)
2. onInterceptTouchEvent(MotionEvent ev) 事件的攔截
3. onTouchEvent(MotionEvent ev) 事件的處理
2. View的子類(非容器類,如:TextView,ImageView等)
1. dispatchTouchEvent(MotionEvent ev) 事件的分發(fā)
2. onTouchEvent(MotionEvent ev) 事件的處理
區(qū)別:
非容器類沒有onInterceptTouchEvent(MotionEvent ev) 事件的攔截
下面是測試案例的主要代碼
1. 打印日志的類
public class LogUtils {
public static void printLog(String tag,MotionEvent ev,String touchName) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//按下
Log.i(tag, touchName + "按下");
break;
case MotionEvent.ACTION_MOVE:
//按下
Log.d(tag, touchName + "移動");
break;
case MotionEvent.ACTION_UP:
//按下
Log.e(tag, touchName + "松開");
break;
default:
break;
}
}
}
2. 第一個容器類(為了方便布局,本案例中用FrameLayout做基類,在事件攔截機制中和直接繼承ViewGroup類一致,因為FrameLayout沒有覆蓋ViewGroup的上面三個touch方法)
public class ViewGroupOne extends FrameLayout {
public ViewGroupOne(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public ViewGroupOne(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
//分發(fā)事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupOne",ev, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
//攔截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupOne",ev, "onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
//處理事件
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupOne",ev, "onTouchEvent");
return super.onTouchEvent(ev);
}
}
3. 第二個容器類
public class ViewGroupTwo extends FrameLayout {
public ViewGroupTwo(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public ViewGroupTwo(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
//分發(fā)事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
//攔截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
//處理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return super.onTouchEvent(event);
}
}
4. 非容器類
public class MyView extends View {
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
// 分發(fā)事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
// 處理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("MyView",event, "onTouchEvent");
return super.onTouchEvent(event);
}
}
5. 主界面的布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
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=".MainActivity" >
<com.itheima13.eventdemo.ViewGroupOne
android:layout_width="200dip"
android:layout_height="200dip"
android:background="#ff0000" >
<com.itheima13.eventdemo.ViewGroupTwo
android:layout_width="150dip"
android:layout_height="150dip"
android:background="#00ff00" >
<com.itheima13.eventdemo.MyView
android:layout_width="100dip"
android:layout_height="100dip"
android:background="#0000ff" />
</com.itheima13.eventdemo.ViewGroupTwo>
</com.itheima13.eventdemo.ViewGroupOne>
</RelativeLayout>
預(yù)覽結(jié)果如下圖(紅色是ViewGroupOne,綠色是ViewGroupTwo,藍色是MyView):
上面原始代碼都是默認的情況,運行后點擊藍色區(qū)域(按下,移動,松開)結(jié)果如圖:
總結(jié): 默認情況只相應(yīng)touch事件的down狀態(tài),而且事件默認傳遞到最內(nèi)層的子控件(MyView)onTouchEvent方法,由于默認MyView的onTouchEvent的方法沒有處理,所以事件依次回傳至父控件(ViewGroupTwo,ViewGroupOne)的onTouchEvent方法,由于每層都沒處理,所以事件回傳至最外層父控件后消失
由于情況復(fù)雜,下面以三個方法為核心介紹所有情況。(一個案例測試一種情況,每個案例的代碼改變都是在上面的基礎(chǔ)代碼上修改測試,也就是每個案例每種情況的代碼都是獨立的,只需參考上面的基本代碼和案例中每種情況修改的代碼即可。)
dispatchTouchEvent(MotionEvent ev)方法
* 有三種情況
* 直接返回true
* 只修改ViewGroupTwo的dispatchTouchEvent方法測試
//分發(fā)事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
return true;
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
* 只修改MyView的dispatchTouchEvent方法測試
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
return true;//super.dispatchTouchEvent(ev);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
結(jié)果分析:
事件傳遞至子控件的dispatchTouchEvent終止。事件只有按順序傳遞,沒有回傳情況。并且事件的三種情況(down,move,up)都有處理回調(diào)
* 直接返回false
* 只修改ViewGroupTwo的dispatchTouchEvent方法測試
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
return false;//super.dispatchTouchEvent(ev);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
* 只修改MyView的dispatchTouchEvent方法測試
// 分發(fā)事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("MyView",ev, "dispatchTouchEvent");
return false;//super.dispatchTouchEvent(ev);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
結(jié)果分析:
事件傳遞至當前控件的dispatchTouchEvent終止,并且事件回傳給父控件的onTouchEvent方法,此過程中只相應(yīng)事件的down狀態(tài)
* 直接調(diào)用super.dispatchTouchEvent(ev);
結(jié)果分析:默認情況,直接調(diào)用ViewGroup的dispatchTouchEvent(ev)方法,事件會傳遞給當前的控件的onInterceptTouchEvent(MotionEvent ev)方法。
onInterceptTouchEvent(MotionEvent ev) 方法
* 有三種情況
* 直接返回true
* 只修改ViewGroupTwo的onInterceptTouchEvent方法
//攔截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
return true;//super.onInterceptTouchEvent(ev);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
結(jié)果分析:touch事件被當前控件攔截,不傳遞給子控件,并調(diào)用自己的onTouchEvent方法處理事件
* 直接返回false
* 只修改ViewGroupTwo的onInterceptTouchEvent方法
//攔截事件
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",ev, "onInterceptTouchEvent");
return false;//super.onInterceptTouchEvent(ev);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
結(jié)果分析:touch事件傳遞給子控件,由子控件處理事件
* 直接調(diào)用super.onInterceptTouchEvent(ev) (調(diào)用ViewGroup的方法)
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
結(jié)果分析:touch事件傳遞給子控件,由子控件處理事件(與直接返回false類似)
onTouchEvent(MotionEvent ev) 方法
* 有三種情況
* 直接返回true
* 只修改ViewGroupTwo的onTouchEvent方法
//處理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return true;//super.onTouchEvent(event);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
結(jié)果分析:down狀態(tài):當touch事件回傳給當前控件的時候,由于返回ture,所有事件回傳終止。針對move和up狀態(tài):事件到當前的onTouchEvent方法終止。也就是說子控件無法響應(yīng)事件。
* 直接返回false
* 只修改ViewGroupTwo的onTouchEvent方法
//處理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return false;//super.onTouchEvent(event);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
>結(jié)果分析:事件回傳給父控件
* 直接調(diào)用super.onTouchEvent(ev)
* 只修改ViewGroupTwo的onTouchEvent方法
//處理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return super.onTouchEvent(event);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
>結(jié)果分析:事件回傳給父控件
事件反向攔截
主要是反向攔截父控件的onInterceptTouchEvent
* 沒申請反向攔截父控件onInterceptTouchEvent的方法
1. 只在ViewGroupTwo中覆蓋了onTouchEvent方法,并且事件消費
//處理事件
@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
LogUtils.printLog("ViewGroupTwo",event, "onTouchEvent");
return true;//super.onTouchEvent(event);
}
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
* 申請反向攔截父控件的onInterceptTouchEvent的方法
1. 在上面的代碼基礎(chǔ)上,添加dispatchTouchEvent方法的覆蓋
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
requestDisallowInterceptTouchEvent(true);
LogUtils.printLog("ViewGroupTwo",ev, "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
注意多了requestDisallowInterceptTouchEvent(true);方法調(diào)用,該方法是攔截父控件的onInterceptTouchEvent方法的執(zhí)行(針對move,up狀態(tài))
運行后對藍色區(qū)域做了按下,移動,松開的操作,運行結(jié)果如下圖:
>結(jié)果分析: 申請反向攔截后,父控件的onInterceptTouchEvent方法將不再執(zhí)行,案例如:智慧北京項目的輪播圖事件反攔截
>源碼解釋:
requestDisallowInterceptTouchEvent(true); 方法源碼如:
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
// We're already in this state, assume our ancestors are too
return;
}
if (disallowIntercept) {
//主要是記錄攔截的標記 看dispatchTouchEvent方法的代碼片段
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
//繼續(xù)設(shè)置父控件的攔截模式
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
dispatchTouchEvent方法代碼片段:
對上面設(shè)置的狀態(tài)值mGroupFlags判斷
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {//根據(jù)狀態(tài)值是否調(diào)用onInterceptTouchEvent
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
要點解析:
看運行效果圖可知,down事件執(zhí)行的時候,已經(jīng)設(shè)置了反向攔截的狀態(tài),所有到move,up狀態(tài)執(zhí)行的時候,判斷就起到了攔截的效果
#### 事件傳遞和攔截流程圖(針對down事件的流程)
實際操作會以下2點就可以了
1. 只需要做自己onTouchEvent方法的事件處理,并且返回值設(shè)置為true就可以滿足自己事件處理
2. 申請不讓父控件的onInterceptTouchEvent的方法執(zhí)行
本文版權(quán)歸傳智播客Android培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明作者出處。謝謝!
作者:傳智播客Android培訓(xùn)學(xué)院
首發(fā):http://xamj520.com/android/