Android开发系列10——事件处理机制

前言

  Android开发采用Java语言,同时拥有多种Android的组件的包配合开发。Android本质是一种静态语言的开发模式。

  手机用户通过Android设备屏幕的各种动作进行交互,交互过程是:Android系统对动作做出响应机制就是事件处理

Android提供了两种事件处理机制

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

一、基于监听的事件处理

  Android的事件监听机制就是一种”面向对象“的事件处理,是一种”委派模式(Delegate)“事件处理方式。Android的UI组件(事件源)将整个事件处理委托给特定的对象(事件监听器);当该事件源发生指定的事件时,就通知所委托的事件监听器,由事件监听器来处理事件。所以”事件监听“的处理模型中,主要涉及三类对象:

  • Event Source(事件源):事件发生的Android组件。例如:按钮、菜单等
  • Event(事件):Android组件是哪个发生的特定事情,一般都是通过Event对象获取。例如:长按、点击等
  • Event Listener(事件监听器):负责监听事件源所发生的的事件,对各种事件作出相应的反应。

监听事件处理的整体流程如下:

1.将事件监听器注册到事件源
2.通过屏幕触发事件源上的事件A
3.生成事件对象
4.触发的事件监听事件Event作为参数传给监听器
5.事件监听器把出入的Event传给事件处理器
5.事件监听器把出入的Event传给事件处理器
5.事件监听器把出入的Event传给事件处理器
5.事件监听器把出入的Event传给事件处理器
外部动作
Event Source<事件源>
Event<事件>
Event Listener<事件监听器>
事件处理1
事件处理2
事件处理3
事件处理4

通过事件监听的整理流程可以非常好的理解,监听模式的底层运行。接下来介绍一下事件监听模式分为以下几种使用场景:

  • 内部类形式: 将事件监听器类定义成当前类的内部类
  • 外部类形式: 将事件监听器类定义成一个外部类
  • Activity本身作为事件监听器类: 让Activity本身事件监听器接口,并实现事件处理方法
  • 匿名内部类形式: 使用匿名内部类创建监听器对象
  • 直接绑定标签: 直接在界面布局文件中指定标签绑定事件 处理

接下来根据每种场景,列举每种场景使用的例子。

1.内部类形式

内部类形式:1.当前类可以重复使用该监听器类。2.当前类的所有界面组件都可以使用该内部类作为监听器。

public class MainActivity extends AppCompatActivity {

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

        Button nextBtn = findViewById(R.id.mainBtn);
        nextBtn.setOnClickListener(new MyClickListener());
    }

    class MyClickListener implements View.OnClickListener{
        @Override
        public void onClick(View v) {
            System.out.println("----------NextBtn 被点击了-------------------");

            Intent intent2 = new Intent(MainActivity.this,NextActivity.class);
            startActivity(intent2);
        }
    }
}
2.外部类形式

通过外部类,对事件源进行监听,并在外部类实现事件。(更推荐使用内部类

外部监听类:

public class SendActionListener implements View.OnLongClickListener {

    private  Activity act;
    private  EditText contentText;
    public  SendActionListener(Activity act, EditText content){
        this.act = act;
        this.contentText = content;
    }

    @Override
    public boolean onLongClick(View v) {
    	
    	// 事件的实现
        String contentStr = contentText.getText().toString();
        Toast.makeText(act, "输入的内容" + contentStr, Toast.LENGTH_LONG).show();

        return false;
    }
}

事件源:

public class MainActivity extends AppCompatActivity {

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

        Button nextBtn = findViewById(R.id.mainBtn);
        EditText contentEdit = findViewById(R.id.eidtText);

        nextBtn.setOnLongClickListener(new SendActionListener(this, contentEdit));
    }
}

3.Activity本身作为事件监听器类

