Android事件处理机制

前言

你的时间有限,不要浪费于重复别人的生活。不要让别人的观点淹没了你内心的声音。

Android事件处理概述

Android提供了两套强大的事件处理机制:
  • 基于监听的事件处理
  • 基于回调的事件处理

基于监听的事件处理

基于监听的事件处理是一种更“面向对象”的事件处理,在事件监听的处理模型中,主要涉及如下三类对象。
  • EventSource(事件源):事件发生的场所,通常就是各个组件,例如按钮、窗口、菜单等。

  • Event(事件):事件封装了界面组件上发生的特定事情,如果程序需要获得界面组件上所发生事件的相关信息,一般通过Event对象来取得。

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

下面以一个简单的入门程序来示范基于监听的事件处理模型。

代码示例

public class MainActivity extends Activity {

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

        Button bn = (Button) findViewById(R.id.bn);

        bn.setOnClickListener(new MyClickListener());
    }
    class MyClickListener implements View.OnClickListener
    {
        @Override
        public void onClick(View v) {
            TextView txt = (TextView) findViewById(R.id.txt);
            txt.setText("bn被单击了");
        }
    }
}

效果

Screenshot_20171025-151034.png
事件源:程序中的bn按钮。
事件监听器:程序中的MyClickListener类。
注册监听器:setXxxxListener(XxxListener)方法。
如果事件源触发的事件足够简单,事件里封装的信息比较有限,那就无须封装事件对象,将事件对象传入事件监听器即可。但对于键盘事件、触摸屏事件等,此时程序需要获取事件发生的详细信息。例如,键盘事件需要获取是哪个键触发的时间,触摸屏事件需要获取事件发生的位置等,对于这种包含更多信息的时间,Android同样会将事件信息封装成XxxEvent对象,并把该对象作为参数传入事件处理器。
下面以一个简单的移动图片来介绍键盘事件的监听。屏幕中的图片会随用户单击键的动作而移动。

代码示例

PictureView.java
public class PictureView extends View {

    public float currentX;
    public float currentY;
    Bitmap picture;

    public PictureView(Context context) {
        super(context);
        //定义图片
        picture = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
        setFocusable(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //创建画笔
        Paint p = new Paint();
        //绘制图片
        canvas.drawBitmap(picture, currentX, currentY, p);

    }
}
该程序不需要布局文件,直接使用PictureView作为Activity显示的内容,并为PictureView增加键盘事件监听器。
MainActivity.java
public class MainActivity extends Activity {

    //定义移动的速度
    private int speed = 10;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //去掉窗口标题
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        //全屏显示
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        //创建PictureView组件
        final PictureView pv = new PictureView(this);
        setContentView(pv);
        //获取窗口管理器
        WindowManager windowManager = getWindowManager();
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics metrics = new DisplayMetrics();
        //获得屏幕宽和高
        display.getMetrics(metrics);
        //设置图片的初始位置
        pv.currentX = metrics.widthPixels / 2;
        pv.currentY = metrics.heightPixels - 40;

        //为PictureView组件的键盘事件绑定监听器
        pv.setOnKeyListener(new OnKeyListener() {

            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                //获取由哪个键触发的事件
                switch (keyCode) {
                //控制图片下移
                case KeyEvent.KEYCODE_S:
                    pv.currentY += speed;
                    break;
                //控制图片上移
                case KeyEvent.KEYCODE_W:
                    pv.currentY -= speed;
                    break;
                //控制图片左移
                case KeyEvent.KEYCODE_A:
                    pv.currentX -= speed;
                    break;
                //控制图片右移
                case KeyEvent.KEYCODE_D:
                    pv.currentX += speed;
                    break;
                }
                //通知PictureView组件重绘
                pv.invalidate();
                return true;
            }
        });
    }
}

效果

你可以通过键入键盘上的'A'、'S'、'D'、'W'键来实现移动图片。

提示

在程序中实现事件监听器,通常有5种形式。
  1. 内部类形式
    上述的第一个程序就是内部类形式。
  2. 外部类形式
  3. Activity本身作为事件监听器类
    setOnXxxxListenner(this);
  4. 匿名内部类形式
    上述移动图片的程序就是匿名内部类形式。
  5. 直接绑定在XML文件
    android:onClick=""

基于回调的事件处理

如果说事件监听机制是一种委托式的事件处理,那么回调机制则恰好相反:对于基于回调的事件处理模型来说,事件源与事件监听器是统一的,或者说事件监听器完全消失了。
下面的程序示范了基于回调的事件处理机制。正如前面所提到的,基于回调的事件处理机制可通过自定义View来实现,自定义View时重写该View的时事件处理方法即可。

