学习Android的第二十四天

目录

Android 事件机制 - 监听

基于监听的事件处理机制

五种不同的使用方式

Android TouchListener

OnTouchListener 相关方法与属性

范例

参考文档

Android 事件机制 - 回调

Android 回调的事件处理机制

自定义 View:

基于回调的事件传播:

Android TouchListener PK OnTouchEvent

Android MotionEvent 多点触碰

范例


Android 事件机制 - 监听

在 Android 应用程序开发中,事件处理机制是至关重要的,以响应用户的交互动作。Android 提供了两种主要的事件处理机制:基于监听的事件处理和基于回调的事件处理。

基于监听的事件处理:

  • 基于监听的事件处理是通过为特定的 UI 控件绑定事件监听器来实现的。当用户执行某个操作时,触发相应的事件,然后调用与之关联的监听器来处理该事件。
  • 例如,为按钮添加点击事件监听器,当用户点击按钮时,触发点击事件,并执行监听器中定义的操作,比如跳转到注册页面或重置密码界面。

基于回调的事件处理:

  • 基于回调的事件处理涉及重写特定的事件回调方法,或者重写 Activity 中的特定回调方法来处理事件。
  • 例如,重写按钮的 onClick 回调方法,在方法中编写跳转逻辑;或者重写 Activity 的 onCreate 方法来初始化界面。

在一般情况下,推荐使用基于回调的事件处理,因为它更直观、易于理解和维护。但在某些特殊情况下,基于监听的事件处理也是必要的,特别是在需要在多个地方重复使用相同的监听器逻辑时。

基于监听的事件处理机制

基于监听的事件处理机制确实是一种委派式的事件处理机制,它由三种对象组成:事件源、事件和事件监听器。它们之间的关系可以通过以下一般流程来描述:

  1. 为某个事件源(例如按钮、文本框等 UI 控件)设置一个监听器,用于监听用户的操作。
  2. 用户的操作触发了事件源的监听器,生成了对应的事件对象。
  3. 将这个事件对象作为参数传递给事件监听器。
  4. 事件监听器对事件对象进行判断,并执行相应的事件处理器(即对应事件的处理方法)。

因此,可以总结为:

事件监听机制是一种委派式的事件处理机制,即事件源将事件处理委托给事件监听器。当事件源发生指定的事件时,会通知指定的事件监听器,以便执行相应的操作。

这种机制使得程序能够响应用户的操作,实现了一种松耦合的设计方式,让事件的产生和处理分离开来,提高了代码的可维护性和灵活性。

五种不同的使用方式

当给一个 UI 控件或 Activity 添加基于监听的事件时,有五种不同的使用方式:

1、直接使用匿名内部类(推荐):

这是一种简洁的方式,可以直接在代码中创建匿名内部类作为事件监听器。

例如,在按钮上添加点击事件监听器:

Button btn_ev = (Button) findViewById(R.id.btn_ev);
btn_ev.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 处理点击事件的逻辑
    }
});

2、使用内部类:

可以在当前类中定义一个内部类,实现特定的事件监听器接口,并将其实例化并绑定到 UI 控件上。
例如,在 Activity 中添加点击事件监听器:

public class MyActivity extends AppCompatActivity {
    private class MyClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            // 处理点击事件的逻辑
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new MyClickListener());
    }
}

3、使用外部类:

可以将事件监听器定义在独立的外部类中,并在需要的地方实例化并绑定到 UI 控件上。
例如,在 Activity 中使用外部类作为点击事件监听器:

public class MyClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
        // 处理点击事件的逻辑
    }
}

public class MyActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new MyClickListener());
    }
}

4、直接使用 Activity 作为事件监听器(常用):

可以将当前的 Activity 直接实现特定的事件监听器接口,并重写其中的回调方法来处理事件。
例如,在 Activity 中直接实现点击事件监听器:

public class MyActivity 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);
        button.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        // 处理点击事件的逻辑
    }
}

5、直接绑定到标签:

Android 提供了一种在布局文件中直接绑定事件处理方法的方式,通过使用 android:onClick 属性指定要调用的方法名。
例如,在布局文件中指定按钮的点击事件处理方法:

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Click Me"
    android:onClick="onButtonClick" />

然后,在对应的 Activity 中定义该方法:

public void onButtonClick(View view) {
    // 处理点击事件的逻辑
}

Android TouchListener

