在android的进阶之路上,看《android开发艺术探索》实在看不下去了,开始怀疑自己的android基础了,于是找了两本android基础的书把android基础过一遍,还确实发现了好多知识漏洞-基于回调的事件,这个还真以前没用过,然后就想着把android事件处理机制的知识点都整理一遍,嗯,就是这样的。
基于监听的事件更接近于“面向对象”的事件处理,这种处理方法与java的AWT/Swing的处理方式相同。
基于监听的事件处理主要涉及3个对象
那么我们怎么把事件源与事件联系到一起呢?就需要为事件注册监听器了,就相当于把事件和监听器绑定到一起,当事件发生后,系统就会自动通知事件监听器来处理相应的事件.怎么注册监听器呢,很简单,就是实现事件对应的Listener接口。 1).为事件对象添加监听
2).当事件发生时,系统会将事件封装成相应类型的事件对象
3).当监听器对象接收到事件对象之后,系统调用监听器中相应的事件处理来处理事件
注意:事件源可以是任何的界面组件,不太需要开发者参与,注册监听器叶只要一行代码就实现了,因此事件编程的重点是实现事件监听器类
android设备可用物理编码按键及案件编码
程序中实现监听器有以下几种方法
MainActivity.java主要代码如下:
点击按钮后:
布局界面如下
MainActivity.java主要代码如下
其中,Claculator.java 代码如下:
看看结果
我们就在上面的基础上直接改MainActivity.java就行了
2.5Activity作为事件监听器
直接改MainActivity.java就行了
结果:
就是在界面组件中为指定的组件通过属性标签定义监听器类 刚刚那个xml文件把button那个部分改一下
<Button
android:paddingTop="20dp"
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="结果"
android:onClick="getResult"/>
然后,在MainActivity.java 里面写那个方法就行了,注意,必须是public void getResult(View view)这个格式
public void getResult(View view){
//逻辑处理代码和使用外部类相同
}
结果:
如果说事件监听机制是一种委托的事件处理,那么回调机制则与之相反,对于基于事件的处理模型来说,事件源与事件监听器是统一的,或者说是事件监听器完全消失了,当用户在UI组件上触发某个事件时,组建自己特定的方法将会负责处理事件 为了使回调方法机制类处理UI组件上发生的事件,开发者需要为该组件提供对应的事件处理方法,而java是一种静态语言,无法为某个对象动态的添加方法,因此只能继续是用UI组件类,并通过重写该类的事件处理的方法来实现 为了处理回调机制的事件处理,android为所有UI组件提供了一些事件处理的回调方法。
下面以一个小例子来说明一下,新建一个工程,布局文件很简单,就一个textview,MainActivity.java中重写了onKeyDown和onKeyUp方法 代码如下
运行截图有四张,按下数字0和松开数字0,按下返回键和松开返回键:
几乎所有基于回调的事件都有一个boolean类型的返回值,发方法用于标识该处理方法是否能够完全处理该事件 (1),如果处理事件的回调方法返回的值为true,则表明该处理方法已完全处理该事件,且事件不会被传播出去 (2),如果处理事件的回调方法返回的值为false,则表明该处理方法并未完全处理该事件,且事件会被传播出去 对于基于回调的事件传播而言,某组件上所发生的事件不仅能触发该组件上的回调方法,也会触发该组件所在的activity类的回调方法-只要事件传播到该activity类
下面以一个小例子来说明android系统中的事件传播流程,该程序重写了EditText类的onKeyDown()方法,而且重写了该EditText所在的Activity类的onKeyDown()方法,由于程序中没有阻止事件的传播,所以程序中可以看到事件从RditText传播到Activity的全过程
自定义的组件类MyTestBox.java
public class MyTestBox extends EditText{
public MyTestBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
super.onKeyDown(keyCode, event);
Log.i("MyTestBox", "这里是MyTestBox的onKeyDown");
return false;
}
}
上面的MyTextBox类重写了EditText类的onKeyDwon()方法,因此,当用户在此组件上按下任意键时都会触发OnKeyDown()方法,在该方法中返回false,即按键事件会继续向外传递
布局文件挺简单的,就是把上面那个自定义的组件包含进来就ok,不过此处包含进来的时候必须要完整包
<cn.aiyuan1996.huidiaoprocess.MyTestBox
android:id="@+id/myTextBox"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
</cn.aiyuan1996.huidiaoprocess.MyTestBox>
然后就是MainActivity了:
然后运行程序,发现程序崩溃了,很好,对于这个问题我花了四个小时没解决,后来我同学也花了半小时没解决,后来他回宿舍看了一个他以前写的,才发现问题,下面我们先来看看报错信息
下面是主要报错信息
10-25 16:50:33.398: E/AndroidRuntime(1463): java.lang.RuntimeException: Unable to start activity ComponentInfo{cn.aiyuan1996.huidiaoprocess/cn.aiyuan1996.huidiaoprocess.MainActivity}: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
10-25 16:50:33.398: E/AndroidRuntime(1463): Caused by: android.view.InflateException: Binary XML file line #8: Error inflating class cn.aiyuan1996.huidiaoprocess.MyTestBox
但是看到这个信息,你大概知道是你的自定义view出问题了,但是你可能还是不知道怎么解决,正如笔者,后来研究了好长时间,也查看了相关blog,但是还是没有解决,还好有同学的帮忙。
其实就是构造函数那块出了问题,构造函数要用有两个参数的那个,把上面那个构造函数改成这个就行了
public MyTestBox(Context context, AttributeSet attrs) {
super(context, attrs);
}
为什么必须要是这个构造函数呢,看看这三个构造函数先
很明显,两个参数的那个构造函数是负责自定义组件的构造的 bug改好后,我们再运行一遍
随便输入一个东西,我们看看打印了什么内容
主要是看这个顺序,首先是触发的是该组件的绑定的事件监听器,然后是该组件所在的类提供的事件回调方法,最后才是传播给组件所在Activity类,如果在任何一个事件处理方法返回了true,那么该事件将不会被继续向外传播
屏幕事件的处理方法onTouchEvent(),该方法的返回值与键盘响应事件相同,都是当程序完整的处理的该事件,且不希望其他回调方法再次处理该事件时返回true,否则返回false 1)屏幕被按下MotionEvent.getAction()==MotionEvent.ACTION_DOWN 2)离开屏幕MotionEvent.getAction()==MotionEvent.ACTION_UP 3)在屏幕中拖动MotionEvent.getAction()==MotionEvent.ACTION_MOVE
下面以一个小例子来说明没有布局文件,直接上MainActivity.java
看看运行截图:
出于性能优化考虑,android的ui线程操作是不安全的,这意味者如果多个线程并发操作UI组件,可能导致线程安全问题,为了解决这个问题,android制定了一条简单的规则,只允许UI线程修改android里的UI组件 当一个程序第一次启动时,android会同时启动一条主线程,这线程主要负责与UI相关度事件,例如用户的按键事件,用户的触摸事件,以及屏幕绘图事件,并非相关的时间分发到组件进行处理,所以主线程又叫UI线程,故android平台只允许Ui线程修改activity的ui组件,新的进程需要动态改变界面组件的属性值时,就需要用到Handler了
Handler类主要有两个作用:在新启动的线程中发送消息,在主线程中获取和处理消息 只能通过回调的方法来实现-开发者只需要重写Handler类中处理的消息的方法即可,当新启动的线程发送消息时,消息会发送到与之关联的MessageQueue,而Handler会不断的从MessageQueue中获取并处理消息-这将导致Handler中的处理消息的方法被回调
下面一个实例演示如何在界面中修改界面的组件,循环播放相册中的照片 布局文件很简单,就一个imageview 然后是MainActivity.java的主要代码
上面代码中的Timer类会启动一个新线程,由于不允许在线程中修改UI界面,所以该线程每隔1200毫秒会发送一个消息,该消息会传递到Activity中,再由Handler类进行处理,从而实现了动态切换的效果。
其实Handler还有蛮多要写的,这篇blog篇幅已经不少了,今天先把Handler写道这里,改天有时间后好好研究研究Handler和Looper后,整理一些资料,再写。
作为一个开发者,我还是最喜欢是用匿名内部类的形式,代码简洁,一目了然。