Android - View 事件分发机制
1、事件调用顺序
以Button为例,以下分别为 onClick、onTouch
1 2 3 4 5 6 | button.setOnClickListener( new OnClickListener() { @Override public void onClick(View v) { Log.d( "TAG" , "onClick execute" ); } }); |
1 2 3 4 5 6 7 | button.setOnTouchListener( new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.d( "TAG" , "onTouch execute, action " + event.getAction()); return false ; } }); |
调用顺序:onTouch->onClick, 并且OnTouch调用两次(ACTION_DOWN、ACTION_UP)
onTouch有返回值,true表示事件被onTouch消费掉了,不会继续向下传递;
2、控件的dispatchTouchEvent方法
Button控件的dispatchTouchEvent继承于View (Button->TextView->View)
1 2 3 4 5 6 7 | public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch( this , event)) { return true ; } return onTouchEvent(event); } |
如果onTouchListener监听器不为空, 当前点击的控件是enable的,mOnTouchListener.onTouch(this, event)返回TRUE,就返回TRUE,否则就去执行onTouchEvent(event)方法并返回;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | 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)) { performClick(); } } } 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 ; } |
首先在第14行我们可以看出,如果该控件是可以点击的就会进入到第16行的switch判断中去,而如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
在经过种种判断之后,会执行到第38行的performClick()方法: mOnClickListener.onClick(this) 就是在此时被调用;
1 2 3 4 5 6 7 8 9 | public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null ) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick( this ); return true ; } return false ; } |
3、Touch事件的层级传递
我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等事件。
这里需要注意,如果处理ACTION_DOWN时,dispatchTouchEvent返回FALSE,后面一系列其它的action就不会再得到处理。总之,当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发后一个action。
参考前面的源码,首先在OnTouchListener之onTouch事件里返回了false,代码执行进入到控件onTouchEvent方法中,然后我们来看一下onTouchEvent方法的细节。
由于我们点击了按钮,代码执行进入到第14行的IF分支,不管action类型,最终都会返回一个true。如果将Button换成ImageView,因为ImageView默认不可点击,onTouchEvent中代码执行进入else分支,dispatchTouchEvent返回false;
4、总结
OnTouchListener之OnTouch事件,返回False,代码执行进入控件OnTouchEvent方法;
控件OnTouchEvent方法中,返回True,才会继续触发后面的action(ACTION_DOWN,ACTION_MOVE,ACTION_UP)
比如点击事件,先响应ACTION_DOWN,返回true,然后手抬起,又从dispatchTouchEvent()分发,再响应ACTION_UP
参考链接:简书 http://www.jianshu.com/p/b1ee0985da16
郭神 http://blog.csdn.net/guolin_blog/article/details/9097463
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步