Android中view的onTouch&onClick事件分发机制详解
2017-06-10 19:25
691 查看
当view设置了setOnClickListener或setOnTouchListener之后,onClick或onTouch方法才会被调用,如下
log如下:
使用自定义MyTextView,继承TextView并重写dispatchTouchEvent和onTouchEvent,log如下:
从log看调用顺序依次为(如果手没有抖,可能没有ACTION_MOVE事件)
ACTION_DOWN:MainActivity@dispatchTouchEvent,MyTextView@dispatchTouchEvent,MainActivity@onTouch,MyTextView@onTouchEvent
ACTION_MOVE:MainActivity@dispatchTouchEvent,MyTextView@dispatchTouchEvent,MainActivity@onTouch,MyTextView@onTouchEvent
ACTION_UP:MainActivity@dispatchTouchEvent,MyTextView@dispatchTouchEvent,MainActivity@onTouch,MyTextView@onTouchEvent
当MyTextView@onTouchEvent的event.getAction()为
MotionEvent.ACTION_UP时才调用onClick
注:单手或双手操作触摸屏幕事件,一般会有下面几种
单手指操作:ACTION_DOWN(0) ACTION_MOVE(2) ACTION_UP(1)
多手指操作:ACTION_DOWN ACTION_POINTER_DOWN(5) ACTION_MOVE ACTION_POINTER_UP(6) ACTION_UP.
从上面的log可以看出是先执行TextView设置的onTouch,再调用对应的onClick方法的,Activity和控件都是先执行dispatchTouchEvent来分发touchEvent,下面我们来从源码的角度,看下这个是怎么实现的
通过上面的方法可以看出,onUserInteraction方法在有当前activity捕获到touchEvent的ACTION_DOWN事件时,会被触发这个方法,说到了onUserInteraction,我们顺便看看和它相关联的一个方法onUserLeaveHint
onUserLeaveHint:当用户按下 Home键,Activity#onUserLeaveHint()将会被回调。但是当来电导致来电activity自动占据前台,Activity#onUserLeaveHint()将不会被回调。
onUserInteraction:当前activity有key, touch, or trackball event事件时都会调用,经常和onUserLeaveHint一起用来管理通知的。
接下来会调用getWindow().superDispatchTouchEvent(ev),把touchEvent派遣给PhoneWindow(getWindow()返回的是Window的实例,而PhoneWindow是Window唯一的孩子)
在PhoneWindow里继续把事件传递给DecorView,从前一篇setContentView详解中,我们知道了DecorView是所有视图的根视图,接下来看看DecorView里的superDispatchKeyEvent
最后调用了父类的dispatchKeyEvent的,接着看看父类FrameLayout.java的dispatchKeyEvent,FrameLayout里没有定义这个方法,dispatchKeyEvent是从FrameLayout的父类ViewGroup.java里获取来,我们直接看这个方法
在这个方法里会依次调用onInterceptTouchEvent,获取actionMasked是否为ACTION_CANCEL判断是否中断或取消事件的传递,通过buildTouchDispatchChildList来获取自定义排序的视图,对应的touchEvent将会被dispatch,通过dispatchTransformedTouchEvent把事件传递到对应view上
这样自定义的MyTextView中的dispatchTouchEvent就会被调用,这个方法是定义在View.java里的,下面来看看具体实现
在这个方法里面会先判断当前view的OnTouchListener是否为null,当前view是否为enabled以及mOnTouchListener.onTouch返回的结果来决定result的值,紧接着下面就用这个值来判断是否需要调用view的onTouchEvent方法,只有当result为false时,
onTouchEvent才会被调用,那么onclick是在那被调用的呢,跟进去看看onTouchEvent的定义
当touchEvent的type为ACTION_UP时,里面会调用performClick,进而在这个里面调用注册的click监听
这样我们就知道了OnClickListener和OnTouchListener的具体调用流程,以及onClick是在onTouch方法之后调用的,只有onTouch返回false时,onTouchEvent才会被调用,并且在onTouchEvent的getAction为ACTION_UP时调用performClick,处理OnClickListener,回调onClick方法。
另外需要注意的是,如果我们重写View@onTouchEvent返回false时,当前view的dispatchTouchEvent、 onTouchEvent,以及注册的onTouch事件只会在onTouchEvent的getAction为ACTION_DOWN时被调用一次,ACTION_MOVE和ACTION_UP事件都不会被调用
从上面log看MyTextView@dispatchTouchEvent和
MyTextView@onTouchEvent只在ACTION_DOWN时执行来一次,其实View@onTouchEvent里根据 switch(action)里每次都默认返回里true,这样对应的ACTION_MOVE和ACTION_UP才会被调用
mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Logger.d(MainActivity.class," onClick"); } }); mTextView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Logger.d(MainActivity.class," onTouch event :"+event.getAction()); return false; } });
log如下:
使用自定义MyTextView,继承TextView并重写dispatchTouchEvent和onTouchEvent,log如下:
从log看调用顺序依次为(如果手没有抖,可能没有ACTION_MOVE事件)
ACTION_DOWN:MainActivity@dispatchTouchEvent,MyTextView@dispatchTouchEvent,MainActivity@onTouch,MyTextView@onTouchEvent
ACTION_MOVE:MainActivity@dispatchTouchEvent,MyTextView@dispatchTouchEvent,MainActivity@onTouch,MyTextView@onTouchEvent
ACTION_UP:MainActivity@dispatchTouchEvent,MyTextView@dispatchTouchEvent,MainActivity@onTouch,MyTextView@onTouchEvent
当MyTextView@onTouchEvent的event.getAction()为
MotionEvent.ACTION_UP时才调用onClick
注:单手或双手操作触摸屏幕事件,一般会有下面几种
单手指操作:ACTION_DOWN(0) ACTION_MOVE(2) ACTION_UP(1)
多手指操作:ACTION_DOWN ACTION_POINTER_DOWN(5) ACTION_MOVE ACTION_POINTER_UP(6) ACTION_UP.
从上面的log可以看出是先执行TextView设置的onTouch,再调用对应的onClick方法的,Activity和控件都是先执行dispatchTouchEvent来分发touchEvent,下面我们来从源码的角度,看下这个是怎么实现的
frameworks/base/core/java/android/app/Activity.java public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN){ onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } public void onUserInteraction() { }
通过上面的方法可以看出,onUserInteraction方法在有当前activity捕获到touchEvent的ACTION_DOWN事件时,会被触发这个方法,说到了onUserInteraction,我们顺便看看和它相关联的一个方法onUserLeaveHint
onUserLeaveHint:当用户按下 Home键,Activity#onUserLeaveHint()将会被回调。但是当来电导致来电activity自动占据前台,Activity#onUserLeaveHint()将不会被回调。
onUserInteraction:当前activity有key, touch, or trackball event事件时都会调用,经常和onUserLeaveHint一起用来管理通知的。
接下来会调用getWindow().superDispatchTouchEvent(ev),把touchEvent派遣给PhoneWindow(getWindow()返回的是Window的实例,而PhoneWindow是Window唯一的孩子)
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java @Override public boolean superDispatchKeyEvent(KeyEvent event) { return mDecor.superDispatchKeyEvent(event); }
在PhoneWindow里继续把事件传递给DecorView,从前一篇setContentView详解中,我们知道了DecorView是所有视图的根视图,接下来看看DecorView里的superDispatchKeyEvent
frameworks/base/core/java/com/android/internal/policy/DecorView.java public boolean superDispatchKeyEvent(KeyEvent event) { // Give priority to closing action modes if applicable. if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { final int action = event.getAction(); // Back cancels action modes first. if (mPrimaryActionMode != null) { if (action == KeyEvent.ACTION_UP) { mPrimaryActionMode.finish(); } return true; } } return super.dispatchKeyEvent(event); }
最后调用了父类的dispatchKeyEvent的,接着看看父类FrameLayout.java的dispatchKeyEvent,FrameLayout里没有定义这个方法,dispatchKeyEvent是从FrameLayout的父类ViewGroup.java里获取来,我们直接看这个方法
frameworks/base/core/java/android/view/ViewGroup.java public boolean dispatchTouchEvent(MotionEvent ev) { ... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; ... // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; ... TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { ... if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } return handled; }
在这个方法里会依次调用onInterceptTouchEvent,获取actionMasked是否为ACTION_CANCEL判断是否中断或取消事件的传递,通过buildTouchDispatchChildList来获取自定义排序的视图,对应的touchEvent将会被dispatch,通过dispatchTransformedTouchEvent把事件传递到对应view上
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; ... // If the number of pointers is the same and we don't need to perform any fancy // irreversible transformations, then we can reuse the motion event for this // dispatch as long as we are careful to revert any changes we make. // Otherwise we need to make a copy. final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
这样自定义的MyTextView中的dispatchTouchEvent就会被调用,这个方法是定义在View.java里的,下面来看看具体实现
frameworks/base/core/java/android/view/View.java public boolean dispatchTouchEvent(MotionEvent event) { ... if (onFilterTouchEventForSecurity(event)) { if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { result = true; } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } } ... return result; } boolean isAccessibilityFocusedViewOrHost() { return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl() .getAccessibilityFocusedHost() == this); }
在这个方法里面会先判断当前view的OnTouchListener是否为null,当前view是否为enabled以及mOnTouchListener.onTouch返回的结果来决定result的值,紧接着下面就用这个值来判断是否需要调用view的onTouchEvent方法,只有当result为false时,
onTouchEvent才会被调用,那么onclick是在那被调用的呢,跟进去看看onTouchEvent的定义
frameworks/base/core/java/android/view/View.java public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // 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) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } } if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_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 (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 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(); } } } ... } return true; } return false; }
当touchEvent的type为ACTION_UP时,里面会调用performClick,进而在这个里面调用注册的click监听
frameworks/base/core/java/android/view/View.java 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; }
这样我们就知道了OnClickListener和OnTouchListener的具体调用流程,以及onClick是在onTouch方法之后调用的,只有onTouch返回false时,onTouchEvent才会被调用,并且在onTouchEvent的getAction为ACTION_UP时调用performClick,处理OnClickListener,回调onClick方法。
另外需要注意的是,如果我们重写View@onTouchEvent返回false时,当前view的dispatchTouchEvent、 onTouchEvent,以及注册的onTouch事件只会在onTouchEvent的getAction为ACTION_DOWN时被调用一次,ACTION_MOVE和ACTION_UP事件都不会被调用
从上面log看MyTextView@dispatchTouchEvent和
MyTextView@onTouchEvent只在ACTION_DOWN时执行来一次,其实View@onTouchEvent里根据 switch(action)里每次都默认返回里true,这样对应的ACTION_MOVE和ACTION_UP才会被调用
frameworks/base/core/java/android/view/View.java public boolean onTouchEvent(MotionEvent event) { ... if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: ... break; case MotionEvent.ACTION_DOWN: ... break; case MotionEvent.ACTION_CANCEL: ... break; case MotionEvent.ACTION_MOVE: ... break; } return true; } return false; }
相关文章推荐
- Android 读书笔记:View的事件分发机制 源码详解 ------《Android开发艺术探索》
- Android中事件处理机制之——View的事件分发详解(一)
- [置顶] 一图详解Android View的事件分发机制
- Android View事件分发机制详解
- Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)dispatchtouchevent,ontouch,ontouchevent,onclick
- Android View 事件分发机制详解
- Android事件分发机制详解(1)----探究View的事件分发
- Android:View事件分发机制详解
- 【Android View事件(二)】详解事件分发机制
- Android View 事件分发机制源码详解(ViewGroup篇)
- Android ViewGroup/View 事件分发机制详解
- Android 事件分发机制详解(1)-View
- Android ViewGroup 事件分发机制详解
- Android事件分发机制详解(2)----分析ViewGruop的事件分发
- Android触摸事件分发处理机制详解与源码分析一(View篇)
- Android触摸屏事件派发机制详解与源码分析一(View篇)onTouch,onClick,ontouchevent
- Android:ViewGroup事件分发机制详解
- Android View 事件分发机制 && Android ViewGroup 事件分发机制 源码解析 --总结
- Android View 事件分发机制源码详解(ViewGroup篇)
- Android View 事件分发机制详解