onTach介绍
ontach是AndroID系统中整个事件机制的基础。AndroID中的其他事件,如onClick、onLongClick等都是以onTach为基础的。
onTach包括从手指按下到离开手机屏幕的整个过程,在微观形式上,具体表现为action_down、action_move和action_up等过程。
onTach两种主要定义形式如下:
1.在自定义控件中,常见的有重写ontouchEvent(MotionEvent ev)
方法。如在开发中经常可以看到重写的ontouchEvent方法,
并且其中有针对不同的微观表现(action_down、action_move和action_up等)做出的相应判断,执行逻辑并可能返回不同的布尔值。
2.在代码中,直接对现有控件设置setontouchListener监听器。并重写监听器的ontouch方法。ontouch回调函数中有vIEw和MotionEvent
ontouch事件传递机制
大家都知道一般我们使用的UI控件都是继承自共同的父类――VIEw。所以VIEw这个类应该掌管着ontouch事件的相关处理。那就让我们去看看:在VIEw中寻找touch相关的方法,其中一个很容易地引起了我们的注意: dispatchtouchEvent(MotionEvent event)
。
根据方法名的意思应该是负责分发触摸事件的,下面给出了源码:
/** * Pass the touch screen motion event down to the target vIEw,or this * vIEw if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the vIEw,false otherwise. */ public boolean dispatchtouchEvent(MotionEvent event) { // If the event should be handled by accessibility focus first. if (event.isTargetAccessibilityFocus()) { // We don't have focus or no virtual descendant has it,do not handle the event. if (!isAccessibilityFocusedVIEwOrHost()) { return false; } // We have focus and got the event,then use normal event dispatch. event.setTargetAccessibilityFocus(false); } boolean result = false; if (minputEventConsistencyVerifIEr != null) { minputEventConsistencyVerifIEr.ontouchEvent(event,0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopnestedScroll(); } if (onFiltertouchEventForSecurity(event)) { //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; } } if (!result && minputEventConsistencyVerifIEr != null) { minputEventConsistencyVerifIEr.onUnhandledEvent(event,0); } // Clean up after nested scrolls if this is the end of a gesture; // also cancel it if we trIEd an ACTION_DOWN but we dIDn't want the rest // of the gesture. if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL || (actionMasked == MotionEvent.ACTION_DOWN && !result)) { stopnestedScroll(); } return result;}
源码有点长,但我们不必每一行都看。首先注意到dispatchtouchEvent的返回值是boolean类型的,注释上的解释:@return True if the event was handled by the vIEw,false otherwise.
也就是说如果该触摸事件被这个VIEw消费了就返回true,否则返回false。在方法中首先判断了该event是否是否得到了焦点,如果没有得到焦点直接返回false。然后让我们把目光转向if (li != null && li.mOntouchListener != null&& (mVIEwFlags & ENABLED_MASK) == ENABLED&& li.mOntouchListener.ontouch(this,event))
这个片段,看到这里有一个名为li的局部变量,属于 ListenerInfo 类,经 mListenerInfo 赋值得到。ListenerInfo只是一个包装类,里面封装了大量的监听器。
再在 VIEw 类中去寻找 mListenerInfo ,可以看到下面的代码:
ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; } mListenerInfo = new ListenerInfo(); return mListenerInfo;}
因此我们可以知道mListenerInfo是不为空的,所以li也不是空,第一个判断为true,然后看到li.mOntouchListener,前面说过ListenerInfo是一个监听器的封装类,所以我们同样去追踪mOntouchListener:
/** * Register a callback to be invoked when a touch event is sent to this vIEw. * @param l the touch Listener to attach to this vIEw */public voID setontouchListener(OntouchListener l) { getListenerInfo().mOntouchListener = l;}
正是通过上面的方法来设置 mOntouchListener 的,我想上面的方法大家肯定都很熟悉吧,正是我们平时经常用的 xxx.setontouchListener ,好了我们从中得知如果设置了OntouchListener则第二个判断也为true,第三个判断为如果该VIEw是否为enable,默认都是enable的,所以同样为true。还剩最后一个:li.mOntouchListener.ontouch(this,event)
,显然是回调了第二个判断中监听器的ontouch()
方法,如果ontouch()
方法返回true,则上面四个判断全部为true,dispatchtouchEvent()
方法会返回true,并且不会执行if (!result && ontouchEvent(event))
这个判断;而在这个判断中我们又看到了一个熟悉的方法:ontouchEvent()
。所以想要执行ontouchEvent,则在上面的四个判断中必须至少有一个false。
那就假定我们在ontouch()
方法中返回的是false,这样就顺利地执行了ontouchEvent,那就看看ontouchEvent的源码吧:
/** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions,it is recommended that * the actions be performed by implementing and calling * {@link #performClick()}. This will ensure consistent system behavior,* including: * <ul> * <li>obeying click sound preferences * <li>dispatching OnClickListener calls * <li>handling {@link AccessibilityNodeInfo#ACTION_CliCK ACTION_CliCK} when * accessibility features are enabled * </ul> * * @param event The motion event. * @return True if the event was handled,false otherwise. */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(); } } } if (mUnsetpressedState == null) { mUnsetpressedState = new UnsetpressedState(); } if (prepressed) { postDelayed(mUnsetpressedState,VIEwConfiguration.getpressedStateDuration()); } else if (!post(mUnsetpressedState)) { // If the post Failed,unpress right Now mUnsetpressedState.run(); } removeTapCallback(); } mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; if (performbuttonActionOntouchDown(event)) { break; } // Walk up the hIErarchy to determine if we're insIDe a scrolling container. boolean isInScrollingContainer = isInScrollingContainer(); // For vIEws insIDe a scrolling container,delay the pressed Feedback for // a short period in case this is a scroll. if (isInScrollingContainer) { mPrivateFlags |= PFLAG_PREpressed; if (mPendingCheckForTap == null) { mPendingCheckForTap = new CheckForTap(); } mPendingCheckForTap.x = event.getX(); mPendingCheckForTap.y = event.getY(); postDelayed(mPendingCheckForTap,VIEwConfiguration.getTapTimeout()); } else { // Not insIDe a scrolling container,so show the Feedback right away setpressed(true,y); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setpressed(false); removeTapCallback(); removeLongPressCallback(); mInContextbuttonPress = false; mHasPerformedLongPress = false; mIgnoreNextUpEvent = false; break; case MotionEvent.ACTION_MOVE: drawableHotspotChanged(x,y); // Be lenIEnt about moving outsIDe of buttons if (!pointInVIEw(x,y,mtouchSlop)) { // OutsIDe button removeTapCallback(); if ((mPrivateFlags & PFLAG_pressed) != 0) { // Remove any future long press/tap checks removeLongPressCallback(); setpressed(false); } } break; } return true; } return false;}
这段源码比 dispatchtouchEvent 的还要长,不过同样我们挑重点的看:if (((vIEwFlags & CliCKABLE) == CliCKABLE || (vIEwFlags & LONG_CliCKABLE) == LONG_CliCKABLE) || (vIEwFlags & CONTEXT_CliCKABLE) == CONTEXT_CliCKABLE)
看到这句话就大概知道了主要是判断该vIEw是否是可点击的,如果可以点击则接着执行,否则直接返回false。可以看到if里面用switch来判断是哪种触摸事件,但在最后都是返回true的。
还有一点要注意:在 ACTION_UP 中会执行 performClick()
方法:
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;}
可以看到上面的li.mOnClickListener.onClick(this);
,没错,我们好像又有了新的发现。根据上面的经验,这句代码会去回调我们设置好的点击事件监听器。也就是我们平常用的xxx.setonClickListener(Listener);
/** * Register a callback to be invoked when this vIEw is clicked. If this vIEw is not * clickable,it becomes clickable. * * @param l The callback that will run * * @see #setClickable(boolean) */public voID setonClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l;}
我们可以看到上面方法设置正是mListenerInfo的点击监听器,验证了上面的猜想。到了这里ontouch事件的传递机制基本已经分析完成了,也算是告一段落了。
好了,这下我们可以解决开头的问题了,顺便我们再来小结一下:在dispatchtouchEvent中,如果设置了OntouchListener并且VIEw是enable的,那么首先被执行的是OntouchListener中的ontouch(VIEw v,MotionEvent event)
。若ontouch返回true,则dispatchtouchEvent不再往下执行并且返回true;不然会执行ontouchEvent,在ontouchEvent中若VIEw是可点击的,则返回true,不然为false。还有在ontouchEvent中若VIEw是可点击以及当前触摸事件为ACTION_UP,会执行performClick()
,回调OnClickListener的onClick方法。
下面是我画的一张草图:
还有一点值得注意的地方是:假如当前事件是ACTION_DOWN,只有dispatchtouchEvent返回true了之后该VIEw才会接收到接下来的ACTION_MOVE,ACTION_UP事件,也就是说只有事件被消费了才能接收接下来的事件。
总结
以上就是关于AndroID中ontouch事件传递机制的详细分析,希望对各位AndroID开发者们的学习或者工作能有一定的帮助,如果有疑问大家可以留言交流。
以上是内存溢出为你收集整理的详细分析Android中onTouch事件传递机制全部内容,希望文章能够帮你解决详细分析Android中onTouch事件传递机制所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)