Android监听、回调事件处理概述

作者:余 均 昊

关键词:Android、监听、回调

一、基于监听的事件处理

1.1、监听的处理模型

监听事件是一种“面向对象”的事件处理,涉及三类对象:

– Event Source(事件源): 事件发生的场所,通常就是各个组件,例如按钮,菜单,窗口等。

– Event(事件):通常就是用户的一次操作,例如单击、触摸、长按、双击等。

– Event Listener(事件监听器):负责监听事件源所发生的事件,并对各种事件做出相应的响应。

基于监听的事件处理机制是一种委派式事件处理方式:普通组件(事件源)将整个事件处理委托给特定的对象(事件监听器);当该事件源发生指定的事件时,就通知所委托的事件监听器,由事件监听器来处理这个事件。

ex:消防所(事件监听器)监听所有的火灾事件并处理火灾,所有的企事业单位(事件源)当发生火灾时本身自己无法灭火,都委拖给消防所来灭火。

图示

描述已自动生成

1.2、监听事件响应处理方法

①匿名类作为监听器(最常用)

优点:

-适合临时使用且复用性不高的场景。

-不需要额外创建文件。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 获取界面上按钮控件
        Button btn_login = findViewById(R.id.btn_login);
        
        /* 匿名类处理事件响应 */
        btn_login.setOnClickListener(new View.OnClickListener() {
            // 重写点击事件的处理方法onClick()
            @Override
            public void onClick(View v) {
                // 点击按钮后,显示的Toast信息
                Toast.makeText(MainActivity.this, "你点击了按钮!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

②内部类作为监听器

优点:

– 提高了代码的复用性和可读性。

– 能直接访问外部类的所有成员变量和方法。

public class MainActivity extends AppCompatActivity {
    // 重新定义一个内部类作为点击监听器
    private class MyClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            // 点击按钮后,要显示的Toast信息
            Toast.makeText(MainActivity.this, "你点击了按钮!", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 拿到XML布局中的按钮
        Button button = findViewById(R.id.my_button);
        
        // 使用内部类作为监听器
        MyClickListener listener = new MyClickListener();
        button.setOnClickListener(listener);
    }
}

③外部类作为监听器

优点:

– 有助于保持代码的整洁和可维护性。

– 减少主Activity的复杂度。

Ⅰ、创建一个新的Java文件

// MyClickListener.java (外部类)
public class MyClickListener implements View.OnClickListener {
    private TextView textshow;
    
    // 把文本框作为参数传入
    public MyClickListener(TextView txt) {
        textshow = txt;
    }
    
    @Override
    public void onClick(View v) {
        // 点击后设置文本框显示的文字
        textshow.setText("点击了按钮!");
    }
}

// MainActivity.java
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 获取文本框引用
        TextView txtshow = findViewById(R.id.txtshow);
        
        // 在MainActivity中使用
        Button button = findViewById(R.id.my_button);
        
        // 直接new一个外部类,并把TextView作为参数传入
        button.setOnClickListener(new MyClickListener(txtshow));
    }
}

④Activity本身作为监听器

优点:

-简化了事件处理器的设置。

-适用于小型项目或者简单的UI交互

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 获取界面上按钮控件
        Button button = findViewById(R.id.button);
        
        // 设置监听器为当前Activity实例
        button.setOnClickListener(this);
    }
    
    // 重写接口中的抽象方法
    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this, "你点击了按钮!", Toast.LENGTH_SHORT).show();
    }
}

⑤直接绑定到标签

优点:

-将UI与行为解耦,简化Activity代码。

-适合简单的点击事件处理。

-对应的方法必须是公开的(public),返回类型为void,并接受一个 View类型的参数。

Ⅰ、在XML布局文件中的代码

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/LinearLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按钮"
        android:onClick="myclick"/>

</LinearLayout>

Ⅱ、在MainActivity中的代码

package com.jay.example.caller;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    // 自定义一个方法,传入一个view组件作为参数
    public void myclick(View source) {
        Toast.makeText(MainActivity.this, "你点击了按钮!", Toast.LENGTH_SHORT).show();
    }
}

⑥Lambda表达式作为监听器

