最近元旦放假在家,自己想对一些Android方面的知识进行一些总结,主要是针对自己平时工作所没能用到的知识。自己买了徐宜生大神的《Android群英传》这本书,学习到了很多知识。所以在这里对其中的Android触摸事件的分发和拦截做一个笔记,同时也加上自己的一些理解。
之前在做quick-cocos2dx开发的时候,很多时候需要考虑点击事件的穿透问题,但是自从自己转到Android开发,并没有怎么在意这方面的问题,所以在这里记录一下,加深自己对于Android事件分发的印象。
首先是一个demo,也就是和《Android群英传》里差不多的一个例子,它主要由以下几个代码组成:
ViewGroupA:
package com.demo.yunansong.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;
public class ViewGroupA extends RelativeLayout {
public ViewGroupA(Context context) {
super(context);
}
public ViewGroupA(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewGroupA(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("touch", "ViewGroupA dispatchTouchEvent" + ev.getAction());
// return true;
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("touch", "ViewGroupA onInterceptTouchEvent" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("touch", "ViewGroupA onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}
package com.demo.yunansong.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;
public class ViewGroupB extends RelativeLayout{
public ViewGroupB(Context context) {
super(context);
}
public ViewGroupB(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ViewGroupB(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("touch", "ViewGroupB dispatchTouchEvent" + ev.getAction());
// return true;
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("touch", "ViewGroupB onInterceptTouchEvent" + ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("touch", "ViewGroupB onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
}
package com.demo.yunansong.myapplication;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class MyView extends View {
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("touch", "View onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("touch", "View dispatchTouchEvent" + event.getAction());
return super.dispatchTouchEvent(event);
}
}
package com.demo.yunansong.myapplication;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick1(View view){
Log.d("yunansong", "onClick ViewGroupA");
}
public void onClick2(View view){
Log.d("yunansong", "onClick ViewGroupB");
}
public void onClick3(View view){
Log.d("yunansong", "onClick View");
}
}
两个自定义的ViewGroup ViewGroupA和ViewGroupB;一个自定义的view myview;activity MainActivity以及它的布局activity_main。
它们的运行效果为下:
根据上面的代码,viewgroup中重写了三种方法,view中重写了两种方法,同时为三个自定义的控件添加了onClick函数。后面我们点击的时候都是点击屏幕的左上角,也就是三个自定义控件重叠的区域。
如果是如上面代码所示的话,我们点击一下手机屏幕,logcat应该是如下图所示:
01-03 11:29:45.158 31573-31573/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent0
01-03 11:29:45.158 31573-31573/com.demo.yunansong.myapplication D/touch: ViewGroupA onInterceptTouchEvent0
01-03 11:29:45.158 31573-31573/com.demo.yunansong.myapplication D/touch: ViewGroupB dispatchTouchEvent0
01-03 11:29:45.158 31573-31573/com.demo.yunansong.myapplication D/touch: ViewGroupB onInterceptTouchEvent0
01-03 11:29:45.158 31573-31573/com.demo.yunansong.myapplication D/touch: View dispatchTouchEvent0
01-03 11:29:45.158 31573-31573/com.demo.yunansong.myapplication D/touch: View onTouchEvent0
01-03 11:29:45.238 31573-31573/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent1
01-03 11:29:45.238 31573-31573/com.demo.yunansong.myapplication D/touch: ViewGroupA onInterceptTouchEvent1
01-03 11:29:45.238 31573-31573/com.demo.yunansong.myapplication D/touch: ViewGroupB dispatchTouchEvent1
01-03 11:29:45.238 31573-31573/com.demo.yunansong.myapplication D/touch: ViewGroupB onInterceptTouchEvent1
01-03 11:29:45.238 31573-31573/com.demo.yunansong.myapplication D/touch: View dispatchTouchEvent1
01-03 11:29:45.238 31573-31573/com.demo.yunansong.myapplication D/touch: View onTouchEvent1
01-03 11:29:45.242 31573-31573/com.demo.yunansong.myapplication D/yunansong: onClick View
首先,我们可以明显地看出,view 的onclick事件是不能穿透的。我们点击了view,只有view的点击事件被响应了。
01-03 12:47:00.358 13242-13242/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent0
01-03 12:47:00.438 13242-13242/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent1
如果我们先把代码还原,然后让ViewGroupA的onInterceptTouchEvent返回true,我们的打印值则是:
01-03 12:56:19.782 19152-19152/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent0
01-03 12:56:19.782 19152-19152/com.demo.yunansong.myapplication D/touch: ViewGroupA onInterceptTouchEvent0
01-03 12:56:19.782 19152-19152/com.demo.yunansong.myapplication D/touch: ViewGroupA onTouchEvent0
01-03 12:56:19.890 19152-19152/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent1
01-03 12:56:19.890 19152-19152/com.demo.yunansong.myapplication D/touch: ViewGroupA onTouchEvent1
01-03 12:56:19.890 19152-19152/com.demo.yunansong.myapplication D/yunansong: onClick ViewGroupA
这个时候我们发现,ViewGruopA的事件全部被处理了,但是它拦截了所有向他的子布局传递的点击事件。
01-03 13:01:54.370 27050-27050/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent0
01-03 13:01:54.370 27050-27050/com.demo.yunansong.myapplication D/touch: ViewGroupA onInterceptTouchEvent0
01-03 13:01:54.374 27050-27050/com.demo.yunansong.myapplication D/touch: ViewGroupB dispatchTouchEvent0
01-03 13:01:54.374 27050-27050/com.demo.yunansong.myapplication D/touch: ViewGroupB onInterceptTouchEvent0
01-03 13:01:54.374 27050-27050/com.demo.yunansong.myapplication D/touch: View dispatchTouchEvent0
01-03 13:01:54.374 27050-27050/com.demo.yunansong.myapplication D/touch: View onTouchEvent0
01-03 13:01:54.522 27050-27050/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent1
01-03 13:01:54.522 27050-27050/com.demo.yunansong.myapplication D/touch: ViewGroupA onInterceptTouchEvent1
01-03 13:01:54.522 27050-27050/com.demo.yunansong.myapplication D/touch: ViewGroupB dispatchTouchEvent1
01-03 13:01:54.522 27050-27050/com.demo.yunansong.myapplication D/touch: ViewGroupB onInterceptTouchEvent1
01-03 13:01:54.522 27050-27050/com.demo.yunansong.myapplication D/touch: View dispatchTouchEvent1
01-03 13:01:54.522 27050-27050/com.demo.yunansong.myapplication D/touch: View onTouchEvent1
01-03 13:01:54.522 27050-27050/com.demo.yunansong.myapplication D/yunansong: onClick View
这个和我们第一次打印的值是一样的的,也就是说ontouchevent在一次点击中只会被响应一次,在这里使我们最底层的View的TouchEvent函数被响应了。
我们再把代码还原,把MyView的dispatchTouchEvent的返回值改为true,那么打印值是:
01-03 13:36:16.718 26436-26436/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent0
01-03 13:36:16.718 26436-26436/com.demo.yunansong.myapplication D/touch: ViewGroupA onInterceptTouchEvent0
01-03 13:36:16.718 26436-26436/com.demo.yunansong.myapplication D/touch: ViewGroupB dispatchTouchEvent0
01-03 13:36:16.718 26436-26436/com.demo.yunansong.myapplication D/touch: ViewGroupB onInterceptTouchEvent0
01-03 13:36:16.718 26436-26436/com.demo.yunansong.myapplication D/touch: View dispatchTouchEvent0
01-03 13:36:16.810 26436-26436/com.demo.yunansong.myapplication D/touch: ViewGroupA dispatchTouchEvent1
01-03 13:36:16.810 26436-26436/com.demo.yunansong.myapplication D/touch: ViewGroupA onInterceptTouchEvent1
01-03 13:36:16.810 26436-26436/com.demo.yunansong.myapplication D/touch: ViewGroupB dispatchTouchEvent1
01-03 13:36:16.810 26436-26436/com.demo.yunansong.myapplication D/touch: ViewGroupB onInterceptTouchEvent1
01-03 13:36:16.810 26436-26436/com.demo.yunansong.myapplication D/touch: View dispatchTouchEvent1
说明onTouchevent事件被阻塞了。
通过以上的几个例子,我们可以看出在我们的布局和view里自带的触摸事件的拦截和分发的函数的执行顺序和相应的逻辑,如果还要获得更多的信息,我们不妨把几个类里响应的重载函数都修改一下,通过logcat查看它们的执行顺序和逻辑。
例子的github地址是:点击打开链接
通过自己的进一步学习,有一些我觉得比较重要的点还需要特别注意一下:
ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。
View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。
后面一段话参考自《Android开发艺术探索》:
对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用;如果这个ViewGroup的onInterceptTouchEvent方法返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。
如果对文章有意见的话,希望告诉我,我会改进的~