android事件多图详解!

最初向要研究android事件的一些机制,是源于MultiDirectionSlidingDrawer这个widget中handler中

放置的按钮点击无效.我想可能是我用的widget没有把这个事件传播给子view吧.于是想认真的研究下android

中事件传播机制.

这里有一往篇篇文章讲得很不错:

orgcent.com/android-touch-event-mechanism/

可惜我在很多地方现在访问不了,所以在此我根据上面的思想.于是我自己实验实验.

自己用的代码如下:

布局:main.xml

 

<?xml version="1.0" encoding="utf-8"?>
<cn.ditouch.eventtest.widget.MyLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <cn.ditouch.eventtest.widget.MyButton
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="hello android event!" />
 
</cn.ditouch.eventtest.widget.MyLinearLayout>
 

 

activity:

 

package cn.ditouch.eventtest;
 
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
 
public class AndroidEventTestActivity extends Activity {
    private static final String TAG = "AndroidEventTestActivity";
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean flag = super.onTouchEvent(event);
        Log.i(TAG, flag + " onTouchEvent:" + event.toString());
        return flag;
    }
 
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean flag = super.dispatchTouchEvent(ev);
        Log.i(TAG, flag + " dispatchTouchEvent:" + ev.toString());
        return flag;
    }
 
}
 

 

 

 

MyLinearLayout:

 

package cn.ditouch.eventtest.widget;
 
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.LinearLayout;
 
public class MyLinearLayout extends LinearLayout {
    private static final String TAG = "MyLinearLayout";
 
    public MyLinearLayout(Context context) {
        super(context);
    }
 
    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean flag = super.onInterceptTouchEvent(ev);
        Log.i(TAG, flag + " onInterceptTouchEvent:" + ev.toString());
        return flag;
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean flag = super.onTouchEvent(event);
        Log.i(TAG, flag + " onTouchEvent:" + event.toString());
        return flag;
    }
 
}
 

 

 

MyButton:

 

package cn.ditouch.eventtest.widget;
 
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;
 
public class MyButton extends Button {
    private static final String TAG = "MyButton";
 
    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean flag = super.onTouchEvent(event);
        Log.i(TAG, flag + " onTouchEvent:" + event.toString());
        return flag;
    }
 
}

 

下面是运行时候的几个截图:(注意我是用模拟器的.目前没有在真机上试过,木有真机用啊!)

为了方便大家看action:下面列出一些常量的值 :

 

0 ACTION_DOWN
1 ACTION_UP
2 ACTION_MOVE
3 ACTION_CANCEL
4 ACTION_OUTSIDE
8 ACTION_SCROLL

 

 

 

运行截图: 


android事件多图详解!_第1张图片

(一)上面默认代码的一些事件:

1.在MyLinearLayout上点击.

对于action=0,即ACTION_DOWN事件的传播顺序是:

ViewGroup:onInterceptTouchEvent()->ViewGroup:onTouchEvent()->Activity:onTouchEvent()

对于 action=1,即ACTION_UP的传播顺序为:(注,这个结果与前面的ViewGroup的返回值为false有关)

Activity:onTouchEvent()

上面都是在默认情况下的结果,其中最关键的默认就是MyLinearLayout:onInterceptTouchEvent()返回了false;

dispatchTouchEvent()在下面再详细解释:

下面先学习点理论再继续实验研究:

看下ViewGroup:onInterceptTouchEvent(MotionEvent)的说明文档如下(基于android2.2源代码):

 