代码示例

MyButton.java
public class MyButton extends Button {

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

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode, event);
        Log.v("-eventdemo", "任意键按下" + keyCode);
        //返回true表明事件不会向外扩散
        return true;
    }
}
callback.xml
<?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" >
    <!-- 使用自定义View时应使用全限定类名 -->
    <com.张敦锋.eventdemo1.MyButton
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="单击"
        />
</LinearLayout>

效果

单击模拟器上的任意键
event1.PNG

提示

对于基于监听的事件模型来说,事件源和事件监听器是分离的,当事件源上发生特定事件时,该事件交给事件监听器负责处理;对于基于回调的时事件处理模型来说,事件源和事件监听器是统一的,当事件源发生特定事件时,该事件还是由事件源本身负责处理。

基于回调的事件传播

几乎所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于标识该处理方法是否能完全处理该事件。
  • 如果处理事件的回调方法返回true,表明该处理方法已完全处理该事件,该事件不会传播出去。
  • 如果处理事件的回调方法返回false,表明该处理方法并未完全处理该事件,该事件会传播出去。
接下里我举个例子来说明事件是如何传播的。

代码示例

public class MyButton extends Button {

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

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode, event);
        Log.v("-MyButton", "任意键按下");
        //返回true表明事件不会向外扩散
        return false;
    }
}
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.callback);
        Button bt = (Button) findViewById(R.id.bt);
        bt.setOnKeyListener(new OnKeyListener() {

            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {

                Log.v("Listener", "任意键按下");
                return false;
            }
        });
    }
//重写onKeyDown方法,该方法可监听它所包含的所有组件的按键被按下事件
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode, event);
        Log.v("Activity", "任意键按下");
        return false;
    }

效果

全部返回false的结果。
event2.1.PNG
把监听器里的返回值设为true,事件将不传播。
event2.2.PNG

响应系统设置的事件

在开发Android应用时,有时候可能需要让应用程序随系统设置而进行调整,比如判断系统的屏幕方向、判断系统方向的方向导航设备等。
程序可调用Activity的如下方法来获取系统的Configuration对象:
Configuration cfg = getResources().getConfiguration();
一旦获得了系统的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:获取系统触摸屏的触摸方式。

代码示例

MainAcivity.java
public class MainActivity extends Activity {

    TextView ori;
    TextView navigation;
    TextView touch;
    TextView mnc;

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

        //获取应用界面中的界面组件
        ori = (TextView) findViewById(R.id.ori);
        navigation = (TextView) findViewById(R.id.navigation);
        touch = (TextView) findViewById(R.id.touch);
        mnc = (TextView) findViewById(R.id.mnc);

    }

    public void show(View v)
    {
        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
                ?"无触摸屏":"支持触摸屏";

        ori.setText(screen);
        mnc.setText(mncCode);
        navigation.setText(naviName);
        touch.setText(touchName);
    }
}

效果

Screenshot_20171026-101005.png

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

下面的程序主要调用Activity的setRequestedOrientation(int)方法来动态更改屏幕方向。

代码示例

MainActivity.java
public class MainActivity extends Activity {

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

    }

    public void change(View v) {
        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();
    }
}

除此之外,为了让Activity能监听屏幕方向更改的事件,还需要在配置该Activity时指定android:configChanges属性。
<activity
            android:configChanges="orientation|screenSize"
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
这样当程序改变手机屏幕方向时,Activity的onConfigurationChanged()方法就会被回调。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 与界面编程紧密相关的就是事件处理机制,当用户在程序界面上执行各种操作时,应用程序必须为用户动作提供响应动作,这种响...
    GB_speak阅读 2,308评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 一.基于监听的事件处理 在事件监听的处理模型中,主要涉及如下三类对象: Event Source(事件源):事件发...
    哇楼主阅读 966评论 0 0
  • 一阵邪风吹过,仿佛失去了理智,头脑眩晕,感觉灵魂渐渐浮出身体,就在自己陷在空白之际,我的灵魂似乎把我带到了长安城楼...
    7827ea344541阅读 886评论 0 0
  • 爱是一场博弈,必须保持永远与对方不分伯仲、势均力敌,才能长此以往地相依相息。因为过强的对手让人疲惫,太弱的对手令人...
    小人物的狂想曲阅读 408评论 2 4