在使用Activity本身作为事件监听器类,需要注意当前的Activity需要”implements“监听器接口。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        Button nextBtn = findViewById(R.id.mainBtn);

        nextBtn.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        Intent inten = new Intent();
        inten.setClass(this, NextActivity.class);
        startActivity(inten);
    }
}

4.匿名内部类形式

匿名内部类形式没有复用的价值,仅临时使用一次,所以匿名内部类的形式的监听器更适合事件监听。

public class MainActivity extends AppCompatActivity{

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

        Button nextBtn = findViewById(R.id.mainBtn);

        nextBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,NextActivity.class);
                startActivity(intent);
            }
        });
    }
}
5.直接绑定标签

Android直接绑定事件监听器的方法是一种比较简单的方式,就是直接在界面布局文件中为指定标签绑定事件方法。

public class MainActivity extends AppCompatActivity{

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

//    绑定的XML上点击的方法
   //Source 代表事件源

   public void onClickNextBtn(View source){

       Intent intent = new Intent(MainActivity.this,NextActivity.class);
       startActivity(intent);
   }
}

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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:id="@+id/root"
    >
 	<Button
        android:id="@+id/mainBtn"
        android:layout_width="200sp"

        android:layout_height="50sp"
        android:layout_marginStart="59dp"
        android:layout_marginTop="51dp"
        android:onClick="onClickNextBtn"
        android:text="@string/MainBtn"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

以上就是所有关于监听事件处理的方式,根据上述每种监听事件采用的方式对比,监听事件在使用过程中内部类监听的方式和直接绑定的方式更好,降低代码量和耦合度。

二、基于回调的事件处理

  监听机制是一种委托(Delegate)的事件处理方式;而回调机制则是事件源和事件监听是统一的(自己的事情自己干)。
  Android提供的GUI组件上发生的所有事情,系统都提供了对应的事件处理方法,Java是一种静态语言无法给GUI组件或对象提供动态的添加方法,只能继承GUI,通过重写该类的处理方法来实现回调事件处理

  Android为GUI组件提供大量响应事件的方法,可以用于回调的使用。

View中系统提供响应方法:

