1. MotionEvent事件
在MotionEvent
操作裏有多種手勢,常用手勢有
ACTION_DOWN
,按下ACTION_UP
,擡起ACTION_MOVE
,移動ACTION_CANCEL
,取消
所有的操作都會在Activity
、View
和ViewGroup
中處理。
在View
和Activity
中,有兩個方法dispatchTouchEvent(MotionEvent)
和onTouchEvent(MotionEvent)
,在ViewGroup
中還有另外一個方法onInterceptTouchEvent(MotionEvent)
。
2. 測試控件
TouchEventView
和TouchEventViewGroup
用來跟蹤手勢的傳遞。
public class TouchEventView extends View {
public final static String LOG_TAG = "TouchEventView";
public TouchEventView(Context context) {
super(context);
}
public TouchEventView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
LogTool.logi(LOG_TAG, "before dispatchTouchEvent " + event.getAction());
boolean handle = super.dispatchTouchEvent(event);
LogTool.logi(LOG_TAG, "after dispatchTouchEvent");
return handle;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
LogTool.logi(LOG_TAG, "before onTouchEvent " + event.getAction());
boolean handle = super.onTouchEvent(event);
LogTool.logi(LOG_TAG, "after onTouchEvent");
return handle;
}
}
public class TouchEventViewGroup extends RelativeLayout {
private final static String LOG_TAG = "TouchRelativeLayout";
public TouchEventViewGroup(Context context) {
super(context);
}
public TouchEventViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
LogTool.logi(LOG_TAG, "before dispatchTouchEvent " + event.getAction());
boolean handle = super.dispatchTouchEvent(event);
LogTool.logi(LOG_TAG, "after dispatchTouchEvent");
return handle;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
LogTool.logi(LOG_TAG, "before onInterceptTouchEvent " + ev.getAction());
boolean handle = super.onInterceptTouchEvent(ev);
LogTool.logi(LOG_TAG, "after onInterceptTouchEvent");
return handle;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
LogTool.logi(LOG_TAG, "before onTouchEvent " + event.getAction());
boolean handle = super.onTouchEvent(event);
LogTool.logi(LOG_TAG, "after onTouchEvent");
return handle;
}
}
佈局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#ffffffff" >
<com.blog.demo.custom.widget.TouchEventViewGroup
android:layout_width="240dp"
android:layout_height="300dp"
android:layout_centerInParent="true"
android:background="#ffff0000" >
<com.blog.demo.custom.widget.TouchEventView
android:layout_width="120dp"
android:layout_height="150dp"
android:layout_centerInParent="true"
android:background="#ff0000ff" />
</com.blog.demo.custom.widget.TouchEventViewGroup>
</RelativeLayout>
3. MotionEvent流程
在Activity
裏面有一個ViewGroup
,ViewGroup
裏面包含一個View
,點擊View
後會發出MotionEvent
事件,每個事件都會從ACTION_DOWN
開始,到ACTION_UP
結束,也會有ACTION_MOVE
、ACTION_CANCEL
。
-
每次
ACTION_DOWN
,都會調用dispatchTouchEvent(MotionEvent)
方法,從Activity
->ViewGroup
->View
,一層層往下傳遞。在View
的dispatchTouchEvent(MotionEvent)
中調用onTouchEvent(MotionEvent)
,返回false
的話,往上傳遞給ViewGroup
和Activity
,依次調用onTouchEvent(MotionEvent)
。 -
如果
ACTION_DOWN
沒有在View
或ViewGroup
中沒有處理,Activity
將在onTouchEvent(MotionEvent)
中處理接下來的所有事件。如果ACTION_DOWN
在View
或ViewGroup
處理,但沒有處理其他MotionEvent
事件,Activity
依然會調用onTouchEvent(MotionEvent)
。 -
如果在
View
的dispatchTouchEvent(MotionEvent)
或者onTouchEvent(MotionEvent)
裏面返回true
,表明View
將處理Action
,不會調用ViewGroup
和Activy
的onTouchEvent(MotionEvent)
方法。其它MotionEvent
事件先在View
的onTouchEvent(MotionEvent)
中處理。未處理的交給Activity
的onTouchEvent(MotionEvent)
方法。 -
如果在
ViewGroup
的dispatchTouchEvent(MotionEvent)
或者onTouchEvent(MotionEvent)
裏面返回true
,表明ViewGroup
將處理。接下來的MotionEvent
事件先在ViewGroup
中的onTouchEvent(MotionEvent)
中處理。未處理的交給Activity
的onTouchEvent(MotionEvent)
方法。 -
在
ViewGroup
中會先調用onInterceptTouchEvent(MotionEvent)
過濾,然後纔會傳遞給View
。如果ViewGroup
的onInterceptTouchEvent(MotionEvent)
返回true
,直接調用ViewGroup
的onTouchEvent(MotionEvent)
。onInterceptTouchEvent(MotionEvent)
只會調用一次。 -
如果
View
中處理了ACTION_DOWN
,在其它MotionEvent
事件時ViewGroup
的onInterceptTouchEvent(MotionEvent)
返回true
,則會傳遞ACTION_CANCEL
給View
,接下來的MotionEvent
事件都會調用ViewGroup
的onTouchEvent(MotionEvent)
處理。
4. 總結
-
沒有處理的事件都會在
Activity
的onTouchEvent(MotionEvent)
中處理。 -
ACTION_DOWN
事件在哪裏處理,接下來的MotionEvent
事件都只會傳遞到那裏。 -
如果
ViewGroup
中的onInterceptTouchEvent(MotionEvent)
返回true
,事件將不會再往下傳遞。onInterceptTouchEvent(MotionEvent)
返回true
後不會再次調用。 -
如果
ViewGroup
的子控件在ACTION_DOWN
時處理了事件,那麼在onInterceptTouchEvent(MotionEvent)
返回true
後代替子控件處理MotionEvent
。
6. OnTouchListener
View
可以添加監聽器,使用setOnTouchListener(OnTouchListener)
方法添加。在dispatchTouchEvent(MotionEvent)
中,先調用OnTouchListener
的onTouch(View, MotionEvent)
。如果返回true
,返回到上一層,否則調用onTouchEvent(MotionEvent)
。
public boolean dispatchTouchEvent(MotionEvent event) {
... ...
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;
}
... ...
}