Android 触摸事件机制(五) 触摸事件示例1--默认处理方式

本文将通过示例演示触摸事件的传递流程。

注意:本文是基于Android 4.4.2版本进行介绍的!

目录
1. 示例概述
1.1. 示例简介
1.2. 示例结论
2. 示例源码
2.1. MyActivity的源码
2.2. MyViewGroup的源码
2.3. MyView的源码
3. 运行结果

1. 示例概述

1.1 示例简介

本文的示例:自定义一个Activity,该Activity中的显示内容是包含一个自定义的ViewGroup,该ViewGroup中包含一个自定义的View。

(01) 自定义的Activity是MyActivity
public boolean dispatchTouchEvent(MotionEvent ev): 调用系统默认的dispatchTouchEvent()
public boolean onTouchEvent(MotionEvent ev): 调用系统默认的onTouchEvent()

(02) 自定义ViewGroup是MyViewGroup
public boolean dispatchTouchEvent(MotionEvent ev): 调用系统默认的dispatchTouchEvent()
public boolean onTouchEvent(MotionEvent ev): 调用系统默认的onTouchEvent()
public boolean onInterceptTouchEvent(MotionEvent ev):: 调用系统默认的onInterceptTouchEvent()

(03) 自定义View是MyView
public boolean dispatchTouchEvent(MotionEvent ev): 调用系统默认的dispatchTouchEvent()
public boolean onTouchEvent(MotionEvent ev): 调用系统默认的onTouchEvent()

1.2 示例结论

(01) MyActivity, ViewGroup和View的触摸事件相关API默认都返回false。即,上面列出的API的默认返回值都是false。
(02) 触摸事件的分发顺序是经过MyActivity --> MyViewGroup --> MyView。
它们的触摸事件的入口都是dispatchTouchEvent(),即MyActivity将事件分发给MyViewGroup时,是通过MyActivity.dispatchTouchEvent()去调用MyViewGroup.dispatchTouchEvent();同样的,MyViewGroup将事件分发给MyView时,也是通过MyViewGroup.dispatchTouchEvent()去调用MyView.dispatchTouchEvent()。
它们的对触摸事件的处理都是在onTouchEvent()中完成的。也就是说,会在它们的dispatchTouchEvent()中,皆会调用(它们各自的)onTouchEvent()来对事件进行处理。onTouchEvent()返回true,就表示消费了个事件,或者说接受了个事件。
前面说过消息的分发顺序是MyActivity --> MyViewGroup --> MyView。如果想在MyActivity中进行消息拦截(即,MyActivity不想将消息分发给它包含的视图),则需要重载dispatchTouchEvent()。如果想在MyViewGroup中进行消息拦截(即,MyViewGroup收到触摸事件之后,不想分发给它的子视图),则一般都会通过覆盖onInterceptTouchEvent(),并在onInterceptTouchEvent()返回true来拦截消息。
(03) MyViewGroup和MyView都没有接受ACTION_DOWN事件的话;那么,ACTION_MOVE和ACTION_UP等触摸事件也就不会发送给它们。

Activity中ACTION_DOWN的流程图如下:

2. 示例源码

点击查看:触摸事件示例1的源码

2.1 MyActivity的源码

public class MyActivity extends Activity {
    private static final String TAG = "##skywang-MyActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }   

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        String actionName = Utils.getActionName(event);
        Log.d(TAG, "dispatchTouchEvent(start) :"+actionName);
        boolean ret = super.dispatchTouchEvent(event);
        Log.d(TAG, "dispatchTouchEvent( end ) :"+actionName+", ret="+ret);
        return ret;
    }   

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        String actionName = Utils.getActionName(event);
        Log.d(TAG, "onTouchEvent(start) :"+actionName);
        boolean ret = super.onTouchEvent(event);
        Log.d(TAG, "onTouchEvent( end ) :"+actionName+", ret="+ret);
        return ret;
    }
}

说明:MyActivity的layout是main.xml。虽然它覆盖了dispatchTouchEvent()和onTouchEvent()方法;但它们都是在调用父类的对应的方法的基础之上,添加了打印信息而已。

2.1.1 getActionName的源码