OnTouchListener 相关方法与属性

  OnTouchListener 是 Android 中的一个接口,用于监听触摸事件。它包含一个方法 onTouch(View v, MotionEvent event),当 UI 控件被触摸时,该方法会被调用。

OnTouchListener 接口中的方法与属性:

方法:

  • onTouch(View v, MotionEvent event):当 UI 控件被触摸时,该方法会被调用。其中,参数 v 表示触发触摸事件的 UI 控件,参数 event 表示封装了触发事件的详细信息,包括事件的类型、触发时间等信息。可以通过 event.getX() 和 event.getY() 获得触摸点的坐标。我们也可以通过判断 event.getAction() 的返回值来获取触摸的动作类型。

属性:

  • 无特定属性。

其中,MotionEvent 是一个封装了触摸事件信息的类,它包含了很多方法和常量,用于获取触摸事件的详细信息。常见的方法包括:

  • getX() 和 getY():获取触摸点的坐标。
  • getAction():获取触摸事件的动作类型。
  • getDownTime():获取触摸点按下的时间。
  • getEventTime():获取触摸事件发生的时间。
  • getPressure():获取触摸点的压力大小。
  • getRawX() 和 getRawY():获取触摸点相对于整个屏幕的坐标。
  • getSize():获取触摸点的面积大小。
  • getTouchMajor() 和 getTouchMinor():获取触摸点的长和宽。

通过在 UI 控件上使用 setOnTouchListener 方法,并实现 OnTouchListener 接口,我们可以监听触摸事件并处理相应的逻辑。

范例

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Touch Me"
        android:layout_centerInParent="true"
        android:textSize="24sp"
        android:padding="16dp"
        android:background="@color/black"
        android:textColor="@android:color/white" />

</RelativeLayout>
package com.example.myapplication2;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 找到一个 TextView 控件
        TextView textView = findViewById(R.id.text_view);

        // 设置触摸监听器
        textView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        // 按下事件
                        Log.d("Touch Event", "按下事件");
                        break;
                    case MotionEvent.ACTION_MOVE:
                        // 移动事件
                        Log.d("Touch Event", "移动事件");
                        break;
                    case MotionEvent.ACTION_UP:
                        // 弹起事件
                        Log.d("Touch Event", "弹起事件");
                        break;
                }
                return true; // 返回 true 表示消费了该事件
            }
        });
    }
}

参考文档

  1. Android android.view.MotionEvent

Android 事件机制 - 回调

基于监听 我们已经学习了,现在我们来学习 基于回调的事件处理机制

Android 回调的事件处理机制

在 Android 中,回调的事件处理机制在自定义 View 和基于回调的事件传播中起着重要作用。

自定义 View:

我们需要创建一个自定义的用户界面组件时,可以通过实现特定的回调接口来处理与该组件相关的事件。

例如,我们可以创建一个自定义的按钮控件,并在其中定义一个回调接口用于处理按钮点击事件。当用户点击该按钮时,我们可以触发回调接口中的方法来执行相应的操作。

在 Android 中,很多 UI 控件都提供了一些常见的回调方法,用于处理与用户交互相关的事件。以下是一些常见 View 组件的回调方法及其说明:

  • boolean onTouchEvent(MotionEvent event):当用户触摸 UI 控件时触发,可以用来处理触摸事件,比如点击、滑动等操作。
  • boolean onKeyDown(int keyCode, KeyEvent event):在 UI 控件上按下某个按键时触发,通常用于处理物理按键的按下事件。
  • boolean onKeyUp(int keyCode, KeyEvent event):在 UI 控件上松开某个按键时触发,通常用于处理物理按键的松开事件。
  • boolean onKeyLongPress(int keyCode, KeyEvent event):当用户长按组件的某个按钮时触发,用于处理长按事件。
  • boolean onKeyShortcut(int keyCode, KeyEvent event):当发生键盘快捷键事件时触发,用于处理键盘快捷键的事件。
  • boolean onTrackballEvent(MotionEvent event):在组件上触发轨迹球屏事件时触发,通常用于处理轨迹球的事件。
  • void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect):当 UI 控件的焦点状态发生改变时触发,可以用来处理焦点的变化事件。

范例

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.myapplication2.MyButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按钮"/>

