基于监听的事件处理——事件和事件监听器
当外部动作在Android组件上执行操作时,系统会自动生成事件对象,这个事件对象会作为参数传给事件源上注册的事件监听器。
事件监听的处理模型涉及三个成员:事件源、事件和事件监听器,其中事件源最容易创建,任意界面组件都看作为事件源;事件的产生无须程序员关心,它是由系统自动产生的;所以实现事件监听器是整个事件处理的核心。
Android对事件监听模型做了进一步进化:如果事件源触发的事件足够简单、事件里封装的事件比较有限,那就无须封装事件对象、将事件对象传入事件监听器。
但对于键盘事件、触摸屏事件等,此时程序需要获取事件发生的详细信息:例如键盘事件需要获取是哪个键触发的事件;触摸屏事件需要获取事件发生的位置等,对于这种包含更多信息的事件,Android同样会将事件信息封装成XxxEvent对象,并把该对象作为参数传入事件处理器。
实例:控制飞机移动
下面以一个简单的飞机游戏为例来介绍键盘事件的监听。游戏中的飞机会随用户单击键盘的动作而移动;单击不同的键盘,飞机向不同的方向移动。
为了实现该程序,先开发一个自定义View,该View负责绘制游戏的飞机,该View类的代码如下。
package com.example.studyevent; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.view.View; public class PlaneView extends View { public float currentX; public float currentY; Bitmap plane; public PlaneView(Context context) { super(context); // TODO Auto-generated constructor stub //定义飞机图片 plane=BitmapFactory.decodeResource(context.getResources(), R.drawable.plane); setFocusable(true); } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); //创建画笔 Paint p=new Paint(); //绘制飞机 canvas.drawBitmap(plane, currentX, currentY, p); } }
上面的PlaneView足够简单,因为这个程序只需要绘制玩家自己控制的飞机,没有增加”敌机“,所以比较简单。如果游戏中还需要增加”敌机“,那么还需要增加数据里控制敌机的坐标,并会在View上绘制敌机。
该游戏几乎不需要界面布局,该游戏直接使用PlaneView做为Activity显示的内容,并为该PlaneView增加键盘事件监听器即可。下面是该Activty代码。
package com.example.studyevent; import android.os.Bundle; import android.app.Activity; import android.util.DisplayMetrics; import android.view.Display; import android.view.KeyEvent; import android.view.Menu; import android.view.View; import android.view.View.OnKeyListener; import android.view.Window; import android.view.WindowManager; public class PlaneGameActivity 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); //创建PlaneView组件 final PlaneView planeView=new PlaneView(this); setContentView(planeView); planeView.setBackgroundResource(R.drawable.back); //获取窗口管理器 WindowManager windowManager=getWindowManager(); Display display=windowManager.getDefaultDisplay(); DisplayMetrics metrics=new DisplayMetrics(); //获取屏幕宽和高 display.getMetrics(metrics); //设置飞机的初始位置 planeView.currentX=metrics.widthPixels/2; planeView.currentY=metrics.heightPixels-40; //为draw组件键盘事件绑定监听器 planeView.setOnKeyListener(new OnKeyListener(){ @Override public boolean onKey(View v, int keyCode, KeyEvent event) { // TODO Auto-generated method stub //获取由哪个触发的事件 switch(event.getKeyCode()) { //控制飞机下降 case KeyEvent.KEYCODE_S: planeView.currentY+=speed; break; //控制飞机上移 case KeyEvent.KEYCODE_W: planeView.currentY-=speed; break; case KeyEvent.KEYCODE_A: planeView.currentX-=speed; break; case KeyEvent.KEYCODE_D: planeView.currentX+=speed; break; } //通知planeView组件重绘 planeView.invalidate(); return true; }}); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.plane_game, menu); return true; } }
上面程序中的粗体字代码就是控制飞机移动的关键代码——由于程序需要根据用户按下的键来确定飞机的移动方向,所以上面的程序先调用KeyEvent(事件对象)的getKeyCode()来获取触发事件的键,然后根据不同的键来改变游戏中飞机的坐标。
正如前面提到的:如果事件发生时有比较多的信息需要传给事件监听器,那么就需要将事件信息封装成Event对象,该Event对象将作为参数传入事件处理函数。
运行上面的程序将看到下图所示效果。
对于上图所示的”游戏“,当用户按下模拟器右边的4个方向键时,将可以看到”游戏“中的飞机可以上、下、左、右自动移动。
在基于监听的事件处理模型中,事件监听器必须实现事件监听器接口,Android为不同的界面组件提供了不同的监听器接口,这些接口通常以内部类的形式存在。以View类为例,它包含了如下几个内部接口。
- View.OnClickListener:单击事件的事件监听器必须实现的接口。
- View.OnCreateContextMenuListener:创建上下文文菜单事件的事件监听器必须实现的接口。
- View.OnFocusChangeListener:焦点改变事件的事件监听器必须实现的接口。
- View.OnKeyListener:按键事件的事件监听器必须实现的接口。
- View.OnLongClickListener:长单击事件的事件监听器必须实现的接口。
- View.OnTouchListener:触摸屏事件的事件监听器必须实现的接口。
通过上面的介绍不难看出,所谓事件监听器,其实就是实现了特定接口的Java类的实例。在程序中实现事件监听器,通常有如下几种形式。
- 内部类形式:将事件监听器类定义成当前类的内部类。
- 外部类形式:将事件监听器类定义成一个外部类。
- Actviy本身作为事件监听器类:让Activity本身实现监听器接口,并实现事件处理方法。
- 匿名内部类形式:使用匿名内部类创建事件监听器对象。