PS:之前写的有些地方不够清楚,配图也有点小问题,特此更正了下。2017-9-18
今天看了Android事件分发的一些资料,自己也研究了源码,了解了事件分发的流程和机制,特此记录下。
首先自定义了两个View,一个Button,一个LinearLayout,分别重写了onTouchEvent、dispatchTouchEvent、onFilterTouchEventForSecurity这三个方法。代码如下:
public class MyButton extends AppCompatButton {
private static final String TAG = "TestTouchEventButton";
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent: "+event.getAction());
// super.onTouchEvent(event);
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(TAG, "dispatchTouchEvent: "+event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
Log.i(TAG, "onFilterTouchEventForSecurity: "+event.getAction());
return super.onFilterTouchEventForSecurity(event);
}
}
public class MyLayout extends LinearLayout {
private static final String TAG = "TestTouchEventLayout";
public MyLayout(Context context) {
super(context);
}
public MyLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent: "+event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent: "+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
Log.i(TAG, "onFilterTouchEventForSecurity: ");
return super.onFilterTouchEventForSecurity(event);
}
}
//activity
public class TestTouchDispatchActivity extends BaseActivity {
private static final String TAG = "TestTouchEventActivity";
@BindView(R.id.btn)
MyButton mBtn;
@Override
protected void initPresenter() {
}
@Override
protected int attachLayoutRes() {
return R.layout.activity_test_touch_dispatch;
}
@Override
protected void initViews() {
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick: ");
}
});
mBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "onTouch: "+event.getAction());
return false;
}
});
}
}
//布局文件
<com.sleep.sunshine.view.MyLayout
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">
<com.sleep.sunshine.view.MyButton
android:id="@+id/btn"
android:text="CLICK"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.sleep.sunshine.view.MyLayout>
一、先屏蔽掉MyLayout中的log,只看Button里面的事件分发和传递情况(PS:每项对比后直接说正确的结论,后面在从源码层面分析):
1.onTouch方法里面返回true和false,打印log如下:
mBtn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(TAG, "onTouch: "+event.getAction());
return true/false;
}
});
//true
07-27 18:45:56.160 16726-16726/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 18:45:56.160 16726-16726/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 18:45:56.160 16726-16726/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 18:45:56.277 16726-16726/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
07-27 18:45:56.277 16726-16726/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
07-27 18:45:56.277 16726-16726/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
//false
07-27 18:43:04.297 14262-14262/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 18:43:04.297 14262-14262/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 18:43:04.297 14262-14262/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 18:43:04.297 14262-14262/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
07-27 18:43:04.373 14262-14262/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
07-27 18:43:04.373 14262-14262/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
07-27 18:43:04.374 14262-14262/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
07-27 18:43:04.374 14262-14262/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 1
07-27 18:43:04.374 14262-14262/com.sleep.sunshine I/TestTouchEventActivity: onClick:
# 对比结论:onTouch返回true后,不会再调用onTouchEvent和onClick方法。
2.将MyButton里面的onTouchEvent方法里面返回分别返回true和false,打印log如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent: "+event.getAction());
return true/false;
}
//true
07-27 18:51:28.626 19877-19877/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 18:51:28.626 19877-19877/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 18:51:28.626 19877-19877/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 18:51:28.626 19877-19877/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
07-27 18:51:28.665 19877-19877/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
07-27 18:51:28.665 19877-19877/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
07-27 18:51:28.665 19877-19877/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
07-27 18:51:28.665 19877-19877/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 1
//false
07-27 18:52:23.649 20415-20415/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 18:52:23.649 20415-20415/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 18:52:23.649 20415-20415/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 18:52:23.649 20415-20415/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
# 对比结论:a、onTouchEvent返回false,代表不再接受同组的事件(ACTION_DOWN= 0 ,ACTION_UP=1)
#b、和上面的1对比说明onClick方法是在super.onTouchEvent(event)方法里面调用的。事实证明查看源码也能发现这一点:
//父类TextView的onTouchEvent方法。
@Override
public boolean onTouchEvent(MotionEvent event) {
......
final boolean textIsSelectable = isTextSelectable();
if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
// The LinkMovementMethod which should handle taps on links has not been installed
// on non editable text that support text selection.
// We reproduce its behavior here to open links for these.
ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
getSelectionEnd(), ClickableSpan.class);
if (links.length > 0) {
links[0].onClick(this);
handled = true;
}
}
......
return superResult;
}
3.将MyButton里面的onFilterTouchEventForSecurity方法里面返回分别返回true和false,打印log如下:
@Override
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
Log.i(TAG, "onFilterTouchEventForSecurity: "+event.getAction());
return true/false;
}
//true
07-27 19:13:40.029 32428-32428/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 19:13:40.029 32428-32428/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
07-27 19:13:40.029 32428-32428/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
07-27 19:13:40.029 32428-32428/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
07-27 19:13:40.082 32428-32428/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
07-27 19:13:40.082 32428-32428/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
07-27 19:13:40.082 32428-32428/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
07-27 19:13:40.082 32428-32428/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 1
07-27 19:13:40.083 32428-32428/com.sleep.sunshine I/TestTouchEventActivity: onClick:
//false
07-27 19:15:31.465 1655-1655/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 19:15:31.467 1655-1655/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
4.将MyButton里面的dispatchTouchEvent方法里面返回分别返回true和false,打印log如下:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(TAG, "dispatchTouchEvent: "+event.getAction());
return true/false;
}
//true
07-27 19:19:09.215 4026-4026/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
07-27 19:19:09.264 4026-4026/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
//false
07-27 19:19:46.849 4676-4676/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
#结论:View 中的dispatchTouchEvent通过开关onFilterTouchEventForSecurity(低版本的API是用的onInterceptTouchEvent)里面控制着onTouch和onTouchEvent的调用,而且可以看到当onTouch返回true的时候,就不会调用onTouchEvent方法了,证明了1中的现象。
public boolean dispatchTouchEvent(MotionEvent event) {
.....
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
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;
}
button事件分发小节:
二、在有父布局Layout的情况下的事件分发直接说结论了,有兴趣的同学可以参考上面的方法去打印Log或者翻看源码。
1、不做任何改变时的log
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 0
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity:
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 0
09-18 10:09:45.327 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 0
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 2
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity:
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 2
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 2
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 2
09-18 10:09:45.392 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 2
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 1
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity:
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 1
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 1
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventActivity: onTouch: 1
09-18 10:09:45.396 31952-31952/com.sleep.sunshine I/TestTouchEventButton: onTouchEvent: 1
09-18 10:09:45.397 31952-31952/com.sleep.sunshine I/TestTouchEventActivity: onClick:
2、将MyButton的onFilterTouchEventForSecurity 方法里直接返回false
@Override
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
Log.i(TAG, "onFilterTouchEventForSecurity: "+event.getAction());
// return super.onFilterTouchEventForSecurity(event);
return false;
}
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 0
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity:
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventButton: dispatchTouchEvent: 0
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventButton: onFilterTouchEventForSecurity: 0
09-18 10:13:33.888 1386-1386/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity:
09-18 10:13:33.889 1386-1386/com.sleep.sunshine I/TestTouchEventActivity: onTouch layout: 0
09-18 10:13:33.889 1386-1386/com.sleep.sunshine I/TestTouchEventLayout: onTouchEvent: 0
3、将MyLayout的onFilterTouchEventForSecurity 方法里直接返回false
09-18 10:16:46.071 4555-4555/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 0
09-18 10:16:46.071 4555-4555/com.sleep.sunshine I/TestTouchEventLayout: onFilterTouchEventForSecurity:
3、将MyLayout的dispatchTouchEvent方法里返回true / false
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent: "+ev.getAction());
// return super.dispatchTouchEvent(ev);
return true/false;
}
//true
09-18 10:18:07.333 5480-5480/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 0
//false
09-18 10:18:07.333 5480-5480/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 0
09-18 10:18:07.362 5480-5480/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 2
09-18 10:18:07.380 5480-5480/com.sleep.sunshine I/TestTouchEventLayout: dispatchTouchEvent: 1
4、总结流程图如下:
Layout 的dispatchTouchEvent -> Layout的onFilterTouchEventForSecurity -> Button的dispatchTouchEvent -> Button的onFilterTouchEventForSecurity