Android基础知识(2)—事件处理

Android事件处理概述

  我觉得应用程序需要处理最多的就是用户动作,也就是需要为用户动作提供响应,这种为用户动作提供响应的机制就是事件处理。Android提供了两套事件处理机制:

  • 基于监听的事件处理:主要做法是为Android界面组件绑定特定的事件监听器;
  • 基于回调的事件处理:主要做法是重写Android组件特定的回调方法或者重写Activity的回调方法;

一、基于监听的事件处理:

(一)监听的处理模型:

  1.Event Source(事件源):事件发生的组件;

  2.Event(事件):一次用户的操作;

  3.Event Listener(事件监听器):负责监听事件源发生的事件,并对各事件做出相应的响应;

  (注:我举个例子来说明一下:当用户单击按钮或单击一个菜单项时,这些动作就会激发一个相应的事件,该事件就会触发事件源上已注册的事件监听器,事件监听器调用相应的事件处理器来做出相应的响应。)

(二)事件和事件监听器:

1、事件源最容易创建,任意界面组件都可以作为事件源;

2、事件的产生无需程序员关心,它是由系统自动产生;

3、实现事件监听器是整个事件处理的核心;

(三)在程序中实现事件监听器,通常有以下几种形式:

  第一种:内部类形式作为事件监听器

【实例】当单击按钮时,文本框内容改变

布局文件代码如下:

<LinearLayout 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="songsong.com.eventtest.Event_Qs">
    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按钮" />
</LinearLayout>

Java文件代码如下:

public class Event_Qs extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button button = (Button) findViewById(R.id.btn1);
        button.setOnClickListener(new MyClickListener());
    }
    class MyClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {

            TextView textView = (TextView) findViewById(R.id.tv1);
            textView.setText("按钮被单击了");
        }
    }
}

  第二种:匿名内部类形式作为事件监听器

大部分时候,事件处理器都没有什么复用价值,(可复用代码通常都被抽象成了业务逻辑方法),因此大部分事件监听器知识临时使用一次,所以使用匿名内部类形式的事件监听器更合适。实际上这种形式是目前使用最广泛的事件监听器。

public class Event_Qs extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button button = (Button) findViewById(R.id.btn1);

        button.setOnClickListener(new View.OnClickListener() {
            //实现事件处理方法
            @Override
            public void onClick(View v) {
                TextView textView = (TextView) findViewById(R.id.tv1);
                textView.setText("按钮被单击了");
            }
        });
    }
}

  第三种:Activity本身作为事件监听器

这种形式使用Activity本身作为监听器类,可以直接在Activity类中定义时间处理器方法。这种形式非常简洁,但使用过程一定要注意程序结构。

public class Event_Qs extends Activity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button btn1 = (Button) findViewById(R.id.btn1);
        Button btn2 = (Button) findViewById(R.id.btn2);

        btn1.setOnClickListener(this);
        btn2.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        TextView textView = (TextView) findViewById(R.id.tv1);
        switch (v.getId()){
            case R.id.btn1:
                textView.setText("按钮1被单击了");
                break;
            case R.id.btn2:
                textView.setText("按钮2被单击了");
                break;
        }
    }
}

  第四种:直接绑定到标签作为事件监听器

Android还有一种更简洁的绑定事件监听器方式,那就是直接在界面布局文件为指定标签绑定事件处理方法。

以下是布局文件代码:

<LinearLayout 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="songsong.com.eventtest.Event_Qs">

    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="btnclick"
        android:text="按钮" />
</LinearLayout>

java文件代码如下:

public class Event_Qs extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button btn1 = (Button) findViewById(R.id.btn1);
    }
//定义了一个事件处理方法
 public void btnclick(View source) {
        TextView textView = (TextView) findViewById(R.id.tv1);
        textView.setText("按钮被单击了");
    }
}

上面程序中定义了一个btnclick(View source)方法,并在xml文件的按钮中添加了android:onClick="btnclick"的属性,当用户单击按钮时,该方法就会被激发并处理btn1按钮的单击事件。

二、基于回调的事件处理

正如文章开头所说基于回调的事件处理的主要做法是重写Android组件特定的回调方法或者重写Activity的回调方法,那么相对于基于监听事件处理来说,事件源与事件监听器就合为一体。或者说,事件监听器就消失了。

(一)回调与监听:

为了实现回调机制的事件处理,Android为所有GUI组件都提供了一些事件处理的回调方法,以View为例,该类包含如下方法。

1、boolean onKeyDown(int,KeyEvent):当按下某组件时触发;