public static String getActionName(MotionEvent event) {
    final int action = event.getAction(); 
    if (action == MotionEvent.ACTION_DOWN) {
        return "DOWN";     
    } else if (action == MotionEvent.ACTION_MOVE) {
        return "MOVE";     
    } else if (action == MotionEvent.ACTION_UP) {
        return "UP";       
    } else if (action == MotionEvent.ACTION_CANCEL) { 
        return "CANCEL";   
    } else {               
        return "NULL";     
    }
}

2.1.2 main.xml的源码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <TextView                  
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Hello World, EventTest-Default" />

    <com.skw.eventtest.MyViewGroup  
        android:layout_width="fill_parent"
        android:layout_height="400dp"
        android:background="#cccccc"
        android:layout_gravity="center"
        android:gravity="center" >

        <com.skw.eventtest.MyView       
            android:layout_width="200dp"
            android:layout_height="100dp"
            android:background="#451c0a" />

    </com.skw.eventtest.MyViewGroup>
</LinearLayout>

说明:main.xml中包含了MyViewGroup,而MyViewGroup中又包含了MyView。

2.2 MyViewGroup的源码

public class MyViewGroup extends LinearLayout {
    private static final String TAG = "##skywang-MyViewGroup";

    public MyViewGroup(Context context){
        super(context);        
    } 

    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs); 
    }     

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        String actionName = Utils.getActionName(event);
        Log.d(TAG, "dispatchTouchEvent(start) :"+actionName);
        boolean ret = super.dispatchTouchEvent(event);
        Log.d(TAG, "dispatchTouchEvent( end ) :"+actionName+", ret="+ret);
        return ret;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        String actionName = Utils.getActionName(event);
        Log.d(TAG, "onTouchEvent(start) :"+actionName);
        boolean ret = super.onTouchEvent(event);
        Log.d(TAG, "onTouchEvent( end ) :"+actionName+", ret="+ret);
        return ret;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        String actionName = Utils.getActionName(event);
        Log.d(TAG, "onInterceptTouchEvent(start) :"+actionName);
        boolean ret = super.onInterceptTouchEvent(event);
        Log.d(TAG, "onInterceptTouchEvent( end ) :"+actionName+", ret="+ret);
        return ret;
    }
}

说明:MyViewGroup继承于ViewGroup。虽然它覆盖了dispatchTouchEvent(), onTouchEvent()和onInterceptTouchEvent()方法;但它们都是在调用父类的对应的方法的基础之上,添加了打印信息而已。

2.3 MyView的源码

public class MyView extends View {
    private static final String TAG = "##skywang-MyView";

    public MyView(Context context) {
        super(context);        
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs); 
    } 

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        String actionName = Utils.getActionName(event);
        Log.d(TAG, "dispatchTouchEvent(start) :"+actionName);
        boolean ret = super.dispatchTouchEvent(event);
        Log.d(TAG, "dispatchTouchEvent( end ) :"+actionName+", ret="+ret);
        return ret;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        String actionName = Utils.getActionName(event);
        Log.d(TAG, "onTouchEvent(start) :"+actionName);
        boolean ret = super.onTouchEvent(event);
        Log.d(TAG, "onTouchEvent( end ) :"+actionName+", ret="+ret);
        return ret;
    }
}

3. 运行结果

3.1 ACTION_DOWN事件

点击MyView所在的区域,ACTION_DOWN相关的log如下:

D/##skywang-MyActivity( 1935): dispatchTouchEvent(start) :DOWN D/##skywang-MyViewGroup( 1935): dispatchTouchEvent(start) :DOWN D/##skywang-MyViewGroup( 1935): onInterceptTouchEvent(start) :DOWN D/##skywang-MyViewGroup( 1935): onInterceptTouchEvent( end ) :DOWN, ret=false D/##skywang-MyView( 1935): dispatchTouchEvent(start) :DOWN D/##skywang-MyView( 1935): onTouchEvent(start) :DOWN D/##skywang-MyView( 1935): onTouchEvent( end ) :DOWN, ret=false D/##skywang-MyView( 1935): dispatchTouchEvent( end ) :DOWN, ret=false D/##skywang-MyViewGroup( 1935): onTouchEvent(start) :DOWN D/##skywang-MyViewGroup( 1935): onTouchEvent( end ) :DOWN, ret=false D/##skywang-MyViewGroup( 1935): dispatchTouchEvent( end ) :DOWN, ret=false D/##skywang-MyActivity( 1935): onTouchEvent(start) :DOWN D/##skywang-MyActivity( 1935): onTouchEvent( end ) :DOWN, ret=false D/##skywang-MyActivity( 1935): dispatchTouchEvent( end ) :DOWN, ret=false

