Android 事件机制 - 回调
在上一章节中我们讲过 Android 由两种事件处理机制 基于监听 和 基于回调,基于监听 我们在上一章节中已经学习了,这一章节我们来学习 基于回调的事件处理机制
回调
回调就是组件自己会监听自己,如果自己的某些属性改变了,就会调用自己的某些方法,而这些方法又会调用其它组件的某些方法
打个比方,比如我最爱喝 金桔柠檬,我们点好后只要留下 姓 ,然后坐等服务员叫我们就好了,服务员会时时监听我的 金桔柠檬 做好了没,如果做好了,就会说 X 先生,你的金桔柠檬做好了
服务员主动告诉我们 x 先生,金桔柠檬做好了 就是一种回调
Android 回调的事件处理机制
Android 有两个场景可以使用基于回调的事件处理机制
- 自定义
View
- 基于回调的事件传播
自定义 View
当用户在 UI 控件上触发某个事件时,控件有自己特定的方法会负责处理该事件
自定义 View 通用的做法是: 继承基础的 UI 控件,重写该控件的事件处理方法
在 xml 布局中使用自定义的 View 时,需要使用 "全限定类名"
常见 View 组件的回调方法
Android 中很多 UI 控件都提供了一些是事件回调方法,比如 View,有以下几个方法
回调方法 | 说明 |
---|---|
boolean onTouchEvent(MotionEvent event) | 触摸 UI 控件时触发 |
boolean onKeyDown(int keyCode,KeyEvent event) | 在 UI 控件上按下手指时触发 |
boolean onKeyUp(int keyCode,KeyEvent event) | 在 UI 控件上松开手指时触发 |
boolean onKeyLongPress(int keyCode,KeyEvent event) | 长按组件某个按钮时 |
boolean onKeyShortcut(int keyCode,KeyEvent event) | 键盘快捷键事件发生 |
boolean onTrackballEvent(MotionEvent event) | 在组件上触发轨迹球屏事件 |
void onFocusChanged (boolean gainFocus, int direction, Rect previously FocusedRect) | 当 UI 控件的焦点发生改变 |
范例
我们接下来写一个范例来熟悉下基于回调的事件处理方式
我们会自定义一个 EditText,然后重写上表中的一系列方法
-
创建一个 空的 Android 项目
cn.twle.android.EventCallback
-
在
MainActivity.java
目录下新建一个文件MsEditText.java
实现自定义EditText
package cn.twle.android.eventcallback; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.widget.EditText; import android.content.Context; public class MsEditText extends EditText { private static String TAG = "MsEditText"; public MsEditText(Context context, AttributeSet attrs) { super(context, attrs); } //重写键盘按下触发的事件 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { super.onKeyDown(keyCode,event); Log.d(TAG, "onKeyDown() 方法被调用"); return true; } //重写弹起键盘触发的事件 @Override public boolean onKeyUp(int keyCode, KeyEvent event) { super.onKeyUp(keyCode,event); Log.d(TAG,"onKeyUp() 方法被调用"); return true; } //组件被触摸了 @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); Log.d(TAG,"onTouchEvent() 方法被调用"); return true; } }
-
修改
activity_main.xml
添加刚刚自定义的按钮<?xml version="1.0" encoding="utf-8" ?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" > <cn.twle.android.eventcallback.MsEditText android:layout_marginTop="16dp" android:id="@+id/et_ev" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="自定义 MsEditText"/> </LinearLayout>
运行后显示日志如下
我们可以看到,因为我们直接重写了 Button 的三个回调方法,当发生点击事件后就不需要添加 事件监听器就可以完成回调
组件会处理对应的事件,即事件由事件源(组件)自身处理
基于回调的事件传播
细心的看一下上表列出的几个事件回调方法,为什么它们的返回值总是 boolean
类型,它有什么用呢?
要回答这个问题,我们就要先搞清楚 Android 中事件处理的流程
- 触发该 UI 的事件监听器
- 触发该 UI 控件提供的回调方法
- 传播到该 UI 组件所在的
Activity
如果三个流程中任意一个返回了 true
就不会继续向外传播,后面的就不会执行了
所以 返回值 boolean 是用来标示这个方法是否已经被完全处理,如果为 false
的话就是没处理完,就会触发该组件所在 Activity
中相关的回调方法
综上,一个事件是否向外传播取决于方法的返回值是时 true
还是 false
范例
-
复用上面的范例
-
修改
MsEditText.java
让onKeyDown()
方法返回false
package cn.twle.android.eventcallback; import android.util.AttributeSet; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.widget.EditText; import android.content.Context; public class MsEditText extends EditText { private static String TAG = "MsEditText"; public MsEditText(Context context, AttributeSet attrs) { super(context, attrs); } //重写键盘按下触发的事件 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { super.onKeyDown(keyCode,event); Log.d(TAG, "onKeyDown() 方法被调用"); return false; } //重写弹起键盘触发的事件 @Override public boolean onKeyUp(int keyCode, KeyEvent event) { super.onKeyUp(keyCode,event); Log.d(TAG,"onKeyUp() 方法被调用"); return true; } //组件被触摸了 @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); Log.d(TAG,"onTouchEvent() 方法被调用"); return true; } }
-
然后修改 MainActivity.java
package cn.twle.android.eventcallback; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.KeyEvent; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MsEditText et_ev = (MsEditText)findViewById(R.id.et_ev); et_ev.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if(event.getAction() == KeyEvent.ACTION_DOWN) { Log.d("MsEditText","监听器的 onKeyDown() 方法被调用"); } return false; } }); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { super.onKeyDown(keyCode, event); Log.i("MsEditText","Activity 的 onKeyDown() 方法被调用"); return false; } }
从运行结果可以看出,我们上面说的三个流程是正确的,传播机制为:
监听器 ---> view 组件的回调方法 ---> Activity的回调方法
可以说 Android 事件处理机制中的基于回调的事件处理机制的核心就是 事件传播的顺序
监听器优先,然后到 View 组件自身,最后再到 Activity 任意一个流程返回值 false 继续传播,true 终止传播