5.3 OnClickListener事件处理流程
5.3.1 OnClickListener注册和使用
应用可以使用SetOnClickListener()给一个View控件注册监听器,其实现在View里面,实际就是给View实例的成员类ListenerInfo的成员mOnClickListener赋值,
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
OnClickListener只是一个接口,定义如下,
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}
使用时,
在activity里实现:implements View.OnClickListener,
在activity里的view上调用setOnClickListener,activity就可以作为OnClickListener监听器,
view.setOnClickListener(this);
在后面调用mOnClickListener的时候,进行处理onClick了。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
5.3.2 OnClickListener栈分析
对于注册的OnClickListener监听器,不能想当然的根据代码分析,任务认为他们的会在onTouchEvent()方法里面执行,为了明白他们的执行时机,我们在一个Activity上定义一个按钮,再注册OnClickListener,点击按钮的时候会得到如下的调用栈,可以看出OnClickListener走了一条完全不同的执行过程,所以分析代码一定要结合调试,否则会误入歧途,
DialActivity$15.onClick(View) line: 3484
ImageButton(View).performClick() line: 4240
View$PerformClick.run() line: 17721
Handler.handleCallback(Message) line: 730
ViewRootImpl$ViewRootHandler(Handler).dispatchMessage(Message) line: 92
Looper.loop() line: 137
ActivityThread.main(String[]) line: 5265
Method.invokeNative(Object, Object[], Class, Class[], Class, int, boolean) line: not available [native method]
Method.invoke(Object, Object...) line: 525
ZygoteInit$MethodAndArgsCaller.run() line: 760
ZygoteInit.main(String[]) line: 576
NativeStart.main(String[]) line: not available [native method]
从调用栈可以看出,OnClick()的执行调用源头是Handler.dispatchMessage(),事件是发送给ViewRootImpl的ViewRootHandler实例的,最终调用Handler的handleCallback,根据我们在另外的文章分析的Handler的三种消息处理方式中了解到,这个消息是在应用层Post出来的,而不是HAL层通过JNI方式上传过来。现在我们有两个方向分析问题,一个是当前消息事件向上的分发处理,一个方向是Post消息的源头。
1)向上:OnClick的执行过程
handleCallback(msg)处理的消息在生成的时候,会有Callback和run方法,所以这种方式就是直接调用run方法,
private static void handleCallback(Message message) {
message.callback.run();
}
本例的callback是一个runnable类PerformClick,它的实现主体是
private final class PerformClick implements Runnable {
public void run() {
performClick();
}
}
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
return true;
}
return false;
}
从代码里就很好理解了,如果View注册了OnClickListener,则执行其所在的activity的onClick方法,到此流程就结束了。
另外,根据代码分析,OnClickListener还有另外一种调用方法,即通过调用callOnClick()来执行onClick,有兴趣的同学可单独分析。
2)向下:谁Post了这个消息
因为我们点击一个控件View的时候,是物理事件,现在onClick处理的是一个间接的应用层消息,我们还要分析物理按键引发的事件和这个消息的关联。
这个在前面分析View的onTouchEvent方法时有讲到,在处理MotionEvent.ACTION_UP事件时,会Post这个消息,进而触发OnClickListener的执行。
case MotionEvent.ACTION_UP:
if (!mHasPerformedLongPress) {
removeLongPressCallback();
…
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
…
5.3.3 OnClickListener相关分析
1、onClick就传入一个View对象,而 onTouch要传入一个View 对象和 MotionEvent的对象
2、onTouch对控件的操作比onCilck更丰富,比如判断触摸的状态(比如按下,或者放开),和得到点击的位置等等
3、当对一个控件触摸的时候touch 先调用, onclick是对 touch 的一个扩展实现
简单来理解就是在事件处理流程中,onTouch比onClick先执行,如果onTouch消耗掉事件,onClick就得不到执行。
Android框架中,对于事件的处理有两种实现机制。
u 基于监听接口
u 基于回调
基于监听机制:为android组件绑定特定的事件监听器。事件监听器是视图View类的接口。如:
View.OnClickListener、View.OnLongClickListener、 View.OnKeyListener、View.OnTouchListener、
View.OnFocusChangeListener等。
接口包含一个单独的回调方法。这个方法将在视图中注册的监听器被用户界面操作触发时由Android框架调用。
基于回调机制: Android平台中,每个View都有自己的处理事件的回调方法,开发人员可以通过重写View中的这些回调方法来实现需要的响应事件。
如:
onKeyDown()、 onKeyUp()、onFocusChanged()、 onTouchEvent()等。
u 基于监听的事件模型分工更明确,事件源、事件监听由两个类分开实现,因此具有更好的可维护性。
u 基于回调的事件处理机制会更好地提高程序的内聚性。适合统一处理事情,比如在一个工程里的所有Button在被按下的时候背景为图片a,在抬起时候背景图片为图片b。
u Android的事件处理机制保证基于监听的事件监听器会被优先触发。所以基于两种实现机制可以同时使用。
在应用里面,可以按如下方式定义并注册onTouchListener,
private ImageView mBtnNum8 = null;
…
mBtnNum8.setOnTouchListener(mOnTouchListenernumber);
…
mOnTouchListenernumber = getOnTouchClickListener();
…
private OnTouchListener getOnTouchClickListener() {
OnTouchListener onTouchListener = new OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == KeyEvent.ACTION_DOWN){
…
}else if((event.getAction() == KeyEvent.ACTION_UP)||(event.getAction() == MotionEvent.ACTION_CANCEL)){
…
};
return onTouchListener;
}
注册过程实现在View类里,
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
在一个ListenerInfo类的实例里注册监听器,ListenerInfo类主要维护各类监听器。
onTouchListener监听器的触发过程在上面流程已经分析过,就不再赘述。