2、boolean onKeyLongPress(int,KeyEvent):当长按某组件时触发;

3、boolean onKeyShortcut(int,KeyEvent):当键盘快捷键事件发生时触发;

4、boolean onKeyUp(int,KeyEvent):当组件上松开某个按键时触发;

5、boolean onKeyTouchEvent(event):当用户在组件上触摸时触发;

6、boolean onTrackball(event):当用户在组件上触发轨迹球事件时触发;

【实例】通过自定义View来实现基于回调的事件处理机制,自定义View时重写该View的事件处理方法。

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("songsong.com.eventtest", "The onkeydown in MyButton" );
        return true;
    }
}

在上面自定义了MyButton类中,重写Button类的onKeyDown(int keyCode, KeyEvent event)方法。

接下来是在界面布局中使用自定义View:

<LinearLayout 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="songsong.com.eventtest.Event_Qs">

    <songsong.com.eventtest.MyButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按钮1" />
</LinearLayout>

运行上面程序,先把焦点定位到该按钮上,接着单击模拟器上任意按钮就可以看到效果:

小结:

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

(二)基于回调的事件传播:

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

  • 如果处理事件的回调方法返回是true,表明该处理方法已经完成处理该事件,不会继续传播。
  • 如果处理事件的回调方法返回是false,表明处理方法并未完全处理该事件,会继续传播。

【实例】分别在Activity中重写onKeyDown和Button类中重写onKeyDown,最后为按钮添加OnKeyListener处理OnKeyDown事件,返回值皆为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("songsong.com.eventtest", "The onkeydown in MyButton");
        return false;
    }
}

上面Mybutton子类重写了OnKeyDown,由于返回了false,事件还将继续传播。

public class Event_Qs extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_qs);
        Button btn1 = (Button) findViewById(R.id.btn1);
        btn1.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                //只处理按下键的事件
                if (event.getAction() == KeyEvent.ACTION_DOWN)

                    Log.v("songsong.com.eventtest", "The onkeydown in OnKeyListener");

                return false;//继续外传
            }
        });
    }
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode, event);
        Log.v("songsong.com.eventtest", "The onkeydown in Activity");
        return false;
    }

上面Activity中重写了OnKeyDown,为按钮添加OnKeyListener处理OnKeyDown事件,返回值皆为false。

效果图:

当事件在传播的过程,最先触发的是按钮上绑定的事件监听器,然后才触发组件提供的事件回调方法,最后才会传播到该组件所在的Activity。当然,如果返回值是true,那么就没然后了。

三、响应系统设置的事件:

开Android应用时,有时候可能要让应用程序随系统设置而进行调整,比如系统的屏幕方向、判断系统方向的导航设备等等,需要对系统设置的更改做出响应。

(一)Configuration类:

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

Configuration cf = getResources().getConfiguration();

当获取完Configuration对象就可以调用该对象提供的属性来取得系统配置信息:

1、public int KeyboardHidden:该属性返回布尔值来标识键盘是否可用,如果软键盘可用 KEYBOARDHIDDEN_NO,硬软键盘都不可用就YES;

2、public int mcc:获取移动信号的国家码;

3、public int mnc:获取移动信号的网络码;

4、public int navigation:判断系统上方向导航设备的类型;

5、public intorientation:获取系统屏幕的方向,ORIENTATION_LANDSCAPE (横向),ORIENTATION_PORTRAIT(竖向);

6、public inttouchscreen:获取系统触摸屏的触摸方式;

【实例】获取系统设备状态

首先定义4个文本框来显示系统信息,通过一个按钮触发。布局文件代码因为太简单以至于不贴出来了:

MainActivity.java代码内容:

public class ConfigurationTest extends Activity {
    TextView ori, nav, touch, mnc;
    Button btncg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_configuration);
        ori = (TextView) findViewById(R.id.ori);  //屏幕方向
        nav = (TextView) findViewById(R.id.nav);  //方向导航设备类型
        touch = (TextView) findViewById(R.id.touch);  //触摸方式
        mnc = (TextView) findViewById(R.id.mnc);    //移动信号的网络码
        btncg = (Button) findViewById(R.id.btncg);

        btncg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Configuration cf = getResources().getConfiguration();
                String ori_str = cf.orientation == Configuration.ORIENTATION_LANDSCAPE ? "横向屏幕" : "纵向屏幕";

                String mnc_str = cf.mnc + "";

                String nav_str = cf.navigation == Configuration.NAVIGATION_NONAV ? "没有方向控制" :
                        cf.navigation == Configuration.NAVIGATION_WHEEL ? "滚轮控制方向" :
                                cf.navigation == Configuration.NAVIGATION_DPAD ? "方向键控制方向" : "轨迹球控制方向";
                String touch_str = cf.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH ? "无触摸屏" : "支持触摸屏";

                ori.setText(ori_str);
                nav.setText(nav_str);
                touch.setText(touch_str);
                mnc.setText(mnc_str);
            }
        });
    }
}

