欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 的事件处理

程序员文章站 2022-03-04 19:42:46
...

标题:Android 的事件处理
作者:猫猫、有点乖

1 Android 的事件处理

不管是桌面应用还是手机应用程序,面对最多的就是用户,经常需要处理的就是用户动作 —— 也就是需要为用户动作提供响应,这种为用户动作提供响应的机制就是事件处理。Android 提供了两种方式的事件处理:

  • 基于回调的事件处理;
  • 基于监听器的事件处理;

对于 Android 基于监听的事件处理而言,主要做法就是为 Android 界面组件绑定特定的事件监听器。

对于 Android 基于回调的事件处理而言,主要做法就是 重写 Android 组件特定的回调方法,或 重写 Activity 的回调方法。Android 为绝大部分界面组件都提供了事件响应的回调方法,开发者只要重写它们就可以了。

那这两者有什么区别吗?一般来说,基于回调的事件处理可用于处理一些具有通用性的事件,并且代码会显得比较简洁。但是对于某些特定的事件,无法使用基于回调的事件处理,只能采用基于监听的事件处理。

2 基于监听的事件处理

基于监听的事件处理是一种更“面向对象”的事件处理,这种处理方式与 Java 的 AWT、Swing 的处理方式几乎完全相同。

2.1 事件监听的处理模型

在事件监听的处理模型中,主要涉及如下三类对象:

  • EventSource(事件源):事件发生的场所,通常就是各个组件;
  • Event(事件):事件封装了界面组件上发生的特定事情,如果程序需要获得界面组件上所发生的相关信息,一般通过 Event 对象来获得;
  • EventListener(事件监听器):负责监听事件源所发生的事件,并对各种事件做出相应的响应;

下面举一个简单的例子来说明:

activity_main.xml

<EditText
    android:id="@+id/txt"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:editable="false"
    android:cursorVisible="false"
    android:textSize="12pt"
    />

<Button
    android:id="@+id/bn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="单击我"
    />

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

        // 获取应用程序中的 bn 按钮
        Button bn = (Button)findViewById(R.id.bn);
        // 为按钮绑定事件监听器
        bn.setOnClickListener(new MyClickListener());
    }

    class MyClickListener implements View.OnClickListener
    {
        @Override
        public void onClick(View arg0)
        {
            EditText txt = (EditText)findViewById(R.id.txt);
            txt.setText("bn按钮被单击了!");
        }
    }
}

以下对上面代码作个简单分析(基于监听的事件处理模型的编程步骤):

  • 获取普通界面组件(事件源),也就是被监听的对象;
  • 实现事件监听器类,该监听器类是一个特殊的 Java 类,必须实现一个 XxxListener 接口;
  • 调用事件源的 setXxxListener 方法将事件监听器对象注册给普通组件;

开发者只需要关注实现事件监听器类,注册监听器也只要一行代码即可。

2.2 事件和事件监听器

如果事件源触发的事件足够简单、事件里封装的信息比较有限,那就无须封闭事件对象,将事件对象传入事件监听器。但对于键盘事件、触摸屏事件等,此时程序需要获取事件发生的详细信息:例如键盘事件需要获取是哪个键触发的事件;触摸屏事件需要获取事件发生的位置等,对于这种包含更多信息的事件,Android 同样会将事件信息封装成 XxxEvent 对象,并把该对象作为参数传入事件处理器。下面举一个简单飞机移动例子:
Android 的事件处理
PlaneView.java

public class PlaneView extends View {
    
    public float currentX;
    public float currentY;
    Bitmap plane;

    public PlaneView(Context context) {
        super(context);
        // 定义飞机图片
        plane = BitmapFactory.decodeResource(context.getResources(), R.drawable.plane);
        setFocusable(true);
    }

    @Override
    public void onDraw (Canvas canvas) {
        super.onDraw(canvas);
        // 创建画笔
        Paint p = new Paint();
        // 绘制飞机
        canvas.drawBitmap(plane, currentX, currentY, p);
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    // 定义飞机的移动速度
    private int speed = 12;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 去掉窗口标题
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // 全屏显示
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // 创建 PlaneView 组件
        final PlaneView planeView = new PlaneView(this);
        setContentView(planeView);
        planeView.setBackgroundResource(R.drawable.back);

        // 获取窗口管理器
        WindowManager windowManager = getWindowManager();
        Display display = windowManager.getDefaultDisplay();

        // 获取屏幕宽和高
        int screenWidth = display.getWidth();
        int screenHeight = display.getHeight();

        // 设置飞机初始位置
        planeView.currentX = screenWidth / 2;
        planeView.currentY = screenHeight - 500;

        // 为 draw 组件键盘事件绑定监听器
        planeView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                // 获取由哪个键触发的事件
                switch (event.getKeyCode()) {
                    // 控制飞机下移
                    case KeyEvent.KEYCODE_DPAD_DOWN:
                        planeView.currentY += speed;
                        break;
                    // 控制飞机上移
                    case KeyEvent.KEYCODE_DPAD_UP:
                        planeView.currentY -= speed;
                        break;
                    // 控制飞机左移
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        planeView.currentX -= speed;
                        break;
                    // 控制飞机右移
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        planeView.currentX += speed;
                        break;
                }
                // 通知 planeView 组件重绘
                planeView.invalidate();
                return true;
            }
        });
    }
}

