Android学习读书笔记之事件处理
一.Android事件处理概述
Android提供了两套强大的事件处理机制:
1.基于监听的事件处理
主要的做法就是为Android界面组件绑定特定的事件监听器。
2.基于回调的事件处理
主要的做法就是重写Android组件特定的回调方法,或者重写Activity的回调方法。Android为绝大部分界面组件都提供了事件响应的回调方法,开发者只要重写他们即可。
二.基于监听的事件处理
1.监听的处理模型
Event Source(事件源):
事件发生的场所,通常就是各个组件,例如按钮,窗口,菜单等。
Event(事件):
事件封装了界面组件上发生的特定事件(通常就是一次用户操作),如果程序需要获得界面组件上所发生事件的相关信息,一般通过Event对象来取得。
Event Listener(事件监听器):
负责监听事件源所发生的事件,并对各种事件做出相应响应。
实例:
该界面布局中只是定义了两个组件:一个文本框和一个按钮。
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal">
<EditText
android:id="@+id/txt"
android:layout_width="match_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="单击我"/>
</LinearLayout>
MainActivty.java
package org.crazyit.event;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获取应用程序中的bn按钮
Button bn = (Button) findViewById(R.id.bn);
// 为按钮绑定事件监听器
bn.setOnClickListener(new MyClickListener()); // ①
}
// 定义一个单击事件的监听器
class MyClickListener implements View.OnClickListener
{
// 实现监听器类必须实现的方法,该方法将会作为事件处理器
@Override
public void onClick(View v)
{
EditText txt = (EditText) findViewById(R.id.txt);
txt.setText("bn按钮被单击了!");
}
}
}
事件源:button按钮
事件监听器:MyClickListener类,监听器类必须由程序员负责实现,实现监听器类的关键就是实现处理器方法。
注册监听器:只要调用事件源的SetxxxListener(xxxListener)方法即可。
对于键盘事件和触摸屏事件等,此时程序需要获取事件发生的详细信息。例如键盘事件需要获取是那个键触发的事件,触摸屏事件需要获取事件发生的位置等,对于这种包含更多信息的事件,Android同样会将事件信息封装成XXXexent对象,并把该对象作为参数传入事件处理器。
以View类为例,它包含了如下几个内部接口:
1> View.OnClickListener: 单击事件的事件监听器必须实现的接口;
2> View.OnCreateContextMenuListener: 创建上下文菜单事件的事件监听器必须实现的接口;
3> View.onFocusChangeListener: 焦点改变事件的事件监听器必须实现的接口;
4> View.OnKeyListener: 按键事件的事件监听器必须实现的接口;
5> View.OnLongClickListener: 长按事件的事件监听器必须实现的接口;
6> View.OnTouchListener: 触摸事件的事件监听器必须实现的接口。
在程序中实现事件监听器,主要方式:
1.内部类形式
2.外部类形式
3.Activity本身作为事件监听器类
4.匿名内部类形式
2.外部类作为事件监听器类
如果某个事件监听器确实需要被多个GUI界面所共享,而且主要是完成某种业务逻辑的实现,则可以考虑使用外部类形式来定义事件监听器类。
SendSmsListener.java
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.telephony.SmsManager;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class SendSmsListener implements OnLongClickListener
{
private Activity act;
private EditText address;
private EditText content;
public SendSmsListener(Activity act, EditText address
, EditText content)
{
this.act = act;
this.address = address;
this.content = content;
}
@Override
public boolean onLongClick(View source)
{
String addressStr = address.getText().toString();
String contentStr = content.getText().toString();
// 获取短信管理器
SmsManager smsManager = SmsManager.getDefault();
// 创建发送短信的PendingIntent
PendingIntent sentIntent = PendingIntent.getBroadcast(act
, 0, new Intent(), 0);
// 发送文本短信
smsManager.sendTextMessage(addressStr, null, contentStr
, sentIntent, null);
Toast.makeText(act, "短信发送完成", Toast.LENGTH_LONG).show();
return false;
}
}
MainActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends Activity
{
EditText address;
EditText content;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获取页面中收件人地址、短信内容
address = (EditText)findViewById(R.id.address);
content = (EditText)findViewById(R.id.content);
Button bn = (Button)findViewById(R.id.send);
// 使用外部类的实例作为事件监听器
bn.setOnLongClickListener(new SendSmsListener(
this , address, content));
}
}
3.Activity本身作为事件监听器类
可以直接在Activity类中定义事件处理器的方法。但是存在两个缺点,
3.1 这种形式可能造成程序结构混乱,Activity的主要职责应该是完成界面初始化工作,但此时还需包含事件处理器方法,从而引起混乱;
3.2 如果Activity界面类需要实现监听器接口,让人感觉怪异;
MainActivity.java
// 实现事件监听器接口
public class MainActivity extends Activity
implements View.OnClickListener
{
EditText show;
Button bn;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
show = (EditText) findViewById(R.id.show);
bn = (Button) findViewById(R.id.bn);
// 直接使用Activity作为事件监听器
bn.setOnClickListener(this);
}
// 实现事件处理方法
@Override
public void onClick(View v)
{
show.setText("bn按钮被单击了!");
}
}
4.匿名内部类作为事件监听器类
大部分时事件监听器只是临时使用一次,所以使用匿名内部类形式会更加合适些。
ManActivity.java
public class MainActivity extends Activity
{
EditText show;
Button bn;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
show = (EditText) findViewById(R.id.show);
bn = (Button) findViewById(R.id.bn);
// 使用匿名内部类的实例作为事件监听器
bn.setOnClickListener(new OnClickListener()
{
// 实现事件处理方法
@Override
public void onClick(View v)
{
show.setText("bn按钮被单击了!");
}
});
}
}
5.直接绑定到标签上
Android还有一种更为简单的绑定事件监听器的方式,那就是直接在界面布局文件中为指定标签绑定事件处理方法。
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal">
<EditText
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:editable="false"
android:cursorVisible="false"/>
<!-- 在标签中为按钮绑定事件处理方法 -->
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="单击我"
android:onClick="clickHandler"/>
</LinearLayout>
MainActivity.java
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
// 定义一个事件处理方法
// 其中source参数代表事件源
public void clickHandler(View source)
{
EditText show = (EditText) findViewById(R.id.show);
show.setText("bn按钮被单击了");
}
}
三.基于回调的事件处理
Android为所有GUI组件都提供了一些事件处理的回调方法。如View为例,该类包含如下方法:
下面程序示范了基于回调的事件处理机制,正如前面所提到的,基于回调的事件处理机制可通过自定义View来实现,自定义View时重写该View的事件处理方法即可。
下面是一个自定义按钮的实现类。
MyButton.java
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("the onKeyDown in MyButton");
// 返回true,表明该事件不会向外扩散
return true;
}
}
接下来在界面布局文件中使用这个自定义View,界面布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 使用自定义View时应使用全限定类名 -->
<org.crazyit.event.MyButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="单击我"/>
</LinearLayout>
通过上述分析,对于基于监听的事件处理模型来说,事件源和事件监听器是分离的,当事件源上发生特定事件时,该事件交给事件监听器负责处理;对于基于回调的事件处理模型来说,事件源和事件监听器是统一的,当事件源发生特定事件时,该事件还是由事件源本身负责处理。
1.基于回调的事件传播
1.1 几乎所有的基于回调的事件处理都有一个boolean的返回值,如果返回true,表明该处理方法已完全处理该事件,该事件不会传播出去。
1.2 如果处理事件的回调方法返回false,表明该处理方法并未完全处理该事件,该事件会传播出去的。
下面的程序示范了Android系统中的事件传播,该程序重写了Button类的onKeyDown(int keyCode, KeyEvent event)方法,而且重写了该Button所在Activity的onKeyDown(int keyCode, KeyEvent event)方法—程序没有阻止事件传播,因此程序可以看到事件从Button传播到Activity的情形中。
MyButton.java
package org.crazyit.event;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Button;
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("-MyButton-", "the onKeyDown in MyButton");
// 返回false,表明并未完全处理该事件,该事件依然向外扩散
return false;
}
}
MainActivity.java
package org.crazyit.event;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.Button;
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button bn = (Button) findViewById(R.id.bn);
// 为bn绑定事件监听器
bn.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View source
, int keyCode, KeyEvent event) {
// 只处理按下键的事件
if (event.getAction() == KeyEvent.ACTION_DOWN) {
Log.v("-Listener-", "the onKeyDown in Listener");
}
// 返回false,表明该事件会向外传播
return true; // ①
}
});
}
// 重写onKeyDown方法,该方法可监听它所包含的所有组件的按键被按下事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
super.onKeyDown(keyCode , event);
Log.v("-Activity-" , "the onKeyDown in Activity");
//返回false,表明并未完全处理该事件,该事件依然向外扩散
return false;
}
}
当该组件上发生某个按键被按下的事件时,Android系统最小触发的应该是该按键上绑定的事件监听器,然后才触发该组件提供的事件回调方法,最后还会传播到该组件所在的Activity,如果我们让任何一个事件处理方法返回了true,那么该事件将不会继续向外传播。
2.重写onTouchEvent方法响应触摸屏事件
对比Android提供的两种事件处理模型,不难发现基于监听的事件处理模型具有更大的优势。
a.基于监听的事件处理模型分工更明确,事件源,事件监听器由两个类分开实现,因此具有更好的可维护性;
b.Android的事件处理机制保证基于监听的事件监听器会被优先触发。
四.响应系统设置的事件
在开发Android应用时,有时候可能需要让应用程序随系统设置而进行调整,如判断系统的屏幕方向,有时需要让应用程序监听系统设置的更改,对系统设置的更改做出响应。
1.Configuration类简介
Configuration cfg = getResources().getConfiguration();
一旦获取了系统的Configuration对象,就可以使用该对象提供的如下常用属性来获取系统的配置信息。
实例:获取系统设备状态
public class MainActivity extends Activity
{
EditText ori;
EditText navigation;
EditText touch;
EditText mnc;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 获取应用界面中的界面组件
ori = (EditText)findViewById(R.id.ori);
navigation = (EditText)findViewById(R.id.navigation);
touch = (EditText)findViewById(R.id.touch);
mnc = (EditText)findViewById(R.id.mnc);
Button bn = (Button)findViewById(R.id.bn);
bn.setOnClickListener(new OnClickListener()
{
// 为按钮绑定事件监听器
@Override
public void onClick(View source)
{
// 获取系统的Configuration对象
Configuration cfg = getResources().getConfiguration();
String screen = cfg.orientation ==
Configuration.ORIENTATION_LANDSCAPE
? "横向屏幕": "竖向屏幕";
String mncCode = cfg.mnc + "";
String naviName = cfg.orientation ==
Configuration.NAVIGATION_NONAV
? "没有方向控制" :
cfg.orientation == Configuration.NAVIGATION_WHEEL
? "滚轮控制方向" :
cfg.orientation == Configuration.NAVIGATION_DPAD
? "方向键控制方向" : "轨迹球控制方向";
navigation.setText(naviName);
String touchName = cfg.touchscreen ==
Configuration.TOUCHSCREEN_NOTOUCH
? "无触摸屏" : "支持触摸屏";
ori.setText(screen);
mnc.setText(mncCode);
touch.setText(touchName);
}
});
}
}
2.重写onConfigurationChanged方法响应系统设置更改
如果程序需要监听系统设置的更改,则可以考虑重写Activity的onConfigurationChanged方法,该方法是一个基于回调的事件处理方法:当系统设置发生更改时,该方法会被自动触发。
实例:监听屏幕方向的改变
该程序的java代码主要会调用Activity的setRequestedOrientation的方法来动态更改屏幕方向。除此之外,我们还重写了Activity的onConfigurationChanged(Configuration newConfig)方法,该方法可用于监听系统设置的更改。
MainActivity.java
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button bn = (Button) findViewById(R.id.bn);
// 为按钮绑定事件监听器
bn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View source) {
Configuration config = getResources().getConfiguration();
// 如果当前是横屏
if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// 设为竖屏
MainActivity.this.setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
// 如果当前是竖屏
if (config.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, "系统的屏幕方向发生改变" + "\n修改后的屏幕方向为:"
+ screen, Toast.LENGTH_LONG).show();
}
}
AndroidManifest.xml
为了让该Activity能监听屏幕方向更改的事件,需要在配置该Activity时指定android:configChanges属性。该属性可以支持mcc,mnc, locale, touchscreen, keyboard, keyboardHidden, navigatin, orientation, screenLayout, uiMode,screenSize,smallestScreenSize,fontScale属性值,其中orientation|screenSize属性值指定该Activity可以监听屏幕方向改变的事件。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.example.event" >
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<!-- 设置Activity可以监听屏幕方向改变的事件 -->
<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>
</application>
</manifest>