第三章 Android界面事件处理(一)—— 杰瑞教育原创教材试读
前 言
JRedu
Android应用开发中,除了界面编程外,另一个重要的内容就是组件的事件处理。在Android系统中,存在多种界面事件,比如触摸事件、按键事件、点击事件等。在用户交互过程中,App必须为用户提供响应逻辑,这种响应就是通过事件处理完成的。
本章内容将详细介绍Android事件的具体处理及常见事件。
| 3.1 Android 事件基础知识 |
Android中提供了两种处理事件的方式:
基于监听器的事件处理
基于回调的事件处理
在事件处理过程中涉及到三个重要概念,具体如下:
事件:事件封装了界面组件上发生的事件的具体信息。通过Event对象,可以获取界面组件事件的相关信息。
事件源:事件产生的来源,通常是指界面上的各种组件,比如文本框、按钮、窗口、菜单等。
事件监听器:监听事件源,并处理事件。
三者之间的关系如图3-1所示。

(图 3-1)
| 3.2 基于监听器的Android事件处理 |
Android中基于监听的事件处理,最主要的做法是为界面组件绑定对应的事件监听器。主要有四种实现方式:
内部类作为事件监听器
匿名内部类作为事件监听器
Activity作为监听器
布局文件中直接绑定
3.2.1内部类作为事件监听器
内部类作为事件监听器有两个优点,一是在本类中可以复用该监听器;二是作为内部类可以访问外部类中的界面组件。下面通过实例讲解具体用法。
实例3-1:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="com.jredu.event.MainActivity">
<TextView
android:id="@+id/show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginBottom="10dp"
/>
<Button
android:id="@+id/btnOK"
android:layout_width="match_parent" //按钮作为事件源
android:layout_height="wrap_content"
android:textSize="16sp"
android:text="点击"
/>
</LinearLayout>
上面布局文件中含有1个按钮,该按钮作为事件源,当点击该按钮后将触发点击事件。为按钮绑定事件监听器的程序如下:
public class MainActivity extends AppCompatActivity {
TextView show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
show = (TextView)findViewById(R.id.show);
Button btn = (Button)findViewById(R.id.btnOK); //获取布局文件中按钮
btn.setOnClickListener(new InnerListener()); //为按钮绑定监听器
}
private class InnerListener implements View.OnClickListener{ //单击事件的监听器
@Override
public void onClick(View v) { //事件发生时,执行的方法
show.setText("这是内部类作为事件监听器!");
}
}
}
程序中定义了一个内部类,该内部类实现了View.OnClickListener,将作为单击事件的监听器。实例运行效果如图3-2。

