首先参考郭霖大神两篇很详细的关于事件分发机制的文章
http://blog.csdn.net/guolin_blog/article/details/9097463
http://blog.csdn.net/guolin_blog/article/details/9153747
文章有点长,所以自行整理重点方便以后自行查阅
View.dispatchTouchEvent()详解
只要你触摸到了任何一个控件,就一定会调用该控件的dispatchTouchEvent方法,从当前类一直往继承关系向上查找dispatchTouchEvent方法,默认情况下View实现dispatchTouchEvent这个方法,如下
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true
- }
- return onTouchEvent(event);
- }
mOnTouchListener在如下方法中被赋值
- public void setOnTouchListener(OnTouchListener l) {
- mOnTouchListener = l;
- }
同时说明如果mOnTouchListener.onTouch(this, event)返回true,那么dispatchTouchEvent函数执行完毕,那么接下来执行不到控件的onTouchEvent方法和绑定的onClick方法,当前事件结束。
举例子说明
- myButton.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:// 0
- Log.d(TAG, "onTouch ACTION_DOWN");
- break;
- case MotionEvent.ACTION_UP:// 1
- Log.d(TAG, "onTouch ACTION_UP");
- break;
- default:
- break;
- }
- return true;
- }
- });
- myButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- // TODO Auto-generated method stub
- Log.d(TAG, "onClick");
- }
- });
那么onClick永远也打印不了,同时自定义的myButton的onTouchEvent也永远执行不了
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- // TODO Auto-generated method stub
- Log.d("Bill", "onTouchEvent " + event.getAction());
- //如果注释了此句,那么不管这个方法返回true-false,都不会执行到onClick方法,因为dispatchTouchEvent不会调用View的onTouchEvent方法
- super.onTouchEvent(event);
- return false;//此时返回true-false无所谓,因为调用了super.onTouchEvent(event),会执行View的onTouchEvent也就会执行到
- //performClick->onClick方法
- }
View.onTouchEvent()方法详解
- public boolean onTouchEvent(MotionEvent event) {
- final int viewFlags = mViewFlags;
- if ((viewFlags & ENABLED_MASK) == DISABLED) {
- // A disabled view that is clickable still consumes the touch
- // events, it just doesn't respond to them.
- return (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
- }
- if (mTouchDelegate != null) {
- if (mTouchDelegate.onTouchEvent(event)) {
- return true;
- }
- }
- if (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP
- boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
- if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
- // take focus if we don't have it already and we should in
- // touch mode.
- boolean focusTaken = false;
- if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
- focusTaken = requestFocus();
- }
- if (!mHasPerformedLongPress) {
- // This is a tap, so remove the longpress check
- removeLongPressCallback();
- // Only perform take click actions if we were in the pressed state
- if (!focusTaken) {
- // Use a Runnable and post this rather than calling
- // performClick directly. This lets other visual state
- // of the view update before click actions start.
- if (mPerformClick == null) {
- mPerformClick = new PerformClick();
- }
- if (!post(mPerformClick)) {
- <span style="color:#FF0000;">performClick();</span>
- }
- }
- }
- if (mUnsetPressedState == null) {
- mUnsetPressedState = new UnsetPressedState();
- }
- if (prepressed) {
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- } else if (!post(mUnsetPressedState)) {
- // If the post failed, unpress right now
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- break;
- case MotionEvent.ACTION_DOWN:
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- mPrivateFlags |= PREPRESSED;
- mHasPerformedLongPress = false;
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- break;
- case MotionEvent.ACTION_CANCEL:
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
- removeTapCallback();
- break;
- case MotionEvent.ACTION_MOVE:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- // Be lenient about moving outside of buttons
- int slop = mTouchSlop;
- if ((x < 0 - slop) || (x >= getWidth() + slop) ||
- (y < 0 - slop) || (y >= getHeight() + slop)) {
- // Outside button
- removeTapCallback();
- if ((mPrivateFlags & PRESSED) != 0) {
- // Remove any future long press/tap checks
- removeLongPressCallback();
- // Need to switch from pressed to not pressed
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
- }
- }
- break;
- }
- return true;
- }
- return false;
- }
1. 该控件必须是可点击的才能执行到performClick->onClick,此时onTouchEvent返回true,从而dispatchTouchEvent方法返回true,所以会会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件,否则被第一事件ACTION_DOWN中断,因为只有当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
2. performClick->onClick是在MotionEvent.ACTION_UP中调用而不是第一个事件.ACTION_DOWN被调用。
举例说明ImageView默认情况下是不可点击的,所以只会触发ACTION_DOWN事件,除非给ImageView增加一个android:clickable="true"的属性,这样ImageView变成可点击的之后,即使在onTouch里返回了false,ACTION_DOWN之后的其它action也是可以得到执行的。
performClick()方法逻辑
- public boolean performClick() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
- if (mOnClickListener != null) {
- playSoundEffect(SoundEffectConstants.CLICK);
- mOnClickListener.onClick(this);
- return true;
- }
- return false;
- }