Lambda表达式是一种简洁的语法,用于定义匿名函数或代码块。它允许你以更紧凑的形式编写内联函数,尤其是在处理函数式接口(即只有一个抽象方法的接口)时。

优点

– Lambda表达式可以显著减少代码量

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 获取界面上TextView控件
        TextView show = findViewById(R.id.textview);
        
        // 获取按钮控件
        Button bn = findViewById(R.id.bn);
        
        // 使用Lambda表达式作为事件监听器
        bn.setOnClickListener(v -> Toast.makeText(MainActivity.this, "按钮被点击!", Toast.LENGTH_SHORT).show());
    }
}

1.3、简单的实例

以下代码展示了如何使用匿名类来设置一个简单的按钮点击事件监听器。当用户点击按钮时,文本框的内容将更新,并显示一个短暂的消息提示。

①在XML布局文件中的代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_view_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textSize="18sp"/>

    <Button
        android:id="@+id/button_click_me"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点击我"
        android:layout_gravity="center"/>

</LinearLayout>
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 初始化UI组件
        Button button = findViewById(R.id.button_click_me);
        final TextView textView = findViewById(R.id.text_view_message);
        
        // 使用匿名内部类设置点击监听器
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 更新文本框内容
                textView.setText("按钮被点击了!");
                
                // 显示短暂的Toast消息
                Toast.makeText(MainActivity.this, "你点击了按钮!", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

上述代码设置了一个Button控件和TextView控件,在MainActivity中通过findViewById方法获取到了这两个控件,随后使用匿名内部类设置监听,在用户点击按钮后,监听器捕获到信息,开始执行内部方法。首先通过 `textView.setText(“不显示!”);` 语句来改变文本视图 `textView` 的显示内容,将原来的文本更新为 `”不显示!”`。然后使用 `Toast.makeText(MainActivity.this, “你点击了按钮!”, Toast.LENGTH_SHORT).show();` 语句来弹出一个短暂的 `Toast` 消息。

图片包含 图形用户界面

描述已自动生成

二、基于回调的事件处理

2.1、回调事件的机制

在基于回调的事件处理机制中,事件源与事件监听器是统一的,或者说事件源本身就是事件监听器,事件源产生的事件由事件源自己去解决,解决的方法就是调用重写的回调方法。

①Android为视图组件提供了一些事件处理的回调方法,例如View类的包含:

boolean onKeyDown(int keyCode, KeyEvent event) 按下按键时触发
boolean onKeyLongPress(int keyCode, KeyEvent event) 长按按键时触发
boolean onKeyShortcut(int keyCode, KeyEvent event) 键盘快捷键事件发生时触发
boolean onKeyShortcut(int keyCode, KeyEvent event) 松 开按键时触发
booleanonTouchEvent(MotionEvent event) 触发触摸屏事件时触发
booleanonTrackballEvent(MotionEvent event) 触发轨迹球事件时触发
void onFocusChanged(boolean gainFocus, int direction, Rect previously FocusedRect) 组件焦点发生改变时触发,该方法只能在View中重写

②、触摸屏回调方法会接收一个MotionEvent对象,这个对象包含了触摸动作的具体信息,有以下几种常用类型:

ACTION_DOWN

手指首次触摸到屏幕时触发,触发一次

ACTION_UP 手指抬起时触发,触发一次
ACTION_MOVE 手指在屏幕上滑动时触发,可以触发多次
ACTION_OUTSIDE 触摸事件发生在视图边界时发生

获取MontionEvent对象的方法有:

1.重载Activity中的onTouchEvent(MotionEvent event)方法;

2.View对象调用View.setOnTouchListener接口实现onTouch(View v, MotionEvent event)方法;

下面提供一个简单的关于触摸屏回调方法的示例:

Ⅰ、MainActivity的示例代码

package com.example.callback;

import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements OnTouchCallback {
    // 声明一个 MyCustomView 的实例变量
    private MyCustomView myCustomView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 通过 findViewById 获取布局文件中定义的 MyCustomView 实例
        myCustomView = findViewById(R.id.custom_view);
        
        // 设置触摸回调,将MainActivity作为监听器传递给 MyCustomView
        myCustomView.setOnTouchCallback(this);
    }

    @Override
    public void onTouch(MotionEvent event) {
        // 根据 MotionEvent 中的动作类型来处理不同的触摸事件
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 当手指按下屏幕时触发
                Log.d("MainActivity", "ACTION_DOWN");
                break;
            case MotionEvent.ACTION_MOVE:
                // 当手指在屏幕上移动时触发
                Log.d("MainActivity", "ACTION_MOVE");
                break;
            case MotionEvent.ACTION_UP:
                // 当手指离开屏幕时触发
                Log.d("MainActivity", "ACTION_UP");
                break;
            // 如果是其他类型的触摸动作,则不做任何处理
            default:
                break;
        }
    }
}