/**
 * Implement this method to intercept all touch screen motion events.  This
 * allows you to watch events as they are dispatched to your children, and
 * take ownership of the current gesture at any point.
 *
 * <p>Using this function takes some care, as it has a fairly complicated
 * interaction with {<a href="http://my.oschina.net/link1212" class="referer" target="_blank">@link</a> View#onTouchEvent(MotionEvent)
 * View.onTouchEvent(MotionEvent)}, and using it requires implementing
 * that method as well as this one in the correct way.  Events will be
 * received in the following order:
 *
 * <ol>
 * <li> You will receive the down event here.
 * <li> The down event will be handled either by a child of this view
 * group, or given to your own onTouchEvent() method to handle; this means
 * you should implement onTouchEvent() to return true, so you will
 * continue to see the rest of the gesture (instead of looking for
 * a parent view to handle it).  Also, by returning true from
 * onTouchEvent(), you will not receive any following
 * events in onInterceptTouchEvent() and all touch processing must
 * happen in onTouchEvent() like normal.
 * <li> For as long as you return false from this function, each following
 * event (up to and including the final up) will be delivered first here
 * and then to the target's onTouchEvent().
 * <li> If you return true from here, you will not receive any
 * following events: the target view will receive the same event but
 * with the action {<a href="http://my.oschina.net/link1212" class="referer" target="_blank">@link</a> MotionEvent#ACTION_CANCEL}, and all further
 * events will be delivered to your onTouchEvent() method and no longer
 * appear here.
 * </ol>
 *
 * @param ev The motion event being dispatched down the hierarchy.
 * @return Return true to steal motion events from the children and have
 * them dispatched to this ViewGroup through onTouchEvent().
 * The current target will receive an ACTION_CANCEL event, and no further
 * messages will be delivered here.
 */
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}

 

个人简单翻译理解如下:

 

 

public boolean onInterceptTouchEvent(MotionEvent ev) 实现以方法以拦截屏幕上所有的touch事件.它可以让你监视转发到你的子View中的事件,并在任何点取得当前手势的所有权. 使用这个方法需要注意几点,因为它与View.onTouchEvent(MotionEvent)的交互比较复杂.使用onTouchEvent(MotionEvent) 需要你以正确的方式实现onInterceptTouchEvent().事件会以下面的顺序接收:

 1.onInterceptTouchEvent()发接收到down事件. 

 2.这个down事件,会被这个ViewGroup的子View处理,或者传递给你自己的onTouchEvent()来处理.这意味着你必须实现onTouchEvent()并返回true,这样你才可以收到其余的手势.(而不是寻找一个父view去处理.),同样,如果你在onTouchEvent(),返回true,你在onInterceptTouchEvent()就不会收到接下来的事件,所以的touch事件都要在onTouchEvent()正确处理. 

3.只要你从onInterceptTouchEvent(MotionEvent)返回false,每一个后续事件(包括到最后的up事件.)将被首先传送到此方法中,然后现到目标的onTouchEvent(). 

4.如果你在此处返回true,此无法接收到后续的其它事件了,目标对象会接收到相同的事件除了ACTION_CANCEL动作.同时所以其它后续事件将传递给你的onTouchEvent()方法,而不会再出现在这个方法中了. 

返回值 : 返回true会将motion事件从子View偷走.并把他们转发到这个ViewGroup的onTouchEvent()方法.当前的目标对象将接收到一个ACTION_CANCEL事件.后续事件也不会父传播到这里了.

看了理论,对照下再看下有多个输出的在MyLinearLayout上移动的输出结果:

android事件多图详解!_第2张图片

看上面的图还需要了解这样一个概念.后面的事件都没有再经过onInterceptTouchEvent()了.

后面的输入就用android事件的消费概念来解释了.即当MyLinearLayout的onTouchEvent()返回false时,也就是说MyLinearLayout没有不想消费事件.所以后面的事件都直接传递到AndroidEventTestActivity中去了. Acitivty也不消费.这个事件就将被丢弃.

下面在MyLinearLayout:onTouchEvent()返回true看输入结果,应该是都后续事件传递到此方法中:

 

@Override
public boolean onTouchEvent(MotionEvent event) {
    boolean flag = super.onTouchEvent(event);
    Log.i(TAG, flag + " onTouchEvent:" + event.toString());
    return true;
}

  

 