</RelativeLayout>
package com.example.myapplication2;

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 MyButton extends Button {
    private static String TAG = "呵呵";

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

    // 重写键盘按下触发的事件
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.i("TAG", "onKeyDown方法被调用");
        return super.onKeyDown(keyCode, event);
    }

    // 重写弹起键盘触发的事件
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        Log.i("TAG", "onKeyUp方法被调用");
        return super.onKeyUp(keyCode, event);
    }

    // 组件被触摸了
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("TAG", "onTouchEvent方法被调用");
        return super.onTouchEvent(event);
    }
}

基于回调的事件传播:

在 Android 中,事件传播是通过 View 树结构来实现的。当用户在屏幕上进行交互时(比如触摸屏幕、点击按钮等),事件会从上至下地在 View 树中传播,直到找到合适的 View 来处理该事件。在这个过程中,可以使用回调来实现事件的处理和传递。

例如,当用户触摸屏幕时,系统会将触摸事件传递给最顶层的 View(通常是 Activity 的根布局),然后依次向下传播给子 View。如果某个 View 想要拦截并处理该事件,可以通过回调机制来实现。比如,在 onInterceptTouchEvent() 方法中返回 true,表示拦截该事件并停止向下传播;在 onTouchEvent() 方法中处理事件逻辑并返回 true,表示消费该事件。

范例

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.myapplication2.MsEditText
        android:layout_marginTop="16dp"
        android:id="@+id/et_ev"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="自定义 MsEditText"/>

</RelativeLayout>
package com.example.myapplication2;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MsEditText et_ev = (MsEditText)findViewById(R.id.et_ev);

        et_ev.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if(event.getAction() == KeyEvent.ACTION_DOWN)
                {
                    Log.d("MsEditText","监听器的 onKeyDown() 方法被调用");
                }
                return false;
            }
        });
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode, event);
        Log.i("MsEditText","Activity 的 onKeyDown() 方法被调用");
        return false;
    }
}
package com.example.myapplication2;

import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.EditText;
import android.content.Context;

public class MsEditText extends EditText  {

    private static String TAG = "MsEditText";
    public MsEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    //重写键盘按下触发的事件
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode,event);
        Log.d("TAG", "onKeyDown() 方法被调用");
        return false;
    }

    //重写弹起键盘触发的事件
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        super.onKeyUp(keyCode,event);
        Log.d("TAG","onKeyUp() 方法被调用");
        return true;
    }

    //组件被触摸了
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        Log.d("TAG","onTouchEvent() 方法被调用");
        return true;
    }
}

注意!!!!!!:

在Android中,事件处理机制遵循一定的传播顺序:首先是监听器(Listener)优先接收事件,然后是View组件自身的回调方法,最后是Activity的回调方法。

如果任意一个流程中返回 false,则事件会继续向下传播;如果返回 true,则表示该事件被消费,传播会被终止。这种基于回调的事件传播机制确保了灵活性和可定制性,开发者可以根据需要在不同的环节对事件进行处理或拦截,从而实现各种交互逻辑和功能。

Android TouchListener PK OnTouchEvent

onTouchEvent() 方法是View类中用于处理触摸事件的回调方法,它提供了在View内部处理触摸事件的机会。如果在View的onTouchEvent()方法中返回false,表示该View没有消费这个触摸事件,事件会继续向外传播,由父容器或者Activity进行处理。

相比之下,TouchListener(触摸监听器)则是一种侦听触摸事件的接口,可以通过设置监听器来实现对触摸事件的响应。与onTouchEvent()不同的是,TouchListener是基于事件的机制,需要手动注册和移除监听器,并且可以在代码中自由地添加、移除或替换监听器。

通常情况下,如果需要对自定义View进行特定的触摸事件处理,会选择重写onTouchEvent()方法;而如果是对普通的View进行触摸事件监听,则可以使用TouchListener。

Android MotionEvent 多点触碰

Android 系统支持处理多达 256 个手指的触摸事件,但实际上手机硬件一般只支持 2-4 个触摸点,有些手机可能支持更多。MotionEvent 是代表触摸事件的对象,可以通过 event.getAction() 方法来判断触摸事件的类型。

常见的 event.getAction() 返回值有以下几种:

  • MotionEvent.ACTION_DOWN:表示按下事件。
  • MotionEvent.ACTION_MOVE:表示移动事件。
  • MotionEvent.ACTION_UP:表示弹起事件。
  • MotionEvent.ACTION_POINTER_DOWN:表示屏幕上已经有一个点被按住,此时再按下其他点时触发。
  • MotionEvent.ACTION_POINTER_UP:表示屏幕上有多个点被按住,松开其中一个点时触发(非最后一个点被放开时)。

