Android事件传递机制

Android事件传递机制

在实际的开发中,我们经常需要自定义一些控件,这就需要我们对控件的事件响应机制有所了解。探讨Android事件传递机制前,需要明确android的两大基础控件类型:View和ViewGroup。View即普通的控件,没有子布局的,如Button、TextView。 ViewGroup继承自View,表示可以有子控件,如Linearlayout、Listview这些。我们所关注的事件操作主要就是发生在View和ViewGroup之间。View和ViewGroup中主要有如下的3个方法来处理事件的响应操作。

  • public boolean dispatchTouchEvent(MotionEvent event)
  • public boolean onTouchEvent(MotionEvent event)
  • public boolean onInterceptTouchEvent(MotionEvent event)

需要注意的是,dispatchTouchEvent和onTouchEvent方法在View和viewGroup中均存在,而onInterceptTouchEvent方法只存在于ViewGroup中。我们还可以观察到这些方法的返回值全部都是boolean型,都返回true或者是false,这是因为事件传递的过程就是一个接一个,某一个点后根据方法boolean的返回值判断是否要继续往下传递。在Android中,所有的事件都是一步步的传递下去,直到被消费了为止。这些方法的返回值就决定了某一事件是否是继续往下传,还是被拦截了,或是被消费了。返回true表示事件被消费了,这是后续的事件响应函数便不会再被执行。返回false,则事件继续传递下去。

此外,view或ViewGroup还可以设置点击监听onClick和触摸监听onTouch,onClick和onTouch也是事件的响应的函数,所以对于View来说,事件的响应函数一共有4个,对于ViewGroup来说一共有5个。我们可以通过下面的代码来设置onClick和onTouch:

设置onClick:

myView.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
    }
});

设置onTouch:

myView.setOnTouchListener(new View.OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub  
        return false;
    }
});

从原理上来讲,明确事件的传递机制,就是弄清楚dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent、onTouch、onClick的执行顺序的关系。下面我们就分别研究View和ViewGroup的事件传递机制。

View中事件传递

对于View来说,事件传递机制有四个函数:dispatchTouchEvent、onTouchEvent、onClick和onTouch。其中dispatchTouchEvent和onTouchEvent是View中的,onClick和onTouch是我们自己设置的回调函数,当然,我们也可以不设置onClick和onTouch,这样事件传递时会直接忽略这两个函数。为了直观的了解,我们先在Eclipse中新建一个工程,并新建一个MyButton类继承于Button,用来实现我们对按钮事件的跟踪。

MyButton.java的代码如下:

package com.example.eventdispatch;

import android.content.Context;
import android.view.MotionEvent;
import android.widget.Button;

public class MyButton extends Button {

    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("MyButton---dispatchTouchEvent---Down");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("MyButton---dispatchTouchEvent---Up");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("MyButton---dispatchTouchEvent---Move");
            break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("MyButton---onTouchEvent---Down");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("MyButton---onTouchEvent---Up");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("MyButton---onTouchEvent---Move");
            break;
        }
        return super.onTouchEvent(event);
    }

}

activity_main.xml如下

<RelativeLayout 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.eventdispatch.MainActivity" >


    <com.example.eventdispatch.MyButton 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MyButton"
        android:id="@+id/btn1" />

</RelativeLayout>

MainActivity.java如下:

package com.example.eventdispatch;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyButton btn = (MyButton) findViewById(R.id.btn1);
        btn.setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                // TODO Auto-generated method stub
                switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    System.out.println("MyButton---onTouch---Down");
                    break;
                case MotionEvent.ACTION_UP:
                    System.out.println("MyButton---onTouch---Up");
                    break;
                case MotionEvent.ACTION_MOVE:
                    System.out.println("MyButton---onTouch---Move");
                    break;
                }
                return false;
            }

        });

        btn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                System.out.println("MyButton clicked!");
            }

        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("Activity---onTouchEvent---Down");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("Activity---onTouchEvent---Up");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("Activity---onTouchEvent---Move");
            break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // TODO Auto-generated method stub
        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("Activity---dispatchTouchEvent---Down");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("Activity---dispatchTouchEvent---Up");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("Activity---dispatchTouchEvent---Move");
            break;
        }
        return super.dispatchTouchEvent(ev);
    }

}

运行工程,点击Activity界面中除Button以外的其它地方,我们会看到如下的输出结果:

点击Button,我们会看到如下的输出结果:

通过日志输出可以看到,点击屏幕ACTION_DOWN时候首先执行了Activity的dispatchTouchEvent方法进行事件分发,然后执行了onTouchEvent(MotionEvent event)方法;点击Button的时候,首先执行了Activity的dispatchTouchEvent方法进行事件分发,然后执行了Button的dispatchTouchEvent方法进行事件分发,然后执行了Button的onTouch和onTouchEvent(MotionEvent event)方法。流程图如下:

我们知道Android中所有事件处理方法的返回值都是boolean类型的,现在我们来修改这个返回值,来看看会发生什么变化。首先从Activity开始,根据之前的日志输出结果,首先执行的是Activity的dispatchTouchEvent方法,现在将之前的返回值super.dispatchTouchEvent(event)修改为true,然后重新编译运行并点击按钮,看到如下的日志输出结果。