显示效果如下:
Android 的事件处理
通过键盘上的上下左右键可控制飞机的移动。从以上可得到,在基于事件监听的处理模型中,事件监听器必须实现事件监听器接口,Android 为不同的界面组件 提供了不同的监听接口,这些接口通常以 内部类 的形式存在,在程序中实现事件监听器,通常有如下几种形式:

  • 内部类形式:将事件监听器类定义成当前类的内部类;
  • 外部类形式:将事件监听器类定义成一个外部类;
  • Activity 本身作为事件监听器类:让 Activity 本身实现监听器接口,并实现事件处理方法;
  • 匿名内部类形式:使用匿名内部类创建事件监听器对象;

2.3 内部类作为事件监听器类

使用内部类作为事件监听器类有两个优势:

  • 使用内部类可以在当前类中复用该监听器类;
  • 因为监听器类是外部类的内部类,所以可以*访问外部类的所有界面组件;

例如上面第一个给出的例子是内部类作为事件监听器类。

2.4 外部类作为事件监听器类

使用外部类作为事件监听器类比较少见,主要是因为以下两个原因:

  • 事件监听器通常属于特定的 GUI 界面,定义成外部类不利于提高程序的内聚性;
  • 外部类形式的事件监听器不能*访问创建 GUI 界面的类中的组件,编程不够灵活;

那什么情况下用外部类作为事件监听器类呢?答案是如果某个事件监听器确实需要被多个 GUI 界面所共享,而且主要是完成某种业务逻辑的实现,则可以考虑使用外部类的形式来定义事件监听器类。

2.5 Activity 本身作为事件监听器

这种形式使用 Activity 本身作为事件监听器类,可以直接在 Activity 类中定义事件处理器方法,这种形式非常简洁,但是有两个缺点:

  • 这种形式可能造成程序结构混乱,Activity 的主要职责应该是完成界面初始化工作,但此时还需包含事件处理器方法,从而引起混乱;
  • 如果 Activity 界面类需要实现监听器接口,让人感觉比较怪异;

当为某个组件添加该事件监听器对象时,直接使用 this 作为事件监听器对象即可。

2.6 匿名内部类作为事件监听器类

大部分时候,事件处理器都没有什么复用价值,因此大部分事件监听器只是临时使用一次,所以使用匿名内部类形式的事件监听器更合适。实际上,这种形式是目前使用最广泛的事件监听器形式。

2.7 直接绑定到标签

Android 还有种更简单的绑定事件监听器的方式,直接在界面布局文件中为指定标签绑定事件处理方法。对于很多 Android 标签而言,它们都支持 onClick、onLongClick 等属性,这种属性的属性值就是一个形如 xxx(View source) 的方法的方法名。

activity_main.xml

<Button
    android:id="@+id/bn"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    android:onClick="clickHandler"
    />

MainAcitivity.java

public class MainActivity extends AppCompatActivity {

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

    public void clickHandler(View source) {
        EditText show = (EditText)findViewById(R.id.show);
        show.setText("bn按钮被单击了!");
    }
}

3 基于回调的事件处理

除了前面介绍的基于监听的事件处理模型之外,Android 还提供了一种基于回调的事件处理模型。从代码实现的角度来看,基于回调的事件处理模型更加简单。

3.1 回调机制与监听机制

如果说事件监听机制是一种委托式的事件处理,那么回调机制则恰好与之相反:对于基于回调的事件处理模型来说,事件源与事件监听器是统一的,或者说事件监听器完全消失了。当用户在 GUI 组件上激发某个事件时,组件自己特定的方法将会负责处理该事件。

为了使用回调机制类处理 GUI 组件上所发生的事件,我们需要为该组件提供对应的事件处理方法 —— 而 Java 又是一种静态语言,我们无法为某个对象动态地添加方法,因此只能继承 GUI 组件类,并重写该类的事件处理方法来实现。

public class MyButton extends Button {
    
    public MyButton(Context context, AttributeSet set) {
        super(context, set);
    }
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode, event);
        Log.v("-fanfanblog.cn-", "the onKeyDown in MyButton");
        // 返回 true,表明该事件不会向外扩散
        return true;
    }
}

接下来在布局文件中使用这个自定义的 View,如下:

<cn.fanfanblog.myapplication.MyButton
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="单击我"
    />

Java 程序无须为该按钮绑定事件监听器 —— 因为该按钮自己重写了 onKeyDown(int keyCode, KeyEvent event) 方法,这意味着该按钮将会自己处理相应的事件。

3.2 基于回调的事件传播

几乎所有基于回调的事件处理方法都有一个 boolean 类型的返回值,该返回值用于标识该处理方法是否能完全处理该事件:

  • 如果处理事件的回调方法返回 true,表明处理方法已完全处理该事件,该事件不会传播出去;
  • 如果处理事件的回调方法返回 false,表明处理方法并未完全处理该事件,该事件会传播出去;

对于基于回调的事件传播而言,某组件上所发生的事情不仅激发该组件上的回调方法,也会触发该组件所在 Activity 的回调方法 —— 只要事件能传播到该 Activity。

(本文完)

相关标签: Android