因为MyLinearLayout返回了true表示要消费事件.所以后面的事件都将会传递到MyLinearLayout:onTouchEvent()中.

为了更好的看清楚这个返回了true表示消费.现在暂时将dispatchTouchEvent()输出去掉.并移动:

android事件多图详解!_第3张图片

为了更好理解传递消费的传递再看一个在MyButton上移动的事件结果:

android事件多图详解!_第4张图片

这个与在MyLinearLayout上移动,最大的区别就是,由于是在MyLinearLayout中实现的拦截方法onInterceptTouchEvent().

在MyLinearLayout中的onTouchEvent()表示要消费touch事件之后.onTouchEvent()就一直接收到事件,而事件没有经过onInterceptTouchEvent()了.但是在MyButton()上移动之后.每次都会经过MyLinearLayout的拦截方法onInterceptTouchEvent().虽然MyLinearLayout中的onTouchEvent()也将表示对消费事件感兴趣.但事件根本没有经过他了.

 

如果我们有这样的需求.因为Button上的onTouchEvent()其实是在其父类TextView中的实现的.我们想实现虽然是在子View中范围中事件,但我们还是想让它把事件传递给父ViewGroup.这个时候应该怎么办呢?

在onInterceptTouchEvent()中返回true.表示拦截事件.

 

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean flag = true;
    Log.i(TAG, flag + " onInterceptTouchEvent:" + ev.toString());
    return flag;
}

 

并在onTouchEvent()中处理事件,结果如下:

android事件多图详解!_第5张图片

如果MyLinearLayout拦截了些事件但是又不想消费此事件即在onTouchEvent()中返回false,则结果如下(虽然是在MyButton

中移动,且MyButton也表示想要消费此事件)

 

通过上面的介绍与实验我相信对于onInterceptTouchEvent()与的用法,及其与onTouchEvent()的关系,及android事件的消费机制有了很好的理解了.

 

下面就回去开始我们忽略的dispatchTouchEvent()方法吧.
首先看android中文档,源自android2.2源代码:

 

/**
 * Called to process touch screen events.  You can override this to
 * intercept all touch screen events before they are dispatched to the
 * window.  Be sure to call this implementation for touch screen events
 * that should be handled normally.
 * 
 * @param ev The touch screen event.
 * 
 * @return boolean Return true if this event was consumed.
 */
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

 

简单理解如下:

 

用于处理触屏事件,你可以重写此方法可以在所有触屏事件转发到窗口前将其拦截.

对于想通过常规方法处理的触屏事件,请确保调用此实现方法.

返回true表示此方法被消费.

通过查看源代码:

Activity:dispatchTouchEvent()其实就是:

 return false;

 

如果在dispatchTouchEvent()中返回true,并不调用其父类的方法那么在屏幕中移动结果如下:

 

好吧,现在知道其实最最先掌握事件的是Acitivity的dispatchTouchEvent()方法了吧.

 

当代码如下时:

 

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean flag = true;
    Log.i(TAG, flag + " dispatchTouchEvent:" + ev.toString());
    super.dispatchTouchEvent(ev);
    return flag;
}

 

 

 

在屏幕上移动的结果如下:

 

  当MyLinearLayout的onTouchEvent()返回true时,移动输出结果如下:

android事件多图详解!_第6张图片

到此.经过半天实验,对于android的事件,机制,onInterceptTouchEvent()方法,onDispatchTouchEvent()方法有了很好的了解了.

还有一个问题就是,MotionEvent中并不包括事件来源的最顶层View的信息.还要靠自己去解决.

另外一个注意的地方就是.在移动事件中,多个MotionEvent其实是一个.同一个MotionEvent对象.从其中的hastCode如上面的4051e9a0都是一样就可以知道了.MotionEvent文档中对此也有说明,请参考!

你可能感兴趣的:(android)