//View 类的文件中找到对应 事件方法<一小部分>

 /**
     * Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)
     * KeyEvent.Callback.onKeyDown()}: perform press of the view
     * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
     * is released, if the view is enabled and clickable.
     * <p>
     * Key presses in software keyboards will generally NOT trigger this
     * listener, although some may elect to do so in some situations. Do not
     * rely on this to catch software key presses.
     *
     * @param keyCode a key code that represents the button pressed, from
     *                {@link android.view.KeyEvent}
     * @param event the KeyEvent object that defines the button action
     */
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                return true;
            }

            if (event.getRepeatCount() == 0) {
                // Long clickable items don't necessarily have to be clickable.
                final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
                        || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
                if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
                    // For the purposes of menu anchoring and drawable hotspots,
                    // key events are considered to be at the center of the view.
                    final float x = getWidth() / 2f;
                    final float y = getHeight() / 2f;
                    if (clickable) {
                        setPressed(true, x, y);
                    }
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            // This is not a touch gesture -- do not classify it as one.
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION);
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
     * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
     * the event).
     * <p>Key presses in software keyboards will generally NOT trigger this listener,
     * although some may elect to do so in some situations. Do not rely on this to
     * catch software key presses.
     */
    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
        return false;
    }

    /**
     * Default implementation of {@link KeyEvent.Callback#onKeyUp(int, KeyEvent)
     * KeyEvent.Callback.onKeyUp()}: perform clicking of the view
     * when {@link KeyEvent#KEYCODE_DPAD_CENTER}, {@link KeyEvent#KEYCODE_ENTER}
     * or {@link KeyEvent#KEYCODE_SPACE} is released.
     * <p>Key presses in software keyboards will generally NOT trigger this listener,
     * although some may elect to do so in some situations. Do not rely on this to
     * catch software key presses.
     *
     * @param keyCode A key code that represents the button pressed, from
     *                {@link android.view.KeyEvent}.
     * @param event   The KeyEvent object that defines the button action.
     */
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                return true;
            }
            if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
                setPressed(false);

                if (!mHasPerformedLongPress) {
                    // This is a tap, so remove the longpress check
                    removeLongPressCallback();
                    if (!event.isCanceled()) {
                        return performClickInternal();
                    }
                }
            }
        }
        return false;
    }

    /**
     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
     * the event).
     * <p>Key presses in software keyboards will generally NOT trigger this listener,
     * although some may elect to do so in some situations. Do not rely on this to
     * catch software key presses.
     *
     * @param keyCode     A key code that represents the button pressed, from
     *                    {@link android.view.KeyEvent}.
     * @param repeatCount The number of times the action was made.
     * @param event       The KeyEvent object that defines the button action.
     */
    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
        return false;
    }

    /**
     * Called on the focused view when a key shortcut event is not handled.
     * Override this method to implement local key shortcuts for the View.
     * Key shortcuts can also be implemented by setting the
     * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items.
     *
     * @param keyCode The value in event.getKeyCode().
     * @param event Description of the key event.
     * @return If you handled the event, return true. If you want to allow the
     *         event to be handled by the next receiver, return false.
     */
    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
        return false;
    }

    /**
     * Check whether the called view is a text editor, in which case it
     * would make sense to automatically display a soft input window for
     * it.  Subclasses should override this if they implement
     * {@link #onCreateInputConnection(EditorInfo)} to return true if
     * a call on that method would return a non-null InputConnection, and
     * they are really a first-class editor that the user would normally
     * start typing on when the go into a window containing your view.
     *
     * <p>The default implementation always returns false.  This does
     * <em>not</em> mean that its {@link #onCreateInputConnection(EditorInfo)}
     * will not be called or the user can not otherwise perform edits on your
     * view; it is just a hint to the system that this is not the primary
     * purpose of this view.
     *
     * @return Returns true if this view is a text editor, else false.
     */

在自定义控件的时候会发现Android studio提示Button已被AppCompatButton取代,AppCompatButton继承自Button,新增加了对动态的背景颜色等功能的支持.

A Button which supports compatible features on older version of the platform, including:

Supports textAllCaps style attribute which works back to Eclair MR1.
Allows dynamic tint of it background via the background tint methods in ViewCompat.
Allows setting of the background tint using backgroundTint and backgroundTintMode.
This will automatically be used when you use Button in your layouts. You should only need to manually use this class when writing custom views.

所以列举例子的时候选择了使用AppCompatXXX组件进行回调,接下回调的代码如下:

Activity 的代码片段:

package com.skyerkj.testmyb;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;

public class MainActivity extends AppCompatActivity {

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


        MyBtn btn = findViewById(R.id.Btn);

        btn.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                System.out.println("---------setOnTouchListener ----111------------");
                return true;
            }
        });

    }
}

Activity对应的页面XML的代码片段:


<com.skyerkj.testmyb.MyBtn
        android:id="@+id/Btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:height="30dp"
        android:width="100dp"

        android:layout_marginLeft="50dp"
        android:layout_marginTop="100dp"

        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="NextBtn"
         />

自定义的MyBtn的代码片段:

package com.skyerkj.testmyb;

import android.content.Context;
import android.service.autofill.OnClickAction;
import android.util.AttributeSet;
import android.view.MotionEvent;

import androidx.appcompat.widget.AppCompatTextView;

public class MyBtn extends AppCompatTextView {
    public MyBtn(Context context, AttributeSet attributeSet){
        super(context,attributeSet);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        super.onTouchEvent(event);
        System.out.println("-------3333---onTouchEvent-----------");
        return false;
    }
}

