一.view
1、dispatchTouchEvent:
问题1:onTouch还是onTouchEvent先执行?
public boolean dispatchTouchEvent(MotionEvent event){ boolean result = false; //如果有事件监听器,先让监听器处理事件。 if (mOnTouchListener.onTouch(event)) { //如果监听器成功处理了该事件,处理结果设置为true。 result = true; } //如果没有监听器,就调用自身的onTouchEvent方法来处理事件。 if (!resutlt && onTouchEvent(event)) { //如果自身的onTouchEvent成功处理事件,处理结果设置为true。 result = true; } return result; }
①onTouch优先于onTouchEvent执行,如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
②onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。
问题2:onTouch先执行,还是onClick执行?
有一个Button 按钮,要想为该按钮设置onClick事件和OnTouch事件
mTestButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.d(TAG, "onClick execute"); } }); mTestButton.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { Log.d(TAG, "onTouch execute, action event " + motionEvent.getAction()); return false; } });
View源码中dispatchTouchEvent()方法的具体实现:
1 public boolean dispatchTouchEvent(MotionEvent event) { 2 if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && 3 mOnTouchListener.onTouch(this, event)) { 4 return true; 5 } 6 return onTouchEvent(event); 7 }
分析上述代码,第2行 如果三个条件都为真的话,就返回true,否则执行onTouchEvent,先看第一个条件mOnTouchListener!=null,这个条件就是如果设置了OnTouchListener就会为true,否则是false; 第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判断当前点击的控件是否是enable的,按钮默认都是enable的,因此这个条件恒定为true;第三个条件就比较复杂了,mOnTouchListener.onTouch(this, event),这个其实就是去回调控件注册touch事件时的onTouch方法。也就是说如果我们在onTouch方法里返回true,就会让这三个条件全部成立,从而整个方法直接返回true。如果我们在onTouch方法里返回false,就会再去执行onTouchEvent(event)方法。onTouchEvent(MotionEvent event)方法同样也是在view中定义的一个方法,主要是处理传递到view 的手势事件,包括ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL四种事件。
接下来我们结合上面的具体例子,来分析一下这个过程,首先会执行dispatchTouchEvent(MotionEvent event) ,所以onTouch方法肯定是早于onClick方法的,如果在onTouch里返回false,就会出现下面的现象:
10-20 18:57:49.670: DEBUG/MainActivity(20153): onTouch execute, action event 0
10-20 18:57:49.715: DEBUG/MainActivity(20153): onTouch execute, action event 1
10-20 18:57:49.715: DEBUG/MainActivity(20153): onClick execute
即先执行了onTouch,再执行了onClick事件,而且onTouch执行了两次,一个是action_down,一个是action_up事件;
如果onTouch里返回true,则出现下面的现象:
10-20 19:01:59.795: DEBUG/MainActivity(21010): onTouch execute, action event 0
10-20 19:01:59.860: DEBUG/MainActivity(21010): onTouch execute, action event 1
结果是onClick事件没有执行了,原因是如果onTouch返回true的话,则dispatchEvent(MotionEvent event)方法直接返回true了,相当于不往下传递事件了,所以onClick不会执行,相反如果onTouch返回false的话(此时会执行onClick方法),则会执行 onTouchEvent(MotionEvent event)方法,由此可以得出这样一个结论,onClick事件的具体调用执行肯定是在onTouchEvent(MotionEvent event)方法源码中,接下来分析一下该函数的源码:
2.onTouchEvent
1 public boolean onTouchEvent(MotionEvent event) { 2 final int viewFlags = mViewFlags; 3 if ((viewFlags & ENABLED_MASK) == DISABLED) { 4 // A disabled view that is clickable still consumes the touch 5 // events, it just doesn't respond to them. 6 return (((viewFlags & CLICKABLE) == CLICKABLE || 7 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); 8 } 9 if (mTouchDelegate != null) { 10 if (mTouchDelegate.onTouchEvent(event)) { 11 return true; 12 } 13 } 14 if (((viewFlags & CLICKABLE) == CLICKABLE || 15 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 16 switch (event.getAction()) { 17 case MotionEvent.ACTION_UP: 18 boolean prepressed = (mPrivateFlags & PREPRESSED) != 0; 19 if ((mPrivateFlags & PRESSED) != 0 || prepressed) { 20 // take focus if we don't have it already and we should in 21 // touch mode. 22 boolean focusTaken = false; 23 if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { 24 focusTaken = requestFocus(); 25 } 26 if (!mHasPerformedLongPress) { 27 // This is a tap, so remove the longpress check 28 removeLongPressCallback(); 29 // Only perform take click actions if we were in the pressed state 30 if (!focusTaken) { 31 // Use a Runnable and post this rather than calling 32 // performClick directly. This lets other visual state 33 // of the view update before click actions start. 34 if (mPerformClick == null) { 35 mPerformClick = new PerformClick(); 36 } 37 if (!post(mPerformClick)) { 38 performClick(); 39 } 40 } 41 } 42 if (mUnsetPressedState == null) { 43 mUnsetPressedState = new UnsetPressedState(); 44 } 45 if (prepressed) { 46 mPrivateFlags |= PRESSED; 47 refreshDrawableState(); 48 postDelayed(mUnsetPressedState, 49 ViewConfiguration.getPressedStateDuration()); 50 } else if (!post(mUnsetPressedState)) { 51 // If the post failed, unpress right now 52 mUnsetPressedState.run(); 53 } 54 removeTapCallback(); 55 } 56 break; 57 case MotionEvent.ACTION_DOWN: 58 if (mPendingCheckForTap == null) { 59 mPendingCheckForTap = new CheckForTap(); 60 } 61 mPrivateFlags |= PREPRESSED; 62 mHasPerformedLongPress = false; 63 postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 64 break; 65 case MotionEvent.ACTION_CANCEL: 66 mPrivateFlags &= ~PRESSED; 67 refreshDrawableState(); 68 removeTapCallback(); 69 break; 70 case MotionEvent.ACTION_MOVE: 71 final int x = (int) event.getX(); 72 final int y = (int) event.getY(); 73 // Be lenient about moving outside of buttons 74 int slop = mTouchSlop; 75 if ((x < 0 - slop) || (x >= getWidth() + slop) || 76 (y < 0 - slop) || (y >= getHeight() + slop)) { 77 // Outside button 78 removeTapCallback(); 79 if ((mPrivateFlags & PRESSED) != 0) { 80 // Remove any future long press/tap checks 81 removeLongPressCallback(); 82 // Need to switch from pressed to not pressed 83 mPrivateFlags &= ~PRESSED; 84 refreshDrawableState(); 85 } 86 } 87 break; 88 } 89 return true; 90 } 91 return false; 92 }
虽然源码有点多,但是我们只重点关注关键代码,在38行我们看到了代码:performClick();这个方法从名字表义来看就是OnClick方法的调用,我们进入到该方法中去看一探究竟,是否执行了OnClick方法呢?
1 public boolean performClick() { 2 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); 3 if (mOnClickListener != null) { 4 playSoundEffect(SoundEffectConstants.CLICK); 5 mOnClickListener.onClick(this); 6 return true; 7 } 8 return false; 9 }
从上述代码可以看到,只要mOnClickListener不是null,就会去调用它的onClick方法。
经验之谈:
关于OnTouchEvent(MotionEvent事件)事件的层级传递。我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。这里需要注意,如果你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
问题:onInterceptTouchEvent与onTouchEvent默认返回值?
其中Layout里的onInterceptTouchEvent默认返回值是false,这样touch事件会传递到View控件,Layout里的onTouch默认返回值是false, View里的onTouch默认返回值是true,当我们手指点击屏幕时候,先调用ACTION_DOWN事件,当onTouch里返回值是true的时候,onTouch回继续调用ACTION_UP事件,如果onTouch里返回值是false,那么onTouch只会调用ACTION_DOWN而不调用ACTION_UP.
二.ViewGroup
1、onInterceptTouchEvent
/** 默认实现是返回false,也就是默认不拦截任何事件 */
public boolean onInterceptTouchEvent(MotionEvent ev);
2、dispatchTouchEvent
/** 根据内部拦截状态,向其child或者自己分发事件 */
public boolean dispatchTouchEvent(MotionEvent ev);
1 public boolean dispatchTouchEvent(MotionEvent ev) { 2 if (ACTION_DOWN事件 || 没有事件处理对象) { 3 if (允许拦截事件,该标志位由child调用requestDisallowInterceptTouchEvent<span style="font-family:微软雅黑;font-size:14px;">设置</span>) { 4 //查询拦截机制的结果,根据该结果来判断是否需要拦截 5 intercepted = onInterceptTouchEvent(ev); 6 } else { 7 //不允许拦截,那么不拦截 8 intercepted = false; 9 } 10 } else { 11 //不是DOWN,并且有处理对象,允许拦截,中断事件传递 12 intercepted = true; 13 } 14 15 if (不取消 && 不拦截) { 16 if (ACTION_DOWN) { //找寻接收事件序列的对象,其他事件不需要再计算事件产生对象,试想一下滑动一个ListView,当手指滑动出ListView的范围时,依然还是ListView响应后续事件。 17 for (遍历所有childView) { 18 if (触摸点不在childView内部) { 19 continue; 20 } 21 if (childView.dispatchTouchEvent(event)) { 22 保存处理该事件的View,后续事件直接传递到该View,不要重新计算; 23 } 24 } 25 } 26 27 if (还没有事件处理对象) { 28 //当前View树中没找到合适的child处理对象,把事件给自己处理,View.dispatchTouchEvent()就是把事件分发给自己 29 super.dispatchTouchEvent(event); 30 } else { 31 //传递给child 32 childView.dispatchTouchEvent(event); 33 } 34 } else if (拦截) { 35 //拦截事件,把事件给自己处理,View.dispatchTouchEvent()就是把事件分发给自己 36 super.dispatchTouchEvent(event); 37 } 38 39 return 处理结果; 40 }
3、requestDisallowInterceptTouchEvent
/** 干涩parent的事件分发机制,通知parent,是否拦截后续事件,如果设置为true,parent就不会拦截该事件,不管什么状态。设置为false,parent走正常的拦截流程 */
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (已经是当前要设置的状态) { // 已经处于这个状态, 假设我们的parent也是这个状态 return; } 设置该状态; // 传递给parent if (有父容器) { 设置父容器的拦截状态; } }