说明:很显然,ACTION_DOWN的流程如下:
(01) MyActivity收到ACTION_DOWN,进入MyActivity.dispatchTouchEvent()
(02) MyActivity.dispatchTouchEvent()对ACTION_DOWN触摸事件进行分发,将消息传递给MyViewGroup。即,进入MyViewGroup.dispatchTouchEvent()
(03) MyViewGroup.dispatchTouchEvent()会调用MyViewGroup.onInterceptTouchEvent()检查自己有没有对触摸事件进行拦截。即先进入MyViewGroup.onInterceptTouchEvent()。 (04) 紧接着,MyViewGroup会退出MyViewGroup.onInterceptTouchEvent()。因为MyViewGroup没有对触摸事件进行拦截,MyViewGroup会继续分发事件。
(05) MyViewGroup将触摸事件分发给MyView,即进入MyView.dispatchTouchEvent()
(06) MyView会调用onTouchEvent()对触摸事件进行处理,即进入MyView.onTouchEvent() 。 (07) 紧接着,MyView会退出MyView.onTouchEvent()。返回false给MyView.dispatchTouchEvent()。
(08) MyView收到MyView.onTouchEvent()的返回值之后,退出MyView.dispatchTouchEvent()。返回false给MyViewGroup的MyViewGroup.dispatchTouchEvent(),表示MyView没有接受该触摸事件。
(09) MyViewGroup则得知MyView没有接受该触摸事件之后,将自己当作一个View,调用View.dispatchTouchEvent();View.dispatchTouchEvent()接着就会进入MyViewGroup.onTouchEvent()
(10) 紧接着,就会退出MyViewGroup.onTouchEvent()。MyViewGroup.onTouchEvent()没有消费该触摸事件,因此返回false。
(11) 然后,View.dispatchTouchEvent()就会结束,并返回false。接着,MyViewGroup就会退出MyViewGroup.dispatchTouchEvent()。并返回false。
(12) MyActivity在得知MyViewGroup没有接受该触摸事件之后,就会调用进入MyActivity.onTouchEvent
(13) 紧接着,就会退出MyActivity.onTouchEvent,并返回false。
(14) 至此,MyActivity.dispatchTouchEvent()才结束。因此,会退出MyActivity.dispatchTouchEvent(),并返回false。

说明:触摸事件的分发顺序是经过MyActivity --> MyViewGroup --> MyView。

3.2 ACTION_MOVE事件

点击MyView所在的区域,ACTION_MOVE相关的log如下:

D/##skywang-MyActivity( 1935): dispatchTouchEvent(start) :MOVE D/##skywang-MyActivity( 1935): onTouchEvent(start) :MOVE D/##skywang-MyActivity( 1935): onTouchEvent( end ) :MOVE, ret=false D/##skywang-MyActivity( 1935): dispatchTouchEvent( end ) :MOVE, ret=false

说明:由于MyViewGroup和MyView都没有接受ACTION_DOWN事件,因此ACTION_MOVE事件就不会再分发给它们。

3.3 ACTION_UP事件

点击MyView所在的区域,ACTION_UP相关的log如下:

D/##skywang-MyActivity( 1935): dispatchTouchEvent(start) :UP D/##skywang-MyActivity( 1935): onTouchEvent(start) :UP D/##skywang-MyActivity( 1935): onTouchEvent( end ) :UP, ret=false D/##skywang-MyActivity( 1935): dispatchTouchEvent( end ) :UP, ret=false

说明:由于MyViewGroup和MyView都没有接受ACTION_DOWN事件,因此ACTION_UP事件就不会再分发给它们。

by skywang
Previous     Next