通过测试,基于回调的事件处理方法都有一个boolean类型的返回值。
该返回值用于标识该处理方法是否能被完全处理该事件。

  • 如果返回true,表明该处理方法已完全处理该事件,该事件不会被传播出去。
  • 如果返回false,表明该处理方法并未完全处理该事件,该事件回继续传播下去。(被其他方法重复处理)

三、响应系统设置的事件

在Android的开发过程中,App会随着系统设置进行调整,比如屏幕方向、系统方向的方向导航设备等,所以App需要监听系统设置,并对系统设置做出响应。

Configuration类专门用于描述手机设备的配置信息,这些信息及包含用户特定的配置项,也包括系统的动态设备配置。

Configuration对象常用属性如下:

  • public float fontScale: 获取当前用户设置的字体的缩放因子。
  • public int keyboard: 获取当前设备所关联的键盘类型。
  • public int keyboardHidden: 该属性返回一个boolean值用于标识当前键盘是否可用。
  • public Locale locale: 获取用户当前的Local。
  • public int mcc: 获取移动信号的国家码。
  • public int mnc: 获取移动信号的网络码。
  • public int navigation: 判断系统上方向导航设备的类型。
  • public int orientation: 获取系统屏幕的方向。
  • public int touchscreen: 获取系统触摸屏的触摸方式。
1.使用基础的系统配置

程序可调用Activity的如下方法来获取系统的Configuration对象:

Configuration cfg = getResources().getConfiguration();

可以通过Activity测试系统配置类Configuration

public class MainActivity extends AppCompatActivity{

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

    }

//    绑定的XML上点击的方法
    //Source 代表事件源

    public void onClickNextBtn(View source){

        Configuration cfg = getResources().getConfiguration();
        String screen = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ? "横向屏幕":"竖向屏幕";
        String mncCode = cfg.mnc + "";
        String naviName = cfg.navigation ==
                Configuration.NAVIGATION_NONAV
                ?"没有控制方向":
                cfg.navigation == Configuration.NAVIGATION_WHEEL
                        ?"滚轮控制方向":
                        cfg.navigation == Configuration.NAVIGATION_DPAD
                                ?"方向键控制方向":"轨迹球控制方向";
        String touchName = cfg.touchscreen ==
                Configuration.TOUCHSCREEN_NOTOUCH
                ?"无触摸屏":"支持触摸屏";

        System.out.println("----" + screen + "----" + mncCode + "----" + naviName + "----" + touchName);


//        Intent intent = new Intent(MainActivity.this,NextActivity.class);
//        startActivity(intent);
    }
}

// 输出结果:  ----竖向屏幕----65535----没有控制方向----支持触摸屏
2.重写系统配置Configuration类

重写onConfigurationChanged方法响应系统设置更改。

为了让Activity能监听屏幕方向更改的事件,还需要在AndroidManifest.xml配置该Activity时指定android:configChanges属性支持监听屏幕方向事件。


	<activity 
            android:name=".MainActivity"
            android:configChanges="orientation|screenSize"
            >
            <intent-filter>
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

通过按钮强制调整屏幕的方向,可以通过onConfigurationChanged重写监听到屏幕被修改。

public class MainActivity extends AppCompatActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    //    绑定的XML上点击的方法
    //Source 代表事件源

    public void onClickNextBtn(View source){

        Configuration cfg = getResources().getConfiguration();
        // 如果当前是横屏
        if (cfg.orientation == Configuration.ORIENTATION_LANDSCAPE) {

            // 设为竖屏
            MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
        // 如果当前是竖屏
        if (cfg.orientation == Configuration.ORIENTATION_PORTRAIT) {
            // 设为横屏
            MainActivity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }

    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        String screen = newConfig.orientation ==
                Configuration.ORIENTATION_LANDSCAPE?"横向屏幕":"竖向屏幕";
        Toast.makeText(this, screen, Toast.LENGTH_SHORT).show();
    }

}

总结

  Android的事件处理机制就两种方式:监听和回调,系统层面则使用Configuration类对系统配置的事件处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值