有这样一个场景,在一个自定义CutomLayout(继承自RelativeLayout)的布局,嵌套了一些View控件,但View并未占据全屏,此时需要监听空白区域的触摸事件来做一些处理,为RelativeLayout设置id后并设置触摸监听事件,但却监听不到RelativeLayout的触摸事件。
View layoutMain = findViewById(R.id.layout_main);
layoutMain.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d(TAG, "onTouch" + event.getAction());
//Todo do something
return false;
}
});
正常onTouch返回true时,才能接收到完整的触摸事件,同时在自定义CutomLayout中重写了onTouchEvent方法,
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent:" + event.getAction());
return super.onTouchEvent(event);
}
onTouch方法回调了false,为什么还可以收到完整的Touch事件。后来发现在布局文件中声明了clickable属性为true。
<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.CustomLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true">
<Button
android:id="@+id/btn_test1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="测试按钮"
android:textSize="40sp" />
<Button
android:id="@+id/btn_test2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="测试按钮"
android:textSize="40sp" />
<Button
android:id="@+id/btn_test3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="测试按钮"
android:textSize="40sp" />
</com.example.myapplication.CustomLayout>
查看View的源码才发现点击事件会潜移默化的影响触摸回调的返回值。CustomLayout继承RelativeLayout,RelativeLayout继承自ViewGroup,而ViewGroup继承View属于View的子类,点击事件和触摸事件的方法主要在View实现。ViewGroup同样处理回调dispatchTouchEvent方法,但ViewGroup方法主要处理是否拦击子View的事件分发(重写onInterceptTouchEvent方法)。ViewGroup的dispatchTouchEvent源码如下:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
}
在onTouch方法返回false会接着触发onTouchEvent,返回true则onTouchEvent方法不会被调用。在View的源码dispatchTouchEvent方法中可以看到,
public boolean dispatchTouchEvent(MotionEvent event) {
...此处省略代码
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;
}
此篇不对android的触摸传递机制过多解释,只对View的设置了点击事件和触摸事件时的事件分发进行分析。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
...此处省略代码
}
从View的onTouchEvent方法中可以看到包含CLICKABLE标志时,onTouchEvent返回了true(clickable为true)。
当设置了点击事件setOnClickListener时,会为mViewFlags添加CLICKABLE标志。设置长按事件(setOnLongClickListener)和在布局文件中声明clickable属性为true时也会向mViewFlags添加CLICKABLE标志。因此点击事件代表在View的事件传递机制中消耗了触摸事件
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setClickable(boolean clickable) {
setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
}
设置了点击事件即设置了CLICKABLE标志后,View的onTouchEvent返回true从而使得触摸事件在此处消费,接收到完整的分发触摸事件。