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;
}
... ...
}