Ⅱ、创建放置接口的Java文件

import android.view.MotionEvent;

// OnTouchCallback 是一个回调接口,用于处理触摸事件
public interface OnTouchCallback {
    // 当自定义视图检测到触摸事件时调用的方法
    void onTouch(MotionEvent event);
}

创建的具体步骤如下:

选择Android并找到下图所示,右击选择创建Java文件,输入文件名,点击回车,即可创建成功

图形用户界面, 应用程序

描述已自动生成

图形用户界面, 应用程序

描述已自动生成

图形用户界面, 文本, 应用程序

描述已自动生成

Ⅲ、创建一个Java.Class文件用来接收并处理触摸事件,并将接收到 一个MotionEvent对象,在通过接口回调的方式将这些事件通知给外部的监听器。(创建方法与上面相似,将类型改成Class即可)

public class MyCustomView extends View {
    // 实现了 OnTouchCallback 接口的对象引用
    private OnTouchCallback onTouchCallback;

    // 构造函数
    public MyCustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 设置回调接口的方法
    // 该方法允许外部对象通过传递实现了 OnTouchCallback 接口的实例来注册成为触摸事件的监听者
    public void setOnTouchCallback(OnTouchCallback callback) {
        this.onTouchCallback = callback;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // 如果有设置的回调并且触摸事件发生,则调用回调方法
        if (onTouchCallback != null) {
            onTouchCallback.onTouch(event);
        }
        return true; // 表示事件已被处理
    }
}

Ⅳ、XML布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.callback.MyCustomView
        android:id="@+id/custom_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

注意:

图形用户界面, 文本, 应用程序

描述已自动生成

图形用户界面, 文本, 应用程序, 电子邮件

描述已自动生成

运行日志:

图形用户界面

低可信度描述已自动生成

③、Android为Activity 和 Fragment 提供了生命周期相关的回调方法:

onCreate(Bundle savedInstanceState) 当Activity首次创建时调用
onStart() 当Activity变得可见给用户时调用
onResume() 当用户开始与Activity交互时调用
onPause() 当另一个Activity获取到前台焦点时调用
onStop() 当Activity不再对用户可见时调用
onDestroy() 当Activity被销毁时调用
onRestart() 当Activity重新启动时调用(从停止状态返回)

下面代码简单展示了如何使用Activity生命周期中的几个关键方法:

Ⅰ、在MainActivity中的代码

package com.example.callback;

import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "LifecycleExample";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 当Activity首次创建时调用
        Log.d(TAG, "onCreate: Activity首次创建");
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 当Activity变得可见给用户时调用
        Log.d(TAG, "onStart: Activity变得可见 ");
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 当用户开始与Activity交互时调用
        Log.d(TAG, "onResume: Activity开始交互");
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 另一个Activity部分覆盖了当前Activity
        Log.d(TAG, "onPause: 暂停当前页面,但保持Activity正常进行");
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 当Activity不再对用户可见时调用
        Log.d(TAG, "onStop: Activity不再对用户可见时调用");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 当Activity被销毁时调用
        Log.d(TAG, "onDestroy: Activity被销毁时调用");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        // 当Activity被重新启动时调用
        Log.d(TAG, "onRestart: Activity被重新启动时调用");
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_view_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:layout_marginTop="20dp"/>

</LinearLayout>

看日志:第一次运行时可以从日志中看到上述我们写的前三个方法被成功回调了,此时这个界面能够被用户看到

当我们点击home键时,就能从日志中看到,此时界面进入后台,用户不可见

图形用户界面, 应用程序, Word, Teams

描述已自动生成

图形用户界面, 文本, 应用程序

描述已自动生成

