第3章 Android事件处理 QQ号:1281147324 QQ群:489059718(Android编程-清华版) 287966120(公共版) 网络资源:http://www.xs360.cn/book
主要内容 1、基于监听的事件处理 2、基于回调的事件处理 5、异步任务处理 事件处理 3、直接绑定到标签 4、Handler消息处理机制
结构导图
本章示例 案例1:简易文本编辑器 案例2:模拟下载过程
3.1 Android的事件处理机制 Android事件处理机制,主要包含以下三种: 1、基于监听的事件处理 2、基于回调的事件处理 3、直接绑定到标签 主要做法是在界面布局文件中为指定标签设置事件属性,属性值是一个方法的方法名,然后再在Activity中定义该方法,编写具体的事件处理代码。
3.1.1 基于监听的事件处理 基于监听的事件处理模型中,主要涉及到三类对象: EventSource(事件源):产生事件的组件即事件发生的场所,如按钮、菜单等; Event(事件):具体某一操作的详细描述,事件封装了操作的相关信息,如果想获得事件源上所发生事件的相关信息,可通过Event对象来取得,例如按键事件按下的是哪个键、触摸事件发生的位置等; EventListener(事件监听器):负责监听用户在事件源上的操作,并对用户的各种操作做出相应的响应,事件监听器中可包含多个事件处理器,一个事件处理器实际上就是一个事件处理方法。
3.1.1 基于监听的事件处理 这三类对象如何协同工作呢? 基于监听的事件处理是一种委托式事件处理; 普通组件(事件源)将整个事件处理委托给特定的对象(事件监听器); 当该事件源发生指定的事情时,系统自动生成事件对象,并通知所委托的事件监听器,由事件监听器相应的事件处理器来处理这个事件。
3.1.1 基于监听的事件处理 基于监听的事件处理模型 按钮被单击 单击 按钮 click() onClickLinstener( ) setOnClickLinstener( ) 按钮 onClick() onClick()
3.1.1 基于监听的事件处理 对委托式事件处理的理解: 委托式事件处理就如同生活中我们每个人的能力都有 限,当碰到一些自己处理不了的事情时,就委托给某个机 构或公司来处理。 首先,你需要把你所遇到的事情和要求向对方描述清 楚,这样,他人才能更好地解决问题; 其次,该机构不止处理你一个人的事,会选派具体的 员工来处理这件事。 其中,我们自己就是事件源,你遇到的事情就是事件, 该机构就是事件监听器,具体解决事情的员工就是事件处 理器。
3.1.1 基于监听的事件处理 基于监听的事件处理模型的编程步骤: Step1:获取普通界面控件(事件源),即被监听的对象; Step2:实现事件监听器类,该监听器类是一个特殊的 Java类,必须实现一个XxxListerner接口; Step3:调用事件源的setXxxListener方法将事件监听器对 象注册给普通组件(事件源)。
3.1.1 基于监听的事件处理 ※实现事件监听器的四种形式: 内部类形式:将事件监听器类定义为当前类的内部类; 外部类形式:将事件监听器类定义成一个外部类; 类自身作为事件监听器类:让Activity本身实现监听器接口,并实现事件处理方法; 匿名内部类形式:使用匿名内部类创建事件监听器对象。
3.1.1 基于监听的事件处理 举例说明事件监听器的四种形式:简易文本编辑器
3.1.1 基于监听的事件处理 (1)内部类的形式 public class MainActivity extends Activity implements OnClickListener { public void onCreate(Bundle savedInstanceState) { ColorListner myColorListner = new ColorListner(); red.setOnClickListener(myColorListner); green.setOnClickListener(myColorListner); blue.setOnClickListener(myColorListner); } private class ColorListner implements OnClickListener { public void onClick(View v) { switch (v.getId()) { case R.id.red: testText.setTextColor(Color.RED); break; case R.id.blue: testText.setTextColor(Color.BLUE); case R.id.green: testText.setTextColor(Color.GREEN); default: 将事件监听器类定义为当前类(MainActivity)的内部类(ColorListner);
3.1.1 基于监听的事件处理 使用内部类有两个优势: 使用内部类可以在当前类中复用该监听器类,即多个事件源可以注册同一个监听器; 使用内部类可以自由访问外部类的所有界面控件,内部类实质上是外部类的成员。 内部类形式比较适合于有多个事件源同时注册同一事件监听器的情形。
3.1.1 基于监听的事件处理 (2)外部类的形式 将事件监听器类定义成一个外部类(SizeListener.java); public class MainActivity extends Activity implements OnClickListener { public void onCreate(Bundle savedInstanceState) { SizeListener mysizeListener=new SizeListener(testText); bigger.setOnClickListener(mysizeListener); smaller.setOnClickListener(mysizeListener); } 将事件监听器类定义成一个外部类(SizeListener.java); public class SizeListener implements OnClickListener { private TextView tv; public SizeListener(TextView tv) { this.tv = tv; } public void onClick(View v) { float f=tv.getTextSize(); switch (v.getId()) { case R.id.bigger: f=f+2; break; case R.id.smaller: f=f-2; default: }(转左侧代码) if(f>=72){ f=72; } if(f<=8){ f=8; tv.setTextSize(f);
3.1.1 基于监听的事件处理 外部类形式较少见的原因: 事件监听器通常属于特定的GUI(图形用户界面),定义成外部类不利于提高程序的内聚性; 外部类形式的事件监听器不能自由访问创建GUI界面中的组件,编程不够简洁。 如果某个事件监听器确实需要被多个GUI界面所共享,而且主要是完成某种业务逻辑的实现,则可以考虑使用外部类的形式来定义事件监听器类。
3.1.1 基于监听的事件处理 (3)类自身作为事件监听器类 让Activity本身实现监听器接口,并实现事件处理方法; public class MainActivity extends Activity implements OnClickListener { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void onClick(View v) { Typeface tf=testText.getTypeface(); switch (v.getId()) { case R.id.italic: if(flag==2||flag==3){ testText.setTypeface(Typeface.MONOSPACE ,Typeface.BOLD_ITALIC); flag=3; }else{ testText.setTypeface(Typeface.MONOSPACE, Typeface.ITALIC);//斜体 flag=1; break; …… default: 让Activity本身实现监听器接口,并实现事件处理方法;
3.1.1 基于监听的事件处理 Activity类本身作为事件监听器,就如同生活中,我们自己刚好能够处理某一件事,不需要委托给他人处理,可以直接在Activity类中定义事件处理器方法,这种形式非常简洁。 不推荐使用的原因: 可能造成程序结构混乱,Activity的主要职责应该是完成界面初始化工作,但此时还需包含事件处理器方法,从而引起混乱; 如果Activity界面类需要实现监听器接口,给人感觉比较怪异。
3.1.1 基于监听的事件处理 (4)匿名内部类形式 使用匿名内部类创建事件监听器对象。 public class MainActivity extends Activity implements OnClickListener { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); content = (EditText) findViewById(R.id.content); content.setOnEditorActionListener(new OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { testText.setText(content.getText().toString()); return false; } });
3.1.1 基于监听的事件处理 匿名内部类中访问局部成员变量时,该成员变量必须是final修饰的,而对于成员变量则没有这个要求。 大部分时候,事件处理器都没有什么复用价值(可复用代码通常都被抽象成了业务逻辑方法),因此大部分事件监听器只是临时使用一次,所以使用匿名内部类形式的事件监听器更合适。实际上,这种形式也是目前使用最广泛的事件监听器形式。
3.1.1 基于监听的事件处理 常见事件监听器接口及其处理方法: 事件 接口 处理方法 描述 单击事件 View.OnClickListener abstract void onClick (View v) 单击组件时触发 View.OnLongClickListener abstract boolean onLongClick (View v) 长按组件时触发 键盘事件 View.OnKeyListener abstract boolean onKey (View v, int keyCode, KeyEvent event) 处理键盘事件 焦点事件 View.OnFocusChangeListener abstract void onFocusChange (View v, boolean hasFocus) 当焦点发生改变时触发 触摸事件 View.OnTouchListener abstract boolean onTouch (View v, MotionEvent event) 产生触摸事件
3.1.1 基于监听的事件处理 View类的常见事件监听器注册方法: 方法 描述 void setOnClickListener (View.OnClickListener l) 注册单击事件 void setOnLongClickListener (View.OnLongClickListener l) 注册长按事件 void setOnKeyListener(View.OnKeyListener l) 注册键盘事件 void setOnFocusChangeListener (View.OnFocusChangeListener l) 注册焦点改变事件 void setOnTouchListener (View.OnTouchListener l) 注册触摸事件 void setOnCreateContextMenuListener( View.OnCreateContextMenuListener l) 注册上下文菜单事件
3.1.2 基于回调的事件处理 如果说事件监听机制是一种委托式的事件处理,那么回调机制则与之相反,对于基于回调的事件处理模型来说,事件源和事件监听器是统一的,或者说事件监听器完全消失了,当用户在GUI控件上激发某个事件时,控件自己特定的方法将会负责处理该事件。 为了使用回调机制来处理GUI控件上所发生的事件,需要为该组件提供对应的事件处理方法,而Java又是一种静态语言,我们无法为每个对象动态地添加方法,因此只能通过继承GUI控件类,并重写该类的事件处理方法来实现。
3.1.2 基于回调的事件处理 View类的常见回调方法: Android平台中,每个View都有自己处理特定事件的回调方法,开发人员可以通过重写View中的这些回调方法来实现相应的事件。 View类的常见回调方法: boolean onKeyDown (int keyCode, KeyEvent event):接口KeyEvent.Callback中的抽象方法,用于捕捉手机键盘被按下的事件。keyCode为被按下的键值即键盘码,event为按键事件的对象、包含了触发事件的详细信息,如事件的状态、类型、发生的时间等。 boolean onKeyUp (int keyCode, KeyEvent event):用于捕捉手机键盘按键抬起的事件; boolean onTouchEvent (MotionEvent event):该方法在View类中定义,该方法用于处理手机屏幕的触摸事件,包括屏幕被按下、屏幕被抬起、在屏幕中拖动。
3.1.2 基于回调的事件处理 自定义控件的一般步骤 1)定义自己组件的类名,并让该类继承View类或一个现有的View的子类。 2)重写父类的一些方法,通常需要提供一个构造器,构造器是创建自定义控件的最基本方式,当Java代码创建该控件或根据XML布局文件加载并构建界面时都将调用该构造器,根据业务需要重写父类的部分方法。例如onDraw()方法,用于实现界面显示,其他方法还有onSizeChanged()、onKeyDown()、onKeyUp()等。 3)使用自定义的组件,既可以通过Java代码来创建,也可以通过XML布局文件进行创建,在XML布局文件中,该组件的标签是完整的包名+类名,而不再仅仅是原来的类名。
事件处理的传播 如果处理事件的回调方法返回true,表明该处理方法已完全处理该事件,该事件不会传播出去; 几乎所有基于回调的事件处理方法都有一个boolean类型的返回值,该返回值用于标识该处理方法是否能完全处理该事件。 如果处理事件的回调方法返回true,表明该处理方法已完全处理该事件,该事件不会传播出去; 如果处理事件的回调方法返回false,表明该处理方法并未完全处理该事件,该事件将会继续向外传播。 对于基于回调事件传播而言,某组件上所发生的事情不仅激发该组件上的回调方法,也会触发该组件所在Activity的回调方法(前提是事件能传播到Activity)。
事件处理的传播 当为同一组件,既采用监听模式,同时又采用回调模式,并且重写了该组件所在Activity对应的回调方法,而且程序没有阻止事件传播,即每个方法都返回为false。那么Android系统的调用顺序是什么呢? 最先触发的是该组件所绑定的事件监听器,接着才触发该组件提供的事件回调方法,最后才传播到该组件所在的Activity。如果我们让任何一个事件处理方法返回了true,那么该事件将不会继续向外传播。
事件处理的传播 案例:自定义一个按钮,重写其触摸的回调方法、为其注册触摸事件监听器并重写它所在Activity上的触摸回调方法,观察事件处理顺序。 如何在布局文件中配置自定义的组件? <完整的包名.类名 需要设置的相关属性/> 做一做:改变方法的返回值(将true改为false), 观察控制台输出结果。
3.1.3 直接绑定到标签 Android提供了一种直接在界面布局文件中为指定标签绑定事件处理方法的机制。对于很多Android界面控件而言,它们都支持如onClick、onLongClick等属性,这些属性的属性值是一个形如xxx(View source)方法的方法名。 例如在布局文件中为按钮添加单击事件的处理方法如下: <Button android:layout_width=”wrap_content” android:layout_heigth=”wrap_content” android:text=”单击我” android:onClick=”clickHandler”/>
3.2 Handler消息传递机制 Android平台不允许Activity新启动的线程访问该 Activity里的界面控件,这样就会导致新启动的线程无法动态 改变界面控件的属性值。 但在实际Android应用开发中,尤其是涉及动画的游戏开 发中,需要让新启动的线程周期性地改变界面控件的属性值, 那如何实现呢? 需要借助Handler的消息传递机制实现
3.2 Handler消息传递机制 Handler类的常用方法: 方法 描 述 描 述 public void handleMessage (Message msg) 通过该方法获取、处理消息 public final boolean sendEmptyMessage (int what) 发送一个只含有what标记的空消息 public final boolean sendMessage (Message msg) 发送消息到Handler,通过handleMessage()方法接收和处理 public final boolean hasMessages (int what) 监测消息队列中是否包含标记为what的消息 public final boolean post (Runnable r) 将一个线程添加到消息队列
3.2 Handler消息传递机制 Handler类主要有两个作用: 新启动的线程何时发送消息?主线程又如何去获取并处理消息呢? 在新启动的线程中发送消息; 在主线程中获取、处理消息。 新启动的线程何时发送消息?主线程又如何去获取并处理消息呢? 当需要界面发生变化的时候,在线程中发送消息。 为了让主线程能“适时”地处理新启动的线程所发送的消息,可采用回调的方式来实现——只需重写Handler类中处理消息的方法,当新启动的线程发送消息时,Handler类中处理消息的方法会被自动调用。
3.2 Handler消息传递机制 Handler消息传递程序开发步骤: 1、创建Handler类对象,并重写handleMessage()方法; 2、在新启动的线程中,调用Handler对象的发送消息方法; 3、利用Handler对象的handleMessage()方法接收消息, 然后根据消息的不同执行不同的操作。 发送和处理消息的是同一个Handler对象,自己发送,自己处理。
3.2 Handler消息传递机制 案例:实现一个动态变化的随机数效果。 1、尝试通过子线程改变主线程界面; 2、尝试直接在主线程中实现该功能; 3、使用Handler处理机制实现该功能。
3.3 异步任务处理 AsyncTask是抽象类,AsyncTask定义了三种泛型类型 Params、Progress和Result。 Android的类AsyncTask对线程间通讯进行了包装,提供了简易的编程方式来使后台线程和UI线程进行通讯:后台线程执行异步任务,并把操作结果通知UI线程。不再需要子线程和Handler就可以完成异步操作并且刷新用户界面。 AsyncTask是抽象类,AsyncTask定义了三种泛型类型 Params、Progress和Result。 Params:启动任务执行的输入参数; Progress:后台任务执行的百分比; Result:后台任务执行最终返回结果的类型,如String,Integer等。
3.3 异步任务处理 AsyncTask类中主要方法: onPreExecute():该方法将在执行实际的后台操作前被UI线程调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条,或者一些控件的实例化,这个方法可以不用实现。 doInBackground(Params...):将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些比较耗时的后台处理工作。可以调用 publishProgress方法来实时更新任务进度。该方法是抽象方法,子类必须实现。 onProgressUpdate(Progress...):在publishProgress方法被调用后,UI 线程将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。 onPostExecute(Result):在doInBackground 执行完成后,onPostExecute 方法将被UI线程调用,后台的计算结果将通过该方法传递到UI线程,并且在界面上展示给用户。 onCancelled():在用户取消线程操作的时候调用。在主线程中调用onCancelled()的时候调用。
3.3 异步任务处理 使用AsyncTask类需遵守的准则: Task的实例必须在UI线程中创建; execute(Params...)方法必须在UI线程中调用; 不要手动的调用onPreExecute(), onPostExecute(Result), doInBackground(Params...),onProgressUpdate(Progress...)这几个方法,需要在UI线程中实例化这个task来调用; 该task只能被执行一次,否则多次调用时将会出现异常。
程 序 运 行 效 果 分 析
方 法 调 用 顺 序 分 析 注意:其中只有doInBackground()方法,以及publishProgress()方法是在子线程中执行的,其他的方法都是在主线程中执行的,所以可以在这些方法中更新界面组件。