前言
你的时间有限,不要浪费于重复别人的生活。不要让别人的观点淹没了你内心的声音。
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被单击了");
}
}
}
效果
事件源:程序中的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种形式。
- 内部类形式
上述的第一个程序就是内部类形式。 - 外部类形式
- Activity本身作为事件监听器类
setOnXxxxListenner(this); - 匿名内部类形式
上述移动图片的程序就是匿名内部类形式。 - 直接绑定在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>
效果
单击模拟器上的任意键
提示
对于基于监听的事件模型来说,事件源和事件监听器是分离的,当事件源上发生特定事件时,该事件交给事件监听器负责处理;对于基于回调的时事件处理模型来说,事件源和事件监听器是统一的,当事件源发生特定事件时,该事件还是由事件源本身负责处理。
基于回调的事件传播
几乎所有基于回调的事件处理方法都有一个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的结果。
把监听器里的返回值设为true,事件将不传播。
响应系统设置的事件
在开发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);
}
}
效果
重写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>