Activity或View类的onTouchEvent()回调函数会接收到touch事件。
一个完整的手势是从ACTION_DOWN开始,到ACTION_UP结束。
简单的情况下,我们只需要在onTouchEvent()中写个switch case语句,处理各种事件(Touch Down、Touch Move、Touch Up等),但是比较复杂的动作就需要更多的处理了。
ViewGroup作为一个parent是可以截获传向它的child的touch事件的。
如果一个ViewGroup的onInterceptTouchEvent()方法返回true,说明Touch事件被截获,子View不再接收到Touch事件,而是转向本ViewGroup的onTouchEvent()方法处理。从Down开始,之后的Move,Up都会在onTouchEvent()方法中处理。
先前还在处理touch event的child view将会接收到一个ACTION_CANCEL。
如果onInterceptTouchEvent返回false,则事件会交给child view处理。
Android中提供了ViewGroup、View、Activity三个层次的Touch事件处理。
处理过程是按照Touch事件从上倒下传递,再按照是否消费的返回值,从下到上返回,即如果View的onTouchEvent返回false,将会向上传给他的parent的ViewGroup,如果ViewGroup不处理,将会一直向上返回到Activity。
即隧道式向下分发,然后冒泡式向上处理。
Activity的Touch事件分发
Activity的dispatchTouchEvent(MotionEvent ev):
/** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed. */ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
对于要正常处理的触摸屏幕事件,要确认调用这个实现。
返回值为true的时候,表明这个事件被消费。
Activity的onTouchEvent(MotionEvent event):
/** * Called when a touch screen event was not handled by any of the views * under it. This is most useful to process touch events that happen * outside of your window bounds, where there is no view to receive it. * * @param event The touch screen event being processed. * * @return Return true if you have consumed the event, false if you haven't. * The default implementation always returns false. */ public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; }
这对于处理window边界之外的Touch事件非常有用,因为通常是没有View会接收到它们的。
返回值为true表明你已经消费了这个事件,false则表示没有消费,默认实现中返回false。
View的Touch事件
View的dispatchTouchEvent(MotionEvent event):
将touch屏幕的事件向下传递到目标view,或者传递到本view,如果他就是目标view。
如果事件被这个view处理,则返回true,否则返回false。
View的onTouchEvent
onTouchEvent(MotionEvent event):
/** * Implement this method to handle touch screen motion events. * * @param event The motion event. * @return True if the event was handled, false otherwise. */ public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == 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)); } 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 & 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); } 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) { postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration()); } else if (!post(mUnsetPressedState)) { // If the post failed, unpress right now mUnsetPressedState.run(); } removeTapCallback(); } 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(); } postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); } else { // Not inside a scrolling container, so show the feedback right away setPressed(true); checkForLongClick(0); } break; case MotionEvent.ACTION_CANCEL: setPressed(false); removeTapCallback(); removeLongPressCallback(); break; case MotionEvent.ACTION_MOVE: final int x = (int) event.getX(); final int y = (int) event.getY(); // 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; }实现这个方法来处理touch屏幕的事件。
如果这个事件被处理了,就返回true。
因为ViewGroup是View的子类,所以它覆写方法时会加上Override注解,如果没有覆写,则沿用父类实现,如onTouchEvent()。
dispatchTouchEvent (MotionEvent ev):
将Touch事件向下传递到目标View,因为自身也是View,所以目标View如果是自己,则传递给自己。
返回true,如果这个事件是被本View所处理。
onInterceptTouchEvent (MotionEvent ev)
ViewGroup中比较特殊的一个方法。默认实现如下:
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
这个方法注释很长:
实现这个方法可以截获所有的Touch事件。这样你就可以控制向child分发的Touch事件。
一般实现这个方法,需要同时实现View.onTouchEvent(MotionEvent)方法。
事件是按照如下的顺序被接收的:
1.首先在onInterceptTouchEvent()中接收到Down事件。
2.Down事件将会:要么给这个ViewGroup的一个child view处理,要么是这个ViewGroup自己的onTouchEvent()处理。
处理意味着你应该在onTouchEvent()的实现中返回true,这样你就可以继续看到这个gesture的其他部分,如果返回false,将会返回寻找一个parent view去处理它。
如果在onTouchEvent()中返回true,你将不会再在onInterceptTouchEvent()再收到接下来的事件,所有的Touch处理必须放在onTouchEvent()中正常处理。
3.如果你在onInterceptTouchEvent()中返回false,接下来的每一个事件都会先传到onInterceptTouchEvent(),之后传递到目标view的onTouchEvent()中。
4.如果你在onInterceptTouchEvent()中返回true,将不会再接收到手势中的其他事件,当前的目标view将会接收到同一个事件,但是动作是 ACTION_CANCEL
。其他所有的事件将会被直接传递到onTouchEvent()中,并且不再在onInterceptTouchEvent()中出现。
onInterceptTouchEvent()的返回值:true将会从子view中偷取运动事件,把它们分配到这个ViewGroup的onTouchEvent()中,当前目标view将会接收到取消动作,并且接下来的动作都不会再经过onInterceptTouchEvent()。
ViewGroup的onTouchEvent()是采用父类View的默认实现,有需要的话可以覆写。
Click事件:View的短按和长按都是注册监听器的(setListener):
onClick是在ACTION_UP之后执行的。
onLongClick则是按下到一定时间之后执行的,这个时间是ViewConfiguration中的:
private static final int TAP_TIMEOUT = 180; //180毫秒
这里需要注意onLongClick的返回值,如果是false,则onLongClick之后,手指抬起,ACTION_UP之后还是回执行到onClick;但是如果onLongClick返回true,则不会再调用onClick。