(图 3-2)
从该案例可以得出,基于监听的事件处理编写步骤如下:
- 通过findViewById获取要监听的界面组件,即事件源。
- 编写事件监听器类,该类需要实现特定的监听器接口。监听器类由程序员负责编写,核心工作就是实现接口中的方法。
- 为界面组件绑定监听器。
3.2.2匿名内部类作为事件监听器
使用匿名内部类作为事件监听器是目前使用比较广泛的一种事件监听器形式。具体案例代码如下:
public class AnonymousListenerActivity extends Activity {
TextView show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
show = (TextView)findViewById(R.id.show);
Button btn = (Button)findViewById(R.id.btnOK);
btn.setOnClickListener(new View.OnClickListener() { //匿名类作为监听器
@Override
public void onClick(View v) {
show.setText("这是匿名内部类作为事件监听器!"); //事件处理方法
}
});
}
}
上面程序通过直接new一个匿名类作为监听器,该形式应用广泛,缺点是代码可读性差,语法不易掌握。
3.2.3Activity作为监听器
使用Activity作为监听器,也是一种常见的事件监听器形式。这种形式需要Activity实现对应的监听器接口。具体案例代码如下:
public class ActivityAsListener extends Activity implements View.OnClickListener{ //实现单击监听器接口
TextView show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listener);
show = (TextView)findViewById(R.id.show);
findViewById(R.id.btnOK).setOnClickListener(this); //获取按钮,并绑定监听器
findViewById(R.id.btnCancel).setOnClickListener(this);
}
@Override
public void onClick(View v) { //实现接口中的单击方法,根据组件ID的进行不同的处理。
switch (v.getId()){
case R.id.btnOK:
show.setText("确定:Activity作为事件监听器!!");
break;
case R.id.btnCancel:
show.setText("取消:Activity作为事件监听器!!");
break;
default:
break;
}
}
}
上面程序使用了Activity作为监听器,该形式的优点是在同一个事件处理方法中可以处理界面中多个相同的事件;缺点是使程序结构显得比较混乱,可读性较差。
3.2.4控件绑定
最为简洁的方式就是在布局文件中为标签直接绑定事件处理方法。在Android大多数标签都支持onClick属性,通过该属性可以为标签直接绑定事件监听器。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context="com.jredu.event.MainActivity">
<TextView
android:id="@+id/show"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginBottom="10dp"
/>
<Button
android:id="@+id/btnOK"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:onClick="clickToShow" //通过onClick属性绑定事件处理方法clickToShow
android:text="确定"
/>
</LinearLayout>
public class TagBindActivity extends Activity {
TextView show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tag_listener);
show = (TextView)findViewById(R.id.show);
}
public void clickToShow(View v){ //事件处理方法,参数v为事件源
show.setText("通过标签直接绑定事件监处理方法!!");
}
}
3.2.5View中事件监听器
View中常见的事件监听器如下表:
| 监听器 | 说明 |
| View.onClickListener | 点击事件监听器 |
| View.onLongClickListener | 长按事件监听器 |
| View.onKeyListener | 键盘事件监听器 |
| View.onFocusChangeListener | 焦点事件监听器 |
| View.onTouchListener | 触摸事件监听器 |
在基于回调函数的事件处理中,UI控件承担了事件源和事件监听器双重职责。当触发UI控件的事件时,该控件会调用相应的回调函数进行处理事件,程序员所要做事情就是在回调函数中编写事件处理逻辑。下面以Android的触摸事件为例,讲解基于回调函数的事件处理方式。
通过自定义组件并重写onTouchEvent方法完成触摸事件的处理。自定义组件的具体知识可参照第十四章内容。
public class TouchView extends View { //TouchView继承自View
private int x=0;
private int y=0;
private Bitmap bitmap;
private Paint mPaint;
private boolean isPressed =false;
public TouchView(Context context) {
super(context);
init(context);
}
public TouchView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context){
bitmap = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ship_center); //初始化图片资源
mPaint = new Paint(); //创建画笔
mPaint.setAntiAlias(true); //为图片边缘去锯齿
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bitmap,x,y,mPaint);
}
private int oX;
private int oY;
@Override
public boolean onTouchEvent(MotionEvent event) { //事件参数,封装事件相关信息
switch (event.getAction()){
case MotionEvent.ACTION_DOWN: //处理ACTION_DOWN触摸事件,记录触摸点的坐标,并设置按下标识isPressed为true。
oX = (int)event.getX();
oY = (int)event.getY();
isPressed = true;
break;
case MotionEvent.ACTION_UP:
isPressed = false;
break;
case MotionEvent.ACTION_MOVE: //处理ACTION_MOVE触摸事件,计算移动的距离,并改变图片的位置。
if(isPressed) {
int mX = (int) event.getX() - oX;
int mY = (int) event.getY() - oY;
oX = (int) event.getX();
oY = (int) event.getY();
x += mX;
y += mY;
invalidate(); //通知TouchView进行重绘
}
break;
}
return true; //返回true,表明组件已经处理了该事件。
}
}
在上面自定义的TouchView类中,我们重写了onTouchEvent方法。该方法负责处理自定义组件TouchView的触摸事件,案例效果如图3-3.

(图3-3)
如上面的案例程序,onTouchEvent方法处理了多种事件。当手指触摸到屏幕、在屏幕上移动或者离开屏幕时,会分别触发ACTION_DOWN、ACTION_MOVE和ACTION_UP事件。这些事件都由onTouchEvent进行处理。
在View中除了onTouchEvent方法,还提供了用于处理其他事件的回调方法,具体参看表3-2。
| 方法 | 说明 |
| boolean onKeyDown(int keyCode,KeyEvent event) | 处理手机按键按下事件 |
| boolean onKeyUp(int keyCode,KeyEvent event) | 处理手机按键抬起事件 |
| boolean onKeyLongPress(int keyCode,KeyEvent event) | 处理手机按键长按事件 |
| boolean onTouchEvent(MotionEvent event) | 处理手机的触屏事件 |
| boolean onTrackballEvent(MotionEvent event) | 处理手机轨迹球事件 |
通过表3-1,可以发现这些回调方法都有一个boolean类型的返回值,该返回值用于表明该方法有没有处理完对应的事件。
- 如果方法返回true,则表明该回调方法完成事件处理,事件到此为止,不会继续传播。
- 如果方法返回false,则表明该回调方法未完成事件处理,事件会继续传播。
下面通过案例说明控件的事件传递过程。
public class JreduButton extends Button { //JreduButton继承Button
private Context mContext;
public JreduButton(Context context) {
super(context);
this.mContext = context;
}
public JreduButton(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
@Override
public boolean onTouchEvent(MotionEvent event) { //重写onTouchEvent方法
Toast t= Toast.makeText(this.mContext, "Button的onTouchEvent完成触摸事件的处理!", Toast.LENGTH_LONG); //定义Toast对象,并设置Toast出现的位置
t.setGravity(Gravity.CENTER,0,0);
t.show();
return true; //返回值为true,表明该方法处理了触摸事件,该事件不再传递
}
}
重写Activity中的onTouchEvent方法,同样用于处理触屏事件,Activity类的代码如下:
public class EventActivity extends AppCompatActivity {
TextView show;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event);
show = (TextView)findViewById(R.id.show);
}
@Override
public boolean onTouchEvent(MotionEvent event) { //重写onTouchEvent的方法,在该方法中设置文本显示内容
show.setText("Activity的onTouchEvent处理了事件!");
return true;
}
}
运行上面的程序,效果如图3-4。