这些事件的触发顺序如下:

  1. 当第一个手指触摸屏幕时,会触发 ACTION_DOWN 事件。
  2. 接着如果有另一个手指也触摸屏幕,会触发 ACTION_POINTER_DOWN 事件。如果还有其他手指触摸,将继续触发该事件。
  3. 当有一个手指离开屏幕时,会触发 ACTION_POINTER_UP 事件。如果还有其他手指离开,将继续触发该事件。
  4. 当最后一个手指离开屏幕时,会触发 ACTION_UP 事件。
  5. 在整个过程中,ACTION_MOVE 事件会持续不断地触发。

在处理触摸事件时,可以使用 event.getPointerCount() 方法来判断当前有多少个手指在触摸屏幕。然后可以通过 event.getX(int) 或者 event.getY(int) 方法来获取不同触摸点的位置,例如 event.getX(0) 可以获得第一个接触点的 X 坐标,event.getX(1) 可以获得第二个接触点的 X 坐标。

范例

写一个范例演示多点触碰( 单指拖动图片,双指缩放图片)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/meimei"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="matrix"
        android:src="@drawable/meimei" />

</RelativeLayout>
package com.example.myapplication2;

import java.lang.Math;

import android.app.Activity;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.widget.ImageView;

public class MainActivity extends Activity implements OnTouchListener {

    private ImageView imageView;
    private Matrix matrix = new Matrix();

    private PointF startPoint = new PointF();
    private float oldDistance = 1f;
    private PointF midPoint = new PointF();
    private boolean isScaling = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        imageView = findViewById(R.id.meimei);
        imageView.setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                startPoint.set(event.getX(), event.getY());
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                oldDistance = calculateDistance(event);
                calculateMidPoint(midPoint, event);
                isScaling = true;
                break;
            case MotionEvent.ACTION_MOVE:
                if (event.getPointerCount() == 1 && !isScaling) {
                    // 单指拖动图片
                    float dx = event.getX() - startPoint.x;
                    float dy = event.getY() - startPoint.y;
                    matrix.postTranslate(dx, dy);
                    imageView.setImageMatrix(matrix);
                    startPoint.set(event.getX(), event.getY());
                } else if (event.getPointerCount() >= 2) {
                    // 双指缩放图片
                    float newDistance = calculateDistance(event);
                    float scale = newDistance / oldDistance;
                    matrix.postScale(scale, scale, midPoint.x, midPoint.y);
                    imageView.setImageMatrix(matrix);
                    oldDistance = newDistance;
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                isScaling = false;
                break;
        }
        return true;
    }

    private float calculateDistance(MotionEvent event) {
        float x = event.getX(0) - event.getX(1);
        float y = event.getY(0) - event.getY(1);
        return (float) Math.sqrt(x * x + y * y);
    }

    private void calculateMidPoint(PointF point, MotionEvent event) {
        float x = (event.getX(0) + event.getX(1)) / 2;
        float y = (event.getY(0) + event.getY(1)) / 2;
        point.set(x, y);
    }
}

相关推荐

  1. 学习Android第二

    2024-03-11 10:50:02       20 阅读
  2. 学习c#第二

    2024-03-11 10:50:02       34 阅读
  3. 学习Android第二

    2024-03-11 10:50:02       20 阅读
  4. 学习Android第二

    2024-03-11 10:50:02       23 阅读
  5. 学习Android第二

    2024-03-11 10:50:02       17 阅读
  6. 学习Android

    2024-03-11 10:50:02       27 阅读
  7. C语言学习第二(预处理)

    2024-03-11 10:50:02       38 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-11 10:50:02       14 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-11 10:50:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-11 10:50:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-11 10:50:02       18 阅读

热门阅读

  1. 我的创作纪念日

    2024-03-11 10:50:02       19 阅读
  2. MetaGPT部分源码解读

    2024-03-11 10:50:02       23 阅读
  3. wpf ListView 列表绑定demo

    2024-03-11 10:50:02       20 阅读
  4. 低代码测试自动化

    2024-03-11 10:50:02       23 阅读
  5. 【力扣】2562. 找出数组的串联值

    2024-03-11 10:50:02       57 阅读
  6. Rust基础知识讲解

    2024-03-11 10:50:02       18 阅读