当我们点击Overview键,再次选中这个界面

图形用户界面, 应用程序

描述已自动生成

文本

描述已自动生成

当我们点击返回键时

图形用户界面, 应用程序, Word

描述已自动生成

图片包含 文本

描述已自动生成

总结:这就是Android提供的较为常用的生命周期相关的回调方法,当然在这里只是简单的演示了一下,教大家如何使用。

2.2、回调事件的处理

基于回调的事件处理步骤为:

(1)定义接口:创建一个java文件用来存放接口

(2)实现接口:在Activity或Fragment中需要响应事件的类中实现该接口

(3)设置监听器:为控件设置监听器,也可以自定义控件

(4)触发回调:当特定事件发生时,调用之前设置的监听器中的方法

简单实例:

以下代码定义一个自定义回调接口,并通过该接口来处理按钮点击事件,从而更新文本框的内容

①在你的项目中创建一个新的Java文件CustomCallback.java,用于定义自定义的回调接口:(创建的具体步骤在上面)

// CustomCallback.java
package com.example.csdn;

// 定义一个自定义回调接口
public interface CustomCallback {
    void onButtonClick(String message);
}

“`html

// MainActivity.java
package com.example.csdn;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements CustomCallback {
    // 声明textView变量
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // 初始化UI组件
        Button button = findViewById(R.id.button_click_me);
        textView = findViewById(R.id.text_view_message);
        
        // 设置点击监听器,并传递当前活动作为回调接口的实现
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 调用自定义回调方法
                onButtonClick("按钮被点击了!");
            }
        });
    }

    // 实现自定义回调接口的方法
    @Override
    public void onButtonClick(String message) {
        // 更新文本框内容
        textView.setText(message);
        
        // 显示短暂的Toast消息
        Toast.makeText(this, "你点击了按钮!", Toast.LENGTH_SHORT).show();
    }
}
// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_view_message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=""
        android:textSize="18sp"/>

    <Button
        android:id="@+id/button_click_me"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点击我"
        android:layout_gravity="center"/>

</LinearLayout>

对于上述代码:首先,先创建一个新的Java文件用于存放回调需要的接口;接着创建一个按钮和文本框,在MainActivity中,我们先对控件初始化,通过 findViewById 方法在已加载的布局中查找 ID 为 button_click_me 的按钮控件,并将其实例赋值给 button变量;同样的textView 也是如此;随后,设置一个监听器用来监听按钮以及调用自定义的方法;最后,重写@Override的方法,自定义一个包含更新文本框和短暂显示消息的方法,并在监听器中调用。

图片包含 形状

描述已自动生成

三、监听与回调的区别

监听

定义:监听就是当特定的操作(如点击、滑动、长按等)发生在某个 UI 组件上时(如:按钮、文本框等),该组件会触发相应的事件并通知已注册的监听器来响应这些事件。例如,你点击了一个按钮,Android会把这一事件发送给一个正在等待的监听器,而监听器在接收到消息后,就会执行存在监听器内部的方法。

回调

定义:通俗来说,回调就是A组件调用B组件的方法,并传递回调接口,在B完成任务后,通过接口通知A,A可以根据B的通知来执行接下的代码。打个比方,你让小王帮你做事,你跟小王说做完通知你一声,小王在做完后跟你说他做完了,这样你就可以做接下来的事了。

区别总结

特性 监听 回调
主要用途 响应用户界面事件 用于解耦代码模块,支持广泛的通信模式
接口类型 预定义接口 可以是任意接口、抽象类,甚至是匿名类
是否返回值 大多没有返回值 可以有返回值,取决于具体需求
触发方式 由系统或框架自动调用 由开发者控制何时调用
适用范围 主要用于UI组件 广泛应用于各种编程场景

四、总结

本质区别:在监听中事件源是主动方,在事件发生时调用监听器的方法;在回调中调用者是主动方,在自身流程中调用回调对象的方法。监听器可以监听多个View控件的事件,而回调却不行。

尽管监听器和回调在某些方面有重叠,但它们各自有着明确的角色和使用场景。监听器更适合处理用户界面中的事件,而回调则提供了更为灵活的方式来实现不同代码模块之间的通信。

10 次浏览
滚动至顶部