可以看到,事件执行到dispatchTouchEvent方法就没有再继续往下分发了,这表示事件在这个函数中已经被消耗了或是被处理了,后续的函数不需要继续执行了。

接着,将上述修改还原,让事件在Activity这继续往下分发,接着就分发到了MyButton,将MyButton的dispatchTouchEvent方法的返回值修改为true,重新编译运行并查看输出日志结果。

从结果可以看到,事件在MyButton的dispatchTouchEvent方法中就没有再继续往下分发了。接着将上述修改还原,将MyButton的onTouchEvent方法返回值修改为true,让其消费事件,根据之前的分析,onClick方法是在onTouchEvent方法中被调用的,事件在这被消费后将不会调用onClick方法了,编译运行,得到如下日志输出结果。

跟分析结果一样,onClick方法并没有被执行,因为事件在MyButton的onTouchEvent方法中被消费了。下图是整个事件传递的流程图。

到这里,我们已经对Android的事件拦截机制有了一个大概的了解。下面我们来看看ViewGroup的事件传递机制。

Android嵌套布局事件传递

对于ViewGroup来说,事件传递机制有三个函数:dispatchTouchEvent、onTouchEvent和onInterceptTouchEvent。onInterceptTouchEvent表示拦截的意思,该函数后面会讲到,这里可以先不管。

同样,我们新建一个MyLayout.java文件,代码如下:

package com.example.eventdispatch;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;

public class MyLayout extends LinearLayout {

    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("MyLayout---dispatchTouchEvent---Down");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("MyLayout---dispatchTouchEvent---Up");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("MyLayout---dispatchTouchEvent---Move");
            break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("MyLayout---onInterceptTouchEvent---Down");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("MyLayout---onInterceptTouchEvent---Up");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("MyLayout---onInterceptTouchEvent---Move");
            break;
        }
        return super.onInterceptTouchEvent(event);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("MyLayout---onTouchEvent---Down");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("MyLayout---onTouchEvent---Up");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("MyLayout---onTouchEvent---Move");
            break;
        }
        return super.onTouchEvent(event);
    }

}

同时修改activity_main.xml如下:

<LinearLayout 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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.eventdispatch.MainActivity" >

    <com.example.eventdispatch.MyLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/layout1">

        <com.example.eventdispatch.MyButton 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MyButton"
        android:id="@+id/btn1" />

    </com.example.eventdispatch.MyLayout>

</LinearLayout>

最后在MainActivity.java中为mylayout设置onClick和onTouch事件:

MyLayout ml = (MyLayout) findViewById(R.id.layout1);
ml.setOnClickListener(new OnClickListener() {

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        System.out.println("MyLayout clicked!");
    }

});

ml.setOnTouchListener(new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // TODO Auto-generated method stub
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            System.out.println("MyLayout---onTouch---DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            System.out.println("MyLayout---onTouch---MOVE");
            break;
        case MotionEvent.ACTION_UP:
            System.out.println("MyLayout---onTouch---UP");
            break;
        default:
            break;
        }
        return false;
    }

});

运行工程,点击Button,输出如下:

注意:上面只有Button响应了click事件,Layout是没有响应click事件的,这一点很重要,它与Layout的消息截取有关,具体的关系后面会讲到。
我们将MyButton的dispatchTouchEvent方法的返回值修改为true,重新编译运行并查看输出日志结果。

从结果可以看到,事件在MyButton的dispatchTouchEvent方法中就没有再继续往下分发了。接着将上述修改还原,将MyButton的onTouchEvent方法返回值修改为true,让其消费事件,编译运行,得到如下日志输出结果。

可以看到,事件传递到了mybutton的onTouchEvent之后就没有再继续传递下去。这与我们上面所讲的相吻合。

onInterceptTouchEvent

现在我们来看看onInterceptTouchEvent方法。修改onInterceptTouchEvent的返回值为true。重新编译运行,如下

可以看到,我们明明点击的按钮,但输出结果显示MyLayout点击事件被执行了,再通过输出结果分析,对比上次的输出结果,发现本次的输出结果完全没有MyButton的信息,没错,由于onInterceptTouchEvent方法我们返回了true,在这里就将事件拦截了,所以他不会继续分发给MyButton了,反而交给自身的onTouchEvent方法执行了,理所当然,最后执行的就是MyLayout的点击事件了。

总结

Android系统中事件是通过层级传递的,一次事件传递对应一个完整的层级关系。事件传递是从ViewGroup传递到View的,而不是反过来传递的。 Android事件最先由系统调用Activity的dispatchTouchEvent方法,分发该事件。根据触摸事件的坐标,将此事件传递给容器控件或者显示控件的dispatchTouchEvent处理,如果是容器控件则调用onInterceptTouchEvent 判断事件是由自己处理,还是继续分发给子View。此处如果容器控件不处理Touch事件,故根据事件发生坐标,将事件传递给它的直接子View(容器控件或者显示控件)。onTouch事件要先于onClick事件执行,onTouch后于dispatchTouchEvent中调用,而onClick在事件处理方法后于onTouchEvent被调用,onTouchEvent要后于dispatchTouchEvent方法的调用。其他控件事件处理过程同上。最后附上一张图。

参考blog

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值