1.1. 控件的事件处理机制
1.1.1.事件种类
在Android中事件包括两类,一类是按键事件,一类是触摸事件。这两大类事件构成了Android事件的框架。按键事件是指手机键盘被按下的事件,包括按下,释放和长按。触摸事件相对复杂一些,最基本的触摸事件有三种,包括ACTION_DOWN,ACTION_MOVE和ACTION_UP,分别对应手指按下,拖动和抬起的操作。一次简单的触摸操作包含的一次ACTION_DOWN,多次ACTION_MOVE以及一次ACTION_UP,而且顺序是固定的,一定是按照ACTION_DOWN,ACTION_MOVE,ACTION_UP的顺序发生。如果将这三种基本的触摸事件进行组合会产生很多复杂的事件,比如点击事件由一个ACTION_DOWN和一个ACTION_UP,长按事件由一个ACTION_DOWN和一段时间组成,滚动和滑动等事件由一个ACTION_DOWN和多个ACTION_MOVE组成。
1.1.2.事件传递机制
Android中事件由WindowManagerService获取,并通过共享内存和管道的方式传递给ViewRoot的。它是整个View树和WindowManager之间的事件信息的翻译者。当事件到达根结点之后,根结点再向它自己或者子结点分发事件,直到有控件处理或者丢弃。
我们知道在View树中有两类结点一类是ViewGroup,一类是View。以ACTION_DOWN事件为例,在ViewGroup中关于事件传递和处理的有三个方法,分别是:
public boolean dispatchTouchEvent(MotionEventev) 用于事件的分发
public boolean onInterceptTouchEvent(MotionEventev) 用于事件的拦截
public booleanonTouchEvent(MotionEvent ev) 处理事件
而View中只包含dispatchTouchEvent()和onTouchEvent()方法。也就是说View不会执行事件的拦截,只会处理事件。
dispatchTouchEvent负责分发事件,ACTION_DOWN事件最先到达的就是最顶层view的dispatchTouchEvent,然后它进行分发,如果返回true,则继续等待下次事件,返回false,则交给这个view的interceptTouchEvent方法来决定是否要拦截这个事件。如果interceptTouchEvent返回true,也就是拦截掉了,则交给它的onTouchEvent来处理,处理之后,如果返回false表示这个事件还没有被处理完成,还需要其他的处理,之前讲到的ListView中的两个长按的响应例子就是利用这个特性做到的,返回true表示已经处理完了。随后的ACTION_MOVE和ACTION_UP事件都会在这个View上处理,而不会再次进行选择。如果interceptTouchEvent返回false,那么就传递给子view,由子view的dispatchTouchEvent再来开始这个事件的分发,从而一层一层传递下去。
如果事件传递到某一层的子view的onTouchEvent上了,这个方法返回了false,那么这个事件会从这个view往上传递,都是onTouchEvent来接收。而如果传递到最上面的onTouchEvent也返回false的话,这个事件就会“消失”,而且接收不到后序的ACTION_MOVE和ACTION_UP事件。
1.1.3.事件处理机制
Android中的事件处理分为两种:监听和回调。两种方式在处理事件上有很大的不同,具体表现在如下几个方面:
两种方式处理的事件种类不同,具体来说,监听处理的是点击,长按,创建上下文菜单,焦点变化等事件,而回调处理的是触摸屏操作(比如ACTION_DOWN,ACTION_MOVE和ACTION_UP事件)以及按键操作。两种事件处理方式的程序结构不同,监听方式处理需要建立监听器对象,并为View设置相应的监听器对象,而回调方式处理的事件比监听方式要细致的多,当用户在GUI组件上触发某事件时,由该组件自身特定的函数负责处理该事件,通常通过重写Override组件类的事件处理函数实现事件的处理。因此,在实际的操作中,监听方式比回调方式应用的场景要多很多。
关于监听方式的例子在前面小节中已经有很多了,在这里我们讲一个回调方式处理事件的例子。回调方式需要重写控件,在此我们重写一个Button,定义如下:
TestButton.java代码清单5-33:
/**
* TestButton自定义按钮,重写了onKeyDown方法
* @author孔明
*/
publicclass TestButton extends Button {
public TestButton(Context context) {
super(context);
}
public TestButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public TestButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
// 重写了onKeyDown方法,在按键时本方法自动被回调
@Override
public boolean onKeyDown(intkeyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
// 按下按键是显示一个提示
DisplayToast("你按下中间键");
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
DisplayToast("你按下下方向键");
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
DisplayToast("你按下左方向键");
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
DisplayToast("你按下右方向键");
break;
case KeyEvent.KEYCODE_DPAD_UP:
DisplayToast("你按下上方向键");
break;
}
return super.onKeyDown(keyCode, event);
}
// 显示提示的方法
public void DisplayToast(String str) {
Toast.makeText(this.getContext(), str, Toast.LENGTH_LONG).show();
}
}
同时,我们新建一个Activity,命名为EventHandleActivity,内容如下:
publicclass EventHandleActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.event_handle);
}
}
在这个Activity中使用的布局文件为event_handle.xml,内容如下:
event_handle.xml代码清单5-34:
<?xml version=“1.0” encoding=“utf-8”?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<!-- 使用自定义的TestButton -->
<com.firstpeople.chapter0520.TestButton
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="自定义按钮"/>
</LinearLayout>
布局文件中使用一个自定义的控件时,需要指出该控件所在的具体的包路径。
运行程序,我们截图如5-50所示:
图5-50 自定义button点击事件示意图
从图上可以看到我们在程序里面添加的提示信息,需要注意的是需要将按钮置为选中状态的情况下才会有相应的提示,也就是说需要将按钮置为焦点时onKeyDown()方法才会被执行。
回调方式处理事件的方法还有其他几个:
boolean onKeyMultiple(intkeyCode,intrepeatCount,KeyEventevent)用于在多个事件连续时发生,用于按键重复,必须重写@Override实现;
boolean onKeyDown(intkeyCode,KeyEventevent)用于在按键进行按下时的操作;
boolean onKeyUp(intkeyCode,KeyEventevent)用于在按键进行释放时的操作;
boolean onKeyLongPress(intkeyCode,KeyEventevent)当你长时间按键时的操作。
孔明:一般情况下,我们使用监听方式来处理事件就足够了,但是作为编程大牛的我们来说,一定要会使用回调方式,至少要知道其中的原理哦~~ |
1.2. 玄德有话说
张飞:大哥!我头晕!我先走一步!
刘备:三弟!三弟!撑住!卧龙先生讲完了!真的讲完了!
张飞:终于讲完了吗?天呐,我以为这一回永远也不会结束了!
孔明:Android界面的内容确实很多,不过也非常重要,这是Android开发的基础!
关羽:我有一个问题哈,这么多控件、布局,我们如何记得住呢?
孔明:很简单,不用记,放轻松!最重要的是理解原理,知道哪里能查到,善于检索资料。时间长了,你不用记这些控件也能深深的扎根在你的脑子里,与你合为一体!
刘备:孔明说的太玄了吧,我也有一个问题,刚才学习布局时,那么多布局好像都能达到效果,那么我该如何选择呢?
孔明:世上有很多路都能达到终点,有的比较快,有的比较安全,最好是又快又安全。懂了吗?
张飞:请用中文,谢谢!
孔明:选择布局方式的时候要选用兼容性强的,简便的方式进行。要尽可能使多种屏幕分辨率的终端都能很好显示,这其实是一个非常困难的事情。只有多做项目,多写代码,多看多想,自然能达到写界面就跟随手涂鸦般轻松的地步
刘备:孔明,我还有一个问题特别想问你。
孔明:说。
刘备:你为什么一直在茅房里?
孔明:很简单……因为我还没上完厕所。