Android中的Touch事件
?
Android中的Touch事件處理
?
主要內容
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;}
?
如果一個屏幕觸摸事件沒有被這個Activity下的任何View所處理,Activity的onTouchEvent將會調用。
這對于處理window邊界之外的Touch事件非常有用,因為通常是沒有View會接收到它們的。
返回值為true表明你已經消費了這個事件,false則表示沒有消費,默認實現中返回false。
?
View的Touch事件
View的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 (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}if (onFilterTouchEventForSecurity(event)) {//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) {return true;}if (onTouchEvent(event)) {return true;}}if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);}return false;} View --> dispatchTouchEvent?
將touch屏幕的事件向下傳遞到目標View,或者傳遞到本View,如果它就是目標View。
如果事件被這個View處理,則返回true,否則返回false。
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 stateif (!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 awaysetPressed(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 buttonsif (!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;} View --> onTouchEvent?
實現這個方法來處理touch屏幕的事件。
返回true如果這個事件被處理了。
?
ViewGroup的Touch事件
因為ViewGroup是View的子類,所以它覆寫方法時會加上Override注解,如果沒有覆寫,則沿用父類實現,如onTouchEvent()。
dispatchTouchEvent (MotionEvent ev):
/*** {@inheritDoc}*/@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(ev, 1);}boolean handled = false;if (onFilterTouchEventForSecurity(ev)) {final int action = ev.getAction();final int actionMasked = action & MotionEvent.ACTION_MASK;// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {// Throw away all previous state when starting a new touch gesture.// The framework may have dropped the up or cancel event for the previous gesture// due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev);resetTouchState();}// 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;}// Check for cancelation.final boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// Update list of touch targets for pointer down, if needed.final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;if (!canceled && !intercepted) {if (actionMasked == MotionEvent.ACTION_DOWN|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {final int actionIndex = ev.getActionIndex(); // always 0 for downfinal int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex): TouchTarget.ALL_POINTER_IDS;// Clean up earlier touch targets for this pointer id in case they// have become out of sync. removePointersFromTouchTargets(idBitsToAssign);final int childrenCount = mChildrenCount;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 View[] children = mChildren;final boolean customOrder = isChildrenDrawingOrderEnabled();for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = customOrder ?getChildDrawingOrder(childrenCount, i) : i;final View child = children[childIndex];if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {continue;}newTouchTarget = getTouchTarget(child);if (newTouchTarget != null) {// Child is already receiving touch within its bounds.// Give it the new pointer in addition to the ones it is handling.newTouchTarget.pointerIdBits |= idBitsToAssign;break;}resetCancelNextUpFlag(child);if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {// Child wants to receive touch within its bounds.mLastTouchDownTime = ev.getDownTime();mLastTouchDownIndex = childIndex;mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();newTouchTarget = addTouchTarget(child, idBitsToAssign);alreadyDispatchedToNewTouchTarget = true;break;}}}if (newTouchTarget == null && mFirstTouchTarget != null) {// Did not find a child to receive the event.// Assign the pointer to the least recently added target.newTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}newTouchTarget.pointerIdBits |= idBitsToAssign;}}}// 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);} else {// Dispatch to touch targets, excluding the new touch target if we already// dispatched to it. Cancel touch targets if necessary.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {final TouchTarget next = target.next;if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {handled = true;} else {final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}if (cancelChild) {if (predecessor == null) {mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;continue;}}predecessor = target;target = next;}}// Update list of touch targets for pointer up or cancel, if needed.if (canceled|| actionMasked == MotionEvent.ACTION_UP|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {resetTouchState();} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {final int actionIndex = ev.getActionIndex();final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);removePointersFromTouchTargets(idBitsToRemove);}}if (!handled && mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);}return handled;} ViewGroup --> dispatchTouchEvent?
將Touch事件向下傳遞到目標View,因為自身也是View,所以目標View如果是自己,則傳遞給自己。
返回true,如果這個事件是被本View所處理。
?
onInterceptTouchEvent (MotionEvent ev)
ViewGroup中比較特殊的一個方法。默認實現如下:
public boolean onInterceptTouchEvent(MotionEvent ev) {return false;}?
這個方法注釋很長:
/*** Implement this method to intercept all touch screen motion events. This* allows you to watch events as they are dispatched to your children, and* take ownership of the current gesture at any point.** <p>Using this function takes some care, as it has a fairly complicated* interaction with {@link View#onTouchEvent(MotionEvent)* View.onTouchEvent(MotionEvent)}, and using it requires implementing* that method as well as this one in the correct way. Events will be* received in the following order:** <ol>* <li> You will receive the down event here.* <li> The down event will be handled either by a child of this view* group, or given to your own onTouchEvent() method to handle; this means* you should implement onTouchEvent() to return true, so you will* continue to see the rest of the gesture (instead of looking for* a parent view to handle it). Also, by returning true from* onTouchEvent(), you will not receive any following* events in onInterceptTouchEvent() and all touch processing must* happen in onTouchEvent() like normal.* <li> For as long as you return false from this function, each following* event (up to and including the final up) will be delivered first here* and then to the target's onTouchEvent().* <li> If you return true from here, you will not receive any* following events: the target view will receive the same event but* with the action {@link MotionEvent#ACTION_CANCEL}, and all further* events will be delivered to your onTouchEvent() method and no longer* appear here.* </ol>** @param ev The motion event being dispatched down the hierarchy.* @return Return true to steal motion events from the children and have* them dispatched to this ViewGroup through onTouchEvent().* The current target will receive an ACTION_CANCEL event, and no further* messages will be delivered here.*/ ViewGroup onInterceptTouchEvent?
實現這個方法可以截獲所有的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的默認實現,有需要的話可以覆寫。
?
代碼示例
? 寫了一個Demo觀察輸出和調用關系,代碼如下:
?
package com.mengdd.hellotouch;import android.util.Log; import android.view.MotionEvent;public class Utils {public static void showMotionEventType(MotionEvent event, String logTag,String methodName) {final int action = event.getActionMasked();switch (action) {case MotionEvent.ACTION_DOWN:Log.i(logTag, methodName + ": " + action + ": ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:Log.i(logTag, methodName + ": " + action + ": ACTION_MOVE");break;case MotionEvent.ACTION_UP:Log.i(logTag, methodName + ": " + action + ": ACTION_UP");break;case MotionEvent.ACTION_CANCEL:Log.i(logTag, methodName + ": " + action + ": ACTION_CANCEL");break;default:break;}}public static void showReturnValue(boolean returnValue, String logTag,String methodName) {Log.w(logTag, methodName + " return: " + returnValue);}public static void showInfo(String info, String logTag, String methodName) {Log.e(logTag, methodName + " info: " + info);} } Utils?
package com.mengdd.hellotouch;import com.example.helloscroller.R;import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent;public class HelloTouchActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_hello_scroller);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {Utils.showMotionEventType(ev, "Activity", "dispatchTouchEvent");boolean result = super.dispatchTouchEvent(ev);Utils.showReturnValue(result, "Activity", "dispatchTouchEvent");return result;}@Overridepublic boolean onTouchEvent(MotionEvent event) {Utils.showMotionEventType(event, "Activity", "onTouchEvent");boolean result = super.onTouchEvent(event);Utils.showReturnValue(result, "Activity", "onTouchEvent");return result;}} Activity?
package com.mengdd.hellotouch;import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout;public class MyViewGroup extends LinearLayout {public MyViewGroup(Context context, AttributeSet attrs) {super(context, attrs);init();}public MyViewGroup(Context context) {super(context);init();}private void init() {setOnClickListener(mOnClickListener);setOnLongClickListener(mOnLongClickListener);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent event) {Utils.showMotionEventType(event, "ViewGroup", "onInterceptTouchEvent");boolean returnValue = super.onInterceptTouchEvent(event);// This method JUST determines whether we want to intercept the motion.// If we return true, onTouchEvent will be called Utils.showReturnValue(returnValue, "ViewGroup", "onInterceptTouchEvent");return returnValue;}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {Utils.showMotionEventType(event, "ViewGroup", "dispatchTouchEvent");boolean returnValue = super.dispatchTouchEvent(event);Utils.showReturnValue(returnValue, "ViewGroup", "dispatchTouchEvent");return returnValue;}// ViewGroup自己的Touch事件處理,如果在onInterceptTouchEvent返回true,則會到這里處理,不傳入child @Overridepublic boolean onTouchEvent(MotionEvent event) {Utils.showMotionEventType(event, "ViewGroup", "onTouchEvent");boolean returnValue = super.onTouchEvent(event);Utils.showReturnValue(returnValue, "ViewGroup", "onTouchEvent");return returnValue;}private OnClickListener mOnClickListener = new OnClickListener() {@Overridepublic void onClick(View v) {Utils.showInfo("onClick", "ViewGroup", "mOnClickListener");// onClick是ACTION_UP后調用的 }};private OnLongClickListener mOnLongClickListener = new OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {// onLongClick按下到一定的時間就調用了Utils.showInfo("onLongClick", "ViewGroup", "mOnLongClickListener");// 如果返回false,則長按結束的ACTION_UP調用onClick// 如果返回true,onLongClick后不再調用onClickreturn true;}};} ViewGroup package com.mengdd.hellotouch;import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.TextView;public class MyView extends TextView {public MyView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}public MyView(Context context, AttributeSet attrs) {super(context, attrs);init();}public MyView(Context context) {super(context);}private void init() {setOnClickListener(mOnClickListener);setOnLongClickListener(mOnLongClickListener);}@Overridepublic boolean dispatchTouchEvent(MotionEvent event) {Utils.showMotionEventType(event, "View", "dispatchTouchEvent");boolean returnValue = super.dispatchTouchEvent(event);Utils.showReturnValue(returnValue, "View", "dispatchTouchEvent");return returnValue;}@Overridepublic boolean onTouchEvent(MotionEvent event) {Utils.showMotionEventType(event, "View", "onTouchEvent");boolean returnValue = super.onTouchEvent(event);Utils.showReturnValue(returnValue, "View", "onTouchEvent");return returnValue;}private OnClickListener mOnClickListener = new OnClickListener() {@Overridepublic void onClick(View v) {Utils.showInfo("onClick", "View", "mOnClickListener");}};private OnLongClickListener mOnLongClickListener = new OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {Utils.showInfo("onLongClick", "View", "mOnLongClickListener");// 如果返回false,則長按結束的ACTION_UP調用onClickreturn false;}};} View <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context=".HelloTouchActivity" ><com.mengdd.hellotouch.MyViewGroupandroid:layout_width="match_parent"android:layout_height="match_parent" android:orientation="vertical"><com.mengdd.hellotouch.MyViewandroid:layout_width="100dp"android:layout_height="100dp"android:layout_gravity="center"android:text="Hello"android:background="#FFBBFFBB" /></com.mengdd.hellotouch.MyViewGroup></RelativeLayout> layout
?
Click事件處理
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。
?
參考資料
本博客中:
Android 觸摸手勢基礎 官方文檔概覽
http://www.cnblogs.com/mengdd/p/3335508.html
Android的Touch事件處理機制
http://www.cnblogs.com/frydsh/archive/2012/11/08/2760408.html?
Android FrameWork——Touch事件派發過程詳解
http://blog.csdn.net/stonecao/article/details/6759189
Android事件傳遞機制【Touch事件】
http://orgcent.com/android-touch-event-mechanism/
Android 編程下 Touch 事件的分發和消費機制
http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html
?
轉載于:https://www.cnblogs.com/mengdd/p/3394345.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Android中的Touch事件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MSP430单片机输入与输出
- 下一篇: dede整站动态化或是整站静态化方法