效果如下:

(二)重写onConfigurationChanged方法响应系统设置:

如果想监听系统设置的更改,就可以重写Activity的onConfigurationChanged()方法,该方法是一个基于回调的事件处理方法:当系统设置发送更改时,该方法会被自动触发。

【实例】动态更改屏幕方向

布局文件中只有一个按钮,通过对单击按钮事件动态修改系统屏幕的方向。

public class Changecfg extends Activity {
    Button btnchange;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_changecfg);
        btnchange = (Button) findViewById(R.id.btnchange);
        btnchange.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Configuration cfg = getResources().getConfiguration();
                if (cfg.orientation == Configuration.ORIENTATION_LANDSCAPE) {   //如果当前是横屏的话
                    //设置为竖屏
                    Changecfg.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
                }

                if (cfg.orientation == Configuration.ORIENTATION_PORTRAIT) {   //如果当前是竖屏的话
                    //设置为横屏
                    Changecfg.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时加上 android:configChanges="orientation|screenSize",为了就是要监听屏幕方向改变的事件。

 <activity
            android:name=".Changecfg"
            android:configChanges="orientation|screenSize"
            android:label="@string/title_activity_changecfg">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

效果:

四、Handler消息传递机制:

出于性能优化的考虑,Android制定了一条简单的规则:只允许UI线程修改Activity里的UI组件。这样的话就会导致新启动的线程无法动态改变界面组件的属性值,但在实际开发中,尤其是涉及动画的游戏开发中,想要新启动的线程周期性的改变界面组件的属性值就要借助Handler的消息传递机制实现了。

(一)Handler类简介:

根据书上所说,Handler类的主要作用有两个:

  1. 在新启动的线程中发送消息;
  2. 在主线程中获取、处理消息;

那么我们要解决的问题就是:

  • 新启动的线程何时发送消息?
  • 主线程何时去获取并处理消息?

显然,我们这章节的内容是事件处理,那么我们通过回调的方式来实现。开发者只需重写Handler类中处理消息的方法,当新启动的线程发送消息时,消息会发送到与之关联的MessageQueue,而Handler会不断地从MessageQueue中获取并处理消息。

Handler类包含如下方法用于发送、处理消息:

  • void handleMessage(Message msg) :进程通过重写这个方法来处理消息。
  • final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息。
  • final boolean hasMessage(int what,Object object):检查消息队列中是否有指定值和指定对象的消息。
  • Message obtainMessage(): 获取消息,可被多种方式重载。
  • sendEmptyMessage(int what): 发送空消息;
  • final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒之后发送空消息。
  • final boolean sendMessage(Message msg):立即发送消息。
  • final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒之后发送空消息

【实例】自动播放图片

通过一个新的线程来周期性地修改ImageVIew所显示的图片,布局文件中只定义了ImageVIew组件。

public class HandlerTest extends Activity {
    int[] imagesID = new int[]{
            R.drawable.a1,
            R.drawable.a2,
            R.drawable.a3,
            R.drawable.a4,
    };
    int currentImageID = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_test);
        final ImageView show = (ImageView) findViewById(R.id.iv1);
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 0x1233) {
                    show.setImageResource(imagesID[currentImageID++ % imagesID.length]); //动态修改所显示的图片
                }
            }
        };
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                handler.sendEmptyMessage(0x1233);  //发送空消息
            }
        }, 0, 1200);
    }
}

上面程序中的代码通过Timer周期性地执行指定任务。由于Android 不允许在新线程中访问Activity里的界面组件,因此程序只能在新的线程里发送一条消息,通知系统更新ImageView组件。

(二) 深入理解Handler的工作机制:

五、异步任务:

时间: 2024-08-03 12:31:40

Android基础知识(2)—事件处理的相关文章

(Android 基础知识review)打电话

1.main.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基础知识(6)—数据持久化之数据存储

