基于回调机制的事件处理
第7章 Android事件处理模型
本章将对Android平台用户界面的各种事件响应进行详细介绍,以加深读者对Android平台的事件处理模型的理解,熟练掌握控件的各种事件处理方法。
Android平台的事件处理机制有两种,一种是基于回调机制的,一种是基于监听接口的,接下来会分别对其进行介绍。
7.1 基于回调机制的事件处理
本节将对基于回调机制的事件处理方式进行介绍。Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通过重写View中的这些回调方法来实现需要的响应事件。当某个事件没有被任何一个View处理时,便会调用Activity中相应的回调方法。接下来将对各种回调方法进行介绍。
7.1.1 onKeyDown方法简介
首先介绍的是onKeyDown方法,该方法是接口KeyEvent.Callback中的抽象方法,所有的View全部实现了该接口并重写了该方法,该方法用来捕捉手机键盘被按下的事件。方法的签名如下所示。
- 1 public boolean onKeyDown (int keyCode, KeyEvent event)
参数keyCode,该参数为被按下的键值即键盘码,手机键盘中每个按钮都会有其单独的键盘码,在应用程序都是通过键盘码才知道用户按下的是哪个键。
参数event,该参数为按键事件的对象,其中包含了触发事件的详细信息,例如事件的状态、事件的类型、事件发生的时间等。当用户按下按键时,系统会自动将事件封装成KeyEvent对象供应用程序使用。
返回值,该方法的返回值为一个boolean类型的变量,当返回true时,表示已经完整地处理了这个事件,并不希望其他的回调方法再次进行处理,而当返回false时,表示并没有完全处理完该事件,更希望其他回调方法继续对其进行处理,例如Activity中的回调方法。
接下来通过一个简单的例子来介绍该方法的使用方法及原理。该例子中自定义一个Button并显示到窗口中,然后对键盘进行监听,根据不同情况打印相关信息。
该案例的开发步骤如下。
创建一个新的Android项目,名为Sample_7_1。
然后开发Sample_7_1.java文件,用下列代码替换原有代码。
- 1 package wyf.ytl; //声明所在包
- 2 import android.app.Activity; //引入相关类
- 3 import android.content.Context; //引入相关类
- 4 import android.os.Bundle; //引入相关类
- 5 import android.util.Log; //引入相关类
- 6 import android.view.KeyEvent; //引入相关类
- 7 import android.widget.Button; //引入相关类
- 8 public class Sample_7_1 extends Activity {
- 9 public final String TAG = "Sample_7_1"; //字符常量
- 10 MyButton myButton; //自定义的Button
- 11 public void onCreate(Bundle savedInstanceState) { //重写的onCreate方法
- 12 super.onCreate(savedInstanceState);
- 13 myButton = new MyButton(this); //创建一个自定义的Button
- 14 myButton.setText("全屏按钮"); //设置按钮上的文字
- 15 myButton.setTextSize(30); //设置文字的大小
- 16 setContentView(myButton); //将按钮显示出来
- 17 }
- 18 public boolean onKeyDown(int keyCode, KeyEvent event) { //重写的键盘按下监听
- 19 Log.d(TAG, "activity onKeyDown"); //打印日志
- 20 return super.onKeyDown(keyCode, event);
- 21 }
- 22 class MyButton extends Button{ //自己定义的Button
- 23 public MyButton(Context context) { //构造器
- 24 super(context);
- 25 }
- 26 public boolean onKeyDown(int keyCode, KeyEvent event){//重写的键盘按下监听
- 27 Log.d(TAG, "MyView onKeyDown"); //打印日志
- 28 return false;
- 29 }
- 20 }
- 31 }
代码位置:见随书光盘中源代码/第7章/ Sample_7_1/src/wyf/ytl目录下的Sample_7_1.java。
第9行定义一个字符串常量,方便之后的打印日志操作,第10行声明了自定义按钮的引用。
第11~17行为重写Activity类中的onCreate回调方法,该方法会在Activity创建时被调用,一般在该方法中做一些界面初始化操作。
第14~15行创建一个自定义的按钮对象,并设置按钮上的文字为"全屏按钮",然后在第16行将该按钮控件显示到主窗口中。
第18~21行为重写的Activity中的onKeyDown方法,该方法是键盘按下事件的处理方法,在方法中先打印日志信息(第19行),然后调用父类的同名方法并将其返回值返回。
第22~30行声明一个自定义的Button。首先需要继承Button,然后实现其构造方法。然后在第26~28行又实现了Button类中的onKeyDown回调方法,该方法会在Button获得焦点并且用户按钮手机键盘按键时被调用。在方法中同样是打印日志信息(第27行)。
运行该程序,将看到如图7-1所示的效果。
打开Eclipse的DDMS中的LogCat窗口,单击右上侧的绿色加号创建一个Filter,在Log Tag文本框中输入"Sample_7_1",单击"OK"按钮过滤信息。
当按钮控件获得焦点时单击手机键盘上的任意键,通过LogCat中看到的日志内容可知,此时先调用自定义的Button中的onKeyDown方法,再调用Activity中的onKeyDown方法,而当按钮控件没有获得焦点时,将只调用Activity中的onKeyDown方法,如图7-2所示。
如果将代码的第28行的false改成true再次运行,当按钮获得焦点时点击按键,只会调用自定义的Button中的onKeyDown方法,而不会再调用Activity中的该方法。
图7-1 全屏按钮事件响应 |
7.1.2 onKeyUp方法简介
本节介绍onKeyUp方法的原理及使用方法,该方法同样是接口KeyEvent.Callback中的一个抽象方法,并且所有的View同样全部实现了该接口并重写了该方法,onKeyUp方法用来捕捉手机键盘按键抬起的事件,方法的签名如下所示。
- 1 public boolean onKeyUp (int keyCode, KeyEvent event)
参数keyCode:参数keyCode同样为触发事件的按键码,需要注意的是,同一个按键在不同型号的手机中的按键码可能不同。
参数event:参数event同样为事件封装类的对象,其含义与onKeyDown方法中的完全相同,在此不再赘述。
返回值:该方法返回值表示的含义与onKeyDown方法相同,同样通知系统是否希望其他回调方法再次对该事件进行处理。
该方法的使用方法与onKeyDown基本相同,只是该方法会在按键抬起时被调用。如果用户需要对按键抬起事件进行处理,通过重写该方法可以实现。
7.1.3 onTouchEvent方法简介
前面已经介绍了手机键盘事件的处理方法,接下来将介绍手机屏幕事件的处理方法onTouchEvent。该方法在View类中的定义,并且所有的View子类全部重写了该方法,应用程序可以通过该方法处理手机屏幕的触摸事件。该方法的签名如下所示。
- public boolean onTouchEvent (MotionEvent event)
参数event:参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。
返回值:该方法的返回值机理与键盘响应事件的相同,同样是当已经完整地处理了该事件且不希望其他回调方法再次处理时返回true,否则返回false。
该方法并不像之前介绍过的方法只处理一种事件,一般情况下以下三种情况的事件全部由onTouchEvent方法处理,只是三种情况中的动作值不同。
屏幕被按下:当屏幕被按下时,会自动调用该方法来处理事件,此时MotionEvent.getAction()的值为MotionEvent.ACTION_DOWN,如果在应用程序中需要处理屏幕被按下的事件,只需重新该回调方法,然后在方法中进行动作的判断即可。
屏幕被抬起:当触控笔离开屏幕时触发的事件,该事件同样需要onTouchEvent方法来捕捉,然后在方法中进行动作判断。当MotionEvent.getAction()的值为MotionEvent.ACTION_UP时,表示是屏幕被抬起的事件。
在屏幕中拖动:该方法还负责处理触控笔在屏幕上滑动的事件,同样是调用MotionEvent.getAction()方法来判断动作值是否为MotionEvent.ACTION_MOVE再进行处理。
接下来通过一个简单的案例介绍该方法的使用方法。在该案例中,会在用户点击的位置绘制一个矩形,然后监测用户触控笔的状态,当用户在屏幕上移动触控笔时,使矩形随之移动,而当用户触控笔离开手机屏幕时,停止绘制矩形。该案例的开发步骤如下。
创建一个名为Sample_7_2的Android项目。
打开Sample_7_2.java文件,输入如下所示的代码。
- 1 package wyf.ytl; //声明所在包
- 2 import android.app.Activity; //引入Activity类
- 3 import android.content.Context; //引入Context类
- 4 import android.graphics.Canvas; //引入Canvas类
- 5 import android.graphics.Color; //引入Color类
- 6 import android.graphics.Paint; //引入Paint类
- 7 import android.os.Bundle; //引入Bundle类
- 8 import android.view.MotionEvent; //引入MotionEvent类
- 9 import android.view.View; //引入View类
- 10 public class Sample_7_2 extends Activity {
- 11 MyView myView; //自定义View的引用
- 12 public void onCreate(Bundle savedInstanceState) { //重写的onCreate方法
- 13 super.onCreate(savedInstanceState);
- 14 myView = new MyView(this); //初始化自定义的View
- 15 setContentView(myView); //设置当前显示的用户界面
- 16 }
- 17 @Override
- 18 public boolean onTouchEvent(MotionEvent event) { //重写的onTouchEvent回调方法
- 19 switch(event.getAction()){
- 20 case MotionEvent.ACTION_DOWN: //按下
- 21 myView.x = (int) event.getX(); //改变x坐标
- 22 myView.y = (int) event.getY()-52; //改变y坐标
- 23 myView.postInvalidate(); //重绘
- 24 break;
- 25 case MotionEvent.ACTION_MOVE: //移动
- 26 myView.x = (int) event.getX(); //改变x坐标
- 27 myView.y = (int) event.getY()-52; //改变y坐标
- 28 myView.postInvalidate(); //重绘
- 29 break;
- 30 case MotionEvent.ACTION_UP: //抬起
- 31 myView.x = -100; //改变x坐标
- 32 myView.y = -100; //改变y坐标
- 33 myView.postInvalidate(); //重绘
- 34 break;
- 35 }
- 36 return super.onTouchEvent(event);
- 37 }
- 38 class MyView extends View{ //自定义的View
- 39 Paint paint; //画笔
- 40 int x = 50; //x坐标
- 41 int y = 50; //y坐标
- 42 int w = 50; //矩形的宽度
- 43 public MyView(Context context) { //构造器
- 44 super(context);
- 45 paint = new Paint(); //初始化画笔
- 46 }
- 47 @Override
- 48 protected void onDraw(Canvas canvas) { //绘制方法
- 49 canvas.drawColor(Color.GRAY); //绘制背景色
- 50 canvas.drawRect(x, y, x+w, y+w, paint); //绘制矩形
- 51 super.onDraw(canvas);
- 52 }
- 53 }
- 54 }
代码位置:见随书光盘中源代码/第7章/ Sample_7_2/src/wyf/ytl目录下的Sample_7_2.java。
第11行声明自定义View的引用,第12~16行重写Activity的onCreate方法,该方法会在此Activity创建时被系统调用,在方法中先初始化自定义的View,然后将当前的用户界面设置成该View。
第18~37行为重写的屏幕监听方法,在该方法中,根据事件动作的不同执行不同的操作。
第20~24行表示当前事件为屏幕被按下的事件,通过调用MotionEvent的getX()和getY()方法得到事件发生的坐标,然后设置给自定义View的x与y成员变量。
第25~29行表示在屏幕上滑动时的事件,同样是得到事件发生的位置并设置给View的x、y。需要注意的是,因为此时手机屏幕并不是全屏模式,所以需要对坐标进行调整。
第30~34行处理的是屏幕被抬起的事件,此时将View的x、y成员变量设成-100。表示并不需要在屏幕中绘制矩形。
第38~53行为自定义的View类,并重写了绘制方法onDraw。在第43~45行的构造器中初始化绘制时需要的画笔,然后在第48~52行的方法中根据成员变量x、y的值绘制绘制矩形。
提示:自定义的View并不会自动刷新,所以每次改变数据模型时都需要调用postInvalidate方法进行屏幕的刷新操作。关于自定义View的使用方法,将会在后面的章节中进行详细介绍,此处只是简单地使用。
运行该案例,将看到如图7-3所示的效果。
点击屏幕时,会在点击的位置绘制一个矩形,当触控笔在屏幕中滑动时,该矩形会随之移动,而当触控笔离开屏幕时,便会取消绘制矩形。
7.1.4 onTrackBallEvent方法简介
接下来将介绍的是手机中轨迹球的处理方法onTrackBallEvent。所有的View同样全部实现了该方法。该方法的签名如下。
- 1 public boolean onTrackballEvent (MotionEvent event)
参数event:参数event为手机轨迹球事件封装类的对象,其中封装了触发事件的详细信息,同样包括事件的类型、触发时间等,一般情况下,该对象会在用户操控轨迹球时被创建。
返回值:该方法的返回值与前面介绍的各个回调方法的返回值机制完全相同,因本书篇幅有限,不再赘述。
轨迹球与手机键盘的区别如下所示。
某些型号的手机设计出的轨迹球会比只有手机键盘时更美观,可增添用户对手机的整体印象。
轨迹球使用更为简单,例如在某些游戏中使用轨迹球控制会更为合理。
使用轨迹球会比键盘更为细化,即滚动轨迹球时,后台的表示状态的数值会变化得更细微、更精准。
该方法的使用方法与前面介绍过的各个回调方法基本相同,可以在Activity中重写该方法,也可以在各个View的实现类中重写。
提示:在模拟器运行状态下,可以通过F6键打开模拟器的轨迹球,然后便可以通过鼠标的移动来模拟轨迹球事件。
7.1.5 onFocusChanged方法简介
前面介绍的各个方法都可以在View及Activity中重写,接下来介绍的onFocusChanged却只能在View中重写。该方法是焦点改变的回调方法,当某个控件重写了该方法后,当焦点发生变化时,会自动调用该方法来处理焦点改变的事件。该方法的签名如下。
- 1 protected void onFocusChanged (boolean gainFocus, int direction, Rect previously FocusedRect)
参数gainFocus:参数gainFocus表示触发该事件的View是否获得了焦点,当该控件获得焦点时,gainFocus等于true,否则等于false。
参数direction:参数direction表示焦点移动的方向,用数值表示,有兴趣的读者可以重写View中的该方法打印该参数进行观察。
参数previouslyFocusedRect:表示在触发事件的View的坐标系中,前一个获得焦点的矩形区域,即表示焦点是从哪里来的。如果不可用则为null。
接下来同样通过一个简单的案例来介绍该方法的使用方法,该案例是向窗口中依次添加四个按钮,然后观察各个按钮获得焦点或失去焦点时DDMS中打印的日志信息。该案例的开发步骤如下。
创建一个名为Sample_7_3的Android项目。
直接编写Sample_7_3类的代码,如下所示。
- 1 package wyf.ytl; //声明所在包
- 2 import android.app.Activity; //引入相关类
- 3 import android.content.Context; //引入相关类
- 4 import android.graphics.Rect; //引入相关类
- 5 import android.os.Bundle; //引入相关类
- 6 import android.util.Log; //引入相关类
- 7 import android.widget.Button; //引入相关类
- 8 import android.widget.LinearLayout; //引入相关类
- 9 public class Sample_7_3 extends Activity {
- 10 MyButton myButton01; //声明myButton01的引用
- 11 MyButton myButton02; //声明myButton02的引用
- 12 MyButton myButton03; //声明myButton03的引用
- 13 MyButton myButton04; //声明myButton04的引用
- 14 public void onCreate(Bundle savedInstanceState) {
- 15 super.onCreate(savedInstanceState);
- 16 myButton01 = new MyButton(this); //初始化myButton01
- 17 myButton02 = new MyButton(this); //初始化myButton02
- 18 myButton03 = new MyButton(this); //初始化myButton03
- 19 myButton04 = new MyButton(this); //初始化myButton04
- 20 myButton01.setText("myButton01"); //设置myButton01上的文字
- 21 myButton02.setText("myButton02"); //设置myButton02上的文字
- 22 myButton03.setText("myButton03"); //设置myButton03上的文字
- 23 myButton04.setText("myButton04"); //设置myButton04上的文字
- 24 LinearLayout LinearLayout1 = new LinearLayout(this); //创建一个线性布局
- 25 LinearLayout1.setOrientation (LinearLayout.VERTICAL); //设置其布局方式
- 26 LinearLayout1.addView(myButton01); //将myButton01添加到布局中
- 27 LinearLayout1.addView(myButton02); //将myButton02添加到布局中
- 28 LinearLayout1.addView(myButton03); //将myButton03添加到布局中
- 29 LinearLayout1.addView(myButton04); //将myButton04添加到布局中
- 30 setContentView(LinearLayout1); //设置当前的用户界面
- 31 }
- 32 class MyButton extends Button{ //自定义Button
- 33 public MyButton(Context context) { //构造器
- 34 super(context);
- 35 }
- 36 @Override
- 37 protected void onFocusChanged(boolean focused, int direction,
- 38 Rect previouslyFocusedRect) { //重写的焦点变化方法
- 39 Log.d("Button", this.getText() + ", focused = " + focused + ", direction = " + direction
- 40 + ", previouslyFocusedRect = " + previouslyFocusedRect);
- 41 super.onFocusChanged(focused, direction, previouslyFocusedRect);
- 42 }
- 43 }
- 44 }
代码位置:见随书光盘中源代码/第7章/ Sample_7_3/src/wyf/ytl目录下的Sample_7_3.java。
第10~14行声明了四个按钮的引用。
第16~19行初始化四个自定义的按钮控件,然后在第20~23行分别设置了各个按钮上的文字。
第24~25行创建一个线性布局,并设置其布局方式为垂直。
第26~29行将四个按钮控件依次添加到线性布局中,然后在第30行将该线性布局设置成当前显示的用户界面。
第32~43行为自定义的Button类,在该类中,重写了onFocusChanged方法(第37~42行),在方法中将相关信息打印到日志中以便于观察与调试。
运行该案例,在模拟器中可观察到如图7-4所示的效果。
此时通过上下键控制各个按钮的焦点获取,在DDMS中可观察到打印的日志,如图7-5所示。
在图7-5中我们可以发现,每次按下一次按键,会调用两次onFocusChanged方法,一次是某个按钮失去焦点时调用,另一次是另一个按钮获得焦点时调用。同时方向direction的值会根据情况的不同而有所不同。
在上面介绍onFocusChanged方法时,提到了焦点的概念。焦点描述了按键事件(或者是屏幕事件等)的承受者,每次按键事件都发生在拥有焦点的View上。在应用程序中,我们可以对焦点进行控制,例如从一个View移动另一个View。下面列出一些与焦点有关的常用方法,读者可以进一步进行学习。
图7-4 焦点事件的演示案例 |
setFocusable方法:设置View是否可以拥有焦点。
isFocusable方法:监测此View是否可以拥有焦点。
setNextFocusDownId方法:设置View的焦点向下移动后获得焦点View的ID。
hasFocus方法:返回了View的父控件是否获得了焦点。
requestFocus方法:尝试让此View获得焦点。
isFocusableTouchMode方法:设置View是否可以在触摸模式下获得焦点,在默认情况下是不可用获得的。
以上是书《Android核心技术与实例详解》的第7章基于回调机制事件的全部内容,与之前的文章《Android事件监听器(Event Listener)》一并组成了android事件处理的两种方式,故总结下来。