Android 中触摸事件的分发和拦截

        最近元旦放假在家,自己想对一些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);
    }
}


 ViewGroupB:

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

MyView:

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


MainActivity:

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");
    }
}

activity_main:



    
        
            
        
    



由上面这段代码可以看出,我们的demo由5个部分组成:

两个自定义的ViewGroup ViewGroupA和ViewGroupB;一个自定义的view myview;activity MainActivity以及它的布局activity_main。

它们的运行效果为下:

Android 中触摸事件的分发和拦截_第1张图片


根据上面的代码,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的点击事件被响应了。
然后是点击事件的传递,我们可以明显地看出执行的顺序,下面我们修改一下代码,让ViewGroupA的 dispatchTouchEvent函数返回true,那么我们的打印值是:

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的事件全部被处理了,但是它拦截了所有向他的子布局传递的点击事件。
如果我们再把代码还原,然后让ViewGroupA的onTouchEvent返回true,那么我们的打印值是:

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方法就会被调用,如此反复直到事件被最终处理。


如果对文章有意见的话,希望告诉我,我会改进的~

你可能感兴趣的:(Android)