阅读前,请浏览此处上方目录. Android基础知识(6)-数据持久化之数据存储 本章内容为个人笔记,参考书籍有:<疯狂的android>第3版.<第一行代码> 首先,我们要知道什么是数据持久化. 数据持久化就是指那些内存中的瞬时数据保存到存储设备中,保证即使手机在关机的情况下,这些数据不会丢失.保存在内存中的数据是处于瞬时状态,保存在存储设备中的数据是处于持久状态.持久化技术则是提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换. Android系统主要提供了三种方式用于简

android基础知识13:AndroidManifest.xml文件解析

1.重要性 AndroidManifest.xml是Android应用程序中最重要的文件之一.它是Android程序的全局配置文件,是每个 android程序中必须的文件.它位于我们开发的应用程序的根目录下,描述了package中的全局数据,包括package中暴露的组件 (activities, services, 等等),以及他们各自的实现类,各种能被处理的数据和启动位置等重要信息. 因此,该文件提供了Android系统所需要的关于该应用程序的必要信息,即在该应用程序的任何代码运行之前系统所

Android基础知识【项目实训】【1】

[该项目实训是Android基础知识的一个综合练习] [项目题目]:校园订餐App设计 综合案例 [设计目标] 1.必要功能 ?快餐店浏览,与订餐 ?今天订餐活动查询与订餐,特价饭菜预定 ?分类订餐查询,预定 ?常定饭菜  预定 ?健康餐推荐 ?定时预定,提前预定 ?订单查看, ?餐馆与饭菜打分,评价 ?用户注册与登录 2.扩展选择功能 ?快速拨打电话 ?饮食跟踪,热量估算 ?系统设置 [项目说明] 该项目为实际应用项目的单机 简化版本,只需要完成Android平台App的设计与开发工作. Ap

Android基础知识【项目实训】【2】

[该项目实训是Android基础知识的一个综合练习,特别提示:项目中会用到一些图片素材,都是随意整理的,稍后会上传一个资源,包含该事项项目的基本功能,也含有图片素材] [项目题目]:校园订餐App设计 综合案例 [目标] 因为项目只涉及基础知识,因此项目中所用数据并不联网,都读取单机数据库.(即将该项目中所用数据,如菜品信息.店铺信息等存入数据库)用户在第一次打开该项目时,会在用户手机上创建这些数据库,并插入测试数据. 1.先制作一个欢迎界面,欢迎的同时,准备数据库 欢迎界面Activity对应

Android基础知识【项目实训】【3】

[该项目实训是Android基础知识的一个综合练习,特别提示:项目中会用到一些图片素材,都是随意整理的,稍后会上传一个资源,包含该事项项目的基本功能,也含有图片素材] [项目题目]:校园订餐App设计 综合案例 [目标] 欢迎界面过后,应该显示app的主界面了,根据[UI设计指导]中的规划,主界面采用上下两级标签导航.这部分是app开发中比较麻烦的一块. 1.先来看一下,最终的效果吧,这样做起来比较有底: 默认显示的主界面,下部是主导航,上面是二级导航,默认打开的是"促销打折"这一版面

Android基础知识【项目实训】【4】

[该项目实训是Android基础知识的一个综合练习,特别提示:项目中会用到一些图片素材,都是随意整理的,稍后会上传一个资源,包含该事项项目的基本功能,也含有图片素材] [项目题目]:校园订餐App设计 综合案例 [目标] 主界面的功能确实比较复杂,因此上一篇知识说的周边内容.现在开始说这个界面的代码和布局文件. 1.先看一下项目的组织结构吧,要不然不好说他们的关系: (1)db包中的都是跟 数据库相关的 (2)eatall中放的都是activity或者fragment (3)entity中放的实

Android基础知识【项目实训】【5】

[该项目实训是Android基础知识的一个综合练习,特别提示:项目中会用到一些图片素材,都是随意整理的,稍后会上传一个资源,包含该事项项目的基本功能,也含有图片素材] [项目题目]:校园订餐App设计 综合案例 [目标] 主界面中包含两个二级子界面,分别是活动界面和账单界面,下面介绍它们的实现代码和布局文件. 1.下面这个是 活动界面的Activity代码,因为这个界面加载时需要 读取数据库中数据了,所有功能的实现上会涉及到 db那个包中一些类. 注意这个Activity也是继承 Activit

看看android基础知识,谁帮我作答

不管怎么着,了解一点android的基本知识还是有必要的,就当开阔一些自己的眼界吧.... android的四大功能组件是_activity_,_service_,_BroadcastReceive广播接收器_,_Content Provider_. android的系统架构是android,_java_虚拟机和_linux_操作系统. 在Activity的___distoryed______状态和__stop_情况下,系统可能会回收Activity. ActivityA中的某个Button的o