(图3-4)
将上面程序中自定义按钮JreduButton中的onTouchEvent的返回值改为false。运行程序,效果如图3-5。

(图3-5)
基于监听的事件处理和基于回调的事件处理二者并不是孤立,Android对同一个事件的处理往往会提供这两种事件处理方式,比如针对触屏事件、按键事件,具体参看下表:
| 基于回调 | 基于监听 |
| onTouchEvent(MotionEvent event) | View.onTouchListener |
| onKeyDown(int keyCode,KeyEvent event) | View.onKeyListener |
| onKeyUp(int keyCode,KeyEvent event) | View.onKeyListener |
| 3.4 Android触屏手势操作 |
通过上一节重写View的onTouchEvent方法或是实现onTouchListener接口可以处理一些简单的触屏事件,比如按下、移动、抬起等。如果想要处理一些复杂的手势,则很难处理。Android提供了GestureDetector类,通过该类可以识别手势。
| 类、接口 | 说明 |
| GestureDetector | 手势识别类 |
| OnGestureListener | 手势滑动监听接口 |
| OnDoubleTapListener | 双击手势监听器接口 |
| SimpleOnGestureListener | OnGestureListener和OnDoubleTapListener的实现类 |
下面我们通过一个案例来讲解手势识别类的具体用法,该案例通过手势在屏幕上滑动可以完成图片切换。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:gravity="center"
tools:context="com.jredu.gesture.MainActivity">
<ImageView //用于切换显示图片的ImageView
android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/jredu01" />
</RelativeLayout>
public class MainActivity extends Activity {
private GestureDetector detector;
private ImageView imageView;
private int index = 0;
private int[] arr = {
R.drawable.jredu01,R.drawable.jredu02,
R.drawable.jredu03,R.drawable.jredu04};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
detector = new GestureDetector(this,new SimpleGestureListener()); //创建手势识别对象
imageView = (ImageView)findViewById(R.id.img);
}
@Override
public boolean onTouchEvent(MotionEvent event) { //将触屏事件交由手势识别对象处理
return detector.onTouchEvent(event);
}
private class SimpleGestureListener extends GestureDetector.SimpleOnGestureListener{
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if(e2.getX()-e1.getX()>30 && Math.abs(velocityX)>Math.abs(velocityY)){ //水平滑动距离大于30并且X轴速度大Y轴速度时进行图片切换。
index++;
if(index>=arr.length){
index=0;
}
imageView.setImageResource(arr[index]);
}
return true; //该事件已被处理。
}
}
}
在上面程序中定义了一个手势识别对象,并且该对象设置了一个手势监听器SimpleGestureListener。SimpleGesutreListener继承了GestureDetector.SimpleOnGestureListener并重写了onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)方法。该方法用于检测滑屏手势,方法中共有4个参数,分别为:
- 参数MotionEvent e1,手势起点的移动事件。
- 参数MotionEvent e2,手势终点的移动事件。
- 参数float velocityX,每秒X轴方向上移动的速度。
- 参数float velocityY,每秒Y轴方向上移动的速度。
该案例显示效果如图3-6.

(图3-6)
想要处理什么样的手势,只需要实现监听器对应的方法即可。除了上面的onFling方法,手势监听器中还包含下面这些方法。
| 方法 | 所属接口 | 说明 |
| onDown(MotionEvent e) | OnGestureListener | 单击,触摸屏按下时触发。 |
| onSingleTapUp(MotionEvent e) | OnGestureListener | 抬起,手指离开屏幕时触发。长按、滚动、滑动时不触发。 |
| onLangPress(MotionEvent) | OnGestureListener | 长按触摸屏时触发。 |
| onShowPress(MotionEvent) | OnGestureListener | 短按触摸屏时触发。 |
| onScroll(MotionEvent e1,MotionEvent e2,float distanceX,float distanceY) | OnGestureListener | 滚屏,触摸屏按下后滚动触发。 |
| onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) | OnGestureListener | 滑屏,触摸屏按下后快速移动并抬起,会先触发滚动手势,跟着触发一个滑动手势。 |
| onDoubleTap(MotionEvent e) | OnDoubleTapListener | 双击,手指在触屏上迅速点击第二次是触发。 |
| onDoubleTapEvent(MotionEvent e) | OnDoubleTapListener | 双击的按下和抬起各触发一次该事件。 |
| onSingleTapConfirmed(MotionEvent e) | OnDoubleTapListener | 单击确认。 |


浙公网安备 33010602011771号