Android的事件处理机制

事件是可以被识别的操作,如按下确定按钮,选择某个单选按钮或者复选框,到达某个时间点。每一种控件有自己可以识别的事件,如窗体的加载、单击、双击等事件,编辑框(文本框)的文本改变事件等等。事件有系统事件和用户事件。系统事件由系统激发,如时间每隔24小时,银行储户的存款发生变更。用户事件由用户的操作激发,如用户点击按钮,在文本框中输入文本。

在前端程序开发过程中,事件处理是一份重要的工作,应用程序必须为用户或者系统的事件提供响应,这种响应就是事件处理。Android支持两种事件处理机制:基于监听的事件处理机制和基于回调的事件处理机制,下面来讲解这两种事件处理机制。

1、基于监听的事件处理机制

在基于监听的事件处理机制中,主要涉及三类对象:

1)事件源:事件发生的组件。

2)事件:是对整个事件信息的封装,例如对于触控的操作,事件对象中分装了触摸的坐标点;

3)事件处理器:完成具体的事件处理;

 监听机制下,事件处理采用委托机制,也就是说当事件源发生事件后,触发事件监听器,事件监听器根据注册的事件处理程序,将具体的事件委托给具体的处理程序进行处理。

下面给出最常用的两种监听事件处理程序注册机制:

1、在布局标签中直接指定事件处理函数

我们可以直接在布局文件中,指定事件处理函数,如下所示:

布局文件:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    <Button

        android:id="@+id/loginButton"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:onClick="loginButtonClick"

        android:text="登陆" />

</LinearLayout>

       Activity

package com.zhangw.kailyard;

 

import android.app.AlertDialog;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

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 loginButtonClick (View view){

        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        builder.setMessage("登陆成功");

        builder.show();

    }

}

我们在Activity中定义了loginButtonClick方法,并在布局文件中通过指定按钮的onclick属性为方法名称,完成事件的绑定。

2、通过具体的对象实现事件处理

更多的情况下,我们采用对象完成具体的事件处理。为了能够处理特定的事件,对象必须满足一定的契约,也就是要实现特定的接口。通常我们是通过匿名类来实现给定的接口,完成事件处理程序的注册,也可以通过内部类,外部类完成时间处理程序的注册。下面给出一个通过匿名类实现事件处理的代码。

       布局文件

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical">

    <Button

        android:id="@+id/loginButton"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="登陆" />

</LinearLayout>

Activity

package com.zhangw.kailyard;

 

import android.app.AlertDialog;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;

import android.widget.Button;

 

public class MainActivity extends AppCompatActivity {

    private Button loginButton;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

 

        loginButton = findViewById(R.id.loginButton);  // 根据ID查找组件

        loginButton.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View view) {

                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);

                builder.setMessage("登录成功");

                builder.show();

            }

        });

    }

}

       这里需要讲解一下,首先我们通过findViewById,根据按钮的ID获取按钮对象,然后调用setOnClickListener给按钮注册Click事件的处理程序。

setOnClickListener的方法说明说下图所示:

根据方法的签名,我们知道需要传递一个实现了View.OnClickListener接口(View类的内部接口)的对象,我们可以看到View.OnClickListener接口只有一个方法:

我们通过匿名类实现了该接口,并传递给了setOnClickListener方法。

这里还需要注意内部匿名类中,如何访问外部对象的this指针。代码如下图所示:

注意:

AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);

builder.setMessage("登录成功");

builder.show();

这段代码用于弹出一个提示框,后期课程将会给大家详细讲解。

2、基于回调的事件处理机制

下面介绍基于回调的事件处理机制,所谓基于回调的事件处理机制是指事件源和事件处理程序统一了,当事件发生时,直接调用事件源相关的方法完成具体事件处理。针对View对象,Android提供了很多默认的事件处理方法,例如onTouchEvent、onKeyDown等,当我们自定义View时,只需要重新这些方法,就可以按照自己的业务逻辑去完成具体的事件处理。

下面通过一个简单的自定义View演示基于回调的事件处理机制。

自定义TouchButton代码:

package com.practise.salary.hello;

 

import android.content.Context;

import android.util.AttributeSet;

import android.util.Log;

import android.view.KeyEvent;

import android.view.MotionEvent;

import android.widget.Button;

 

public class TouchButton extends Button {

    public TouchButton(Context context, AttributeSet attributeSet) {

        super(context, attributeSet);

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent motionEvent){

        super.onTouchEvent(motionEvent);

        Log.v("基于回调的onTouchEvent事件处理", "按钮中" + motionEvent.toString());

        return true;

    }

 

    @Override

    public boolean onKeyDown(int keyCode, KeyEvent keyEvent){

        super.onKeyDown(keyCode, keyEvent);

        Log.v("基于回调的onKeyDown事件处理", "按钮中" + keyEvent.toString());

        return true;

    }

}

布局文件使用自定的Button

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:background="#fff"

    >

 

    <com.practise.salary.hello.TouchButton

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:text="点击测试回调事件" />

 

</LinearLayout>

运行的结果如下图所示:

在事件处理过程中,有一个重要的概念就是事件传播。也就是说当我们触发了事件后,是否激发后续的事件处理函数。

大家注意,上述的事件回调方法都返回一个boolean值,如果方法返回true,则表示方法已经处理完成了事件,不会传播出去,如果为false,则表示未处理完成事件,需要传播。我们修改一下上面的样例,演示一下事件传播的过程。

首先我们自定义一个布局容器,如下所示:

自定义布局文件

package com.practise.salary.hello;

 

import android.content.Context;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.widget.LinearLayout;

 

public class CustLinearLayout extends LinearLayout {

    public CustLinearLayout(Context context, AttributeSet attributeSet){

        super(context, attributeSet);

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent motionEvent){

        super.onTouchEvent(motionEvent);

        Log.v("基于回调的onTouchEvent事件处理", "布局中" + motionEvent.toString());

        return true;

    }

}

其次,我们修改MainActivity,重写onTouchEvent方法,如下所示:

MainActivity代码

package com.practise.salary.hello;

 

import android.app.Activity;

import android.os.Bundle;

import android.util.Log;

import android.view.MotionEvent;

 

public class MainActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent motionEvent){

        super.onTouchEvent(motionEvent);

        Log.v("基于回调的onTouchEvent事件处理", "Activity中" + motionEvent.toString());

        return true;

    }

}

最后,我们使用两个自定的空间完成页面的设计。

Layout代码

<?xml version="1.0" encoding="utf-8"?>

<com.practise.salary.hello.CustLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:background="#fff"

    >

 

    <com.practise.salary.hello.TouchButton

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:text="点击测试回调事件" />

 

</com.practise.salary.hello.CustLinearLayout>

       Ok,我们运行后,点击按钮,发现只是回调了按钮中的onTouchEvent方法,如下图所示:

下面我们把按钮控件中的onTouchEvent的返回值改成false,再次运行,我们会发现触发了自定义布局的onTouchEvent事件,如下图所示:

最后,我们把布局中的onTouchEvent事件的返回值改成false,运行后我们发现,触发了Activity的onTouchEvent事件,如下图所示:

事件传播只能传播到Activity。

下面我们修改MainActivity代码,在为TouchButton注册一个事件处理程序,代码如下:

修改的后的MainAcitvity代码

package com.practise.salary.hello;

 

import android.app.Activity;

import android.os.Bundle;

import android.util.Log;

import android.view.MotionEvent;

import android.view.View;

 

public class MainActivity extends Activity {

    private TouchButton touchButton;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        touchButton = (TouchButton)findViewById(R.id.touchButton);

 

        touchButton.setOnTouchListener(new View.OnTouchListener() {

            @Override

            public boolean onTouch(View v, MotionEvent event) {

                Log.v("基于事件的onTouchEvent事件处理", "onTouch事件中" + event.toString());

                return false;

            }

        });

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent motionEvent){

        super.onTouchEvent(motionEvent);

        Log.v("基于回调的onTouchEvent事件处理", "Activity中" + motionEvent.toString());

        return true;

    }

}

运行后如下图所示:

我们发现委托的事件处理优先于回调的事件处理。

下面我们把onTouch的事件处理方法返回值改成true,在此运行,如下图所示,

我们会发现事件的传播被阻止了。

事件传播不一定是从下到上,也有可能是从上到下,下面我们分析一下onInterceptTouchEvent事件。

onInterceptTouchEvent是ViewGoup里面的一个事件,他用来拦截Touch操作的。下面我们修改上述代码,并新增加一个MiddleLinearLayout布局。下面给出源代码:

MainAcitvity

package com.practise.salary.hello;

 

import android.app.Activity;

import android.os.Bundle;

 

public class MainActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

    }

}

       CustLinearLayout

package com.practise.salary.hello;

 

import android.content.Context;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.widget.LinearLayout;

 

public class CustLinearLayout extends LinearLayout {

    public CustLinearLayout(Context context, AttributeSet attributeSet){

        super(context, attributeSet);

    }

 

    @Override

    public boolean onInterceptTouchEvent(MotionEvent motionEvent){

        super.onInterceptTouchEvent(motionEvent);

        Log.v("onInterceptTouchEvent", "布局中" + motionEvent.toString());

        return true;

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent motionEvent){

        super.onTouchEvent(motionEvent);

        Log.v("基于回调的onTouchEvent事件处理", "布局中" + motionEvent.toString());

        return false;

    }

}

       MiddleLinearLayout

package com.practise.salary.hello;

 

import android.content.Context;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.widget.LinearLayout;

 

public class MiddleLinearLayout extends LinearLayout {

    public MiddleLinearLayout(Context context, AttributeSet attributeSet){

        super(context, attributeSet);

    }

 

    @Override

    public boolean onInterceptTouchEvent(MotionEvent motionEvent){

        super.onInterceptTouchEvent(motionEvent);

        Log.v("onInterceptTouchEvent", "MiddleLinearLayout布局中" + motionEvent.toString());

        return true;

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent motionEvent){

        super.onTouchEvent(motionEvent);

        Log.v("基于回调的onTouchEvent事件处理", "MiddleLinearLayout布局中" + motionEvent.toString());

        return false;

    }

}

       TouchButton

package com.practise.salary.hello;

 

import android.content.Context;

import android.util.AttributeSet;

import android.util.Log;

import android.view.MotionEvent;

import android.widget.Button;

 

public class TouchButton extends Button {

    public TouchButton(Context context, AttributeSet attributeSet) {

        super(context, attributeSet);

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent motionEvent){

        super.onTouchEvent(motionEvent);

        Log.v("基于回调的onTouchEvent事件处理", "按钮中" + motionEvent.toString());

        return false;

    }

}

activity_main

<?xml version="1.0" encoding="utf-8"?>

<com.practise.salary.hello.CustLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:background="#fff"

    >

    <com.practise.salary.hello.MiddleLinearLayout

        android:layout_width="match_parent"

        android:layout_height="match_parent">

        <com.practise.salary.hello.TouchButton

            android:id="@+id/touchButton"

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            android:text="点击测试回调事件" />

    </com.practise.salary.hello.MiddleLinearLayout>

 

</com.practise.salary.hello.CustLinearLayout>

下面我们运行,结果如下图所示:

       我们发现只是触发了CustLinearLayout中的相关事件,子元素的所有事件都没有触发。

       下面我们把CustLinearLayout的onInterceptTouchEvent方法返回值改成false,再次运行如下图所示:

       我们发现除了CustLinearLayout中的相关事件,MiddleLinearLayout中的相关事件也被触发了。

最后我们把MiddleLinearLayout的onInterceptTouchEvent方法返回值改成false,再次运行,如下图所示

我们发现所有的事件都触发了。

下图我们总结了一下Touch相关的事件触发顺序

       从上图中我们可以明显看出来,onInterceptTouchEvent事件的传递是从上到下的。

       Android的事件处理机制比较凌乱,所幸大多数情况下我们只是简单的通过监听机制完成事件的处理,大家如果在具体的工作中遇到了问题,一定要细心分析,多做一些模拟程序测试一下。本节内容就到此,希望通过本节大家掌握Android的事件处理原理,至于各种组件具体有哪些的事件,我们在学习Android组件时在给大家详细介绍。

(张伟:2018年9月20日)

(转载时请注明来源)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值