Android 触屏事件 OnTouch onClick onTouchEvent对于触屏事件的处理和分发

来源:互联网 发布:淘宝网宠物用品店 编辑:程序博客网 时间:2024/04/28 12:51

Android 触屏事件 OnTouch onClick onTouchEvent对于触屏事件的处理和分发 


           做项目的时候经常遇到需要事件分发,很多时候我们发现当我们触发了onTouch却触发不了onClick。或者触发了View的事件却触发不了ViewGroup的事件。那么他们之间到底是什么关系呢,其实最终他们涉及的只是两个问题

         OnTouch 、onClick 、onTouchEvent 之间的关系
         OnTouch 、onClick 、onTouchEvent 之间的处理顺序

这里,我做了简单的例子来看看他们之间的关系。

<FrameLayout 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:background="#FFFFFF">    <com.example.empty.MyFrameLayout        android:id="@+id/mFrame"        android:layout_width="300dp"        android:layout_gravity="center"        android:layout_height="300dp"        android:background="#00FFFF" >        <com.example.empty.MyImageView            android:id="@+id/mImage"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/image_128"            android:background="#00FF00"            android:layout_gravity="center" />    </com.example.empty.MyFrameLayout></FrameLayout>

package com.example.empty;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.view.View.OnTouchListener;import android.view.MotionEvent;import android.widget.FrameLayout;public class MyFrameLayout extends FrameLayout implements OnClickListener,OnTouchListener{    private static final String TAG = "Event";    public MyFrameLayout(Context context, AttributeSet attrs) {        super(context, attrs);        // TODO Auto-generated constructor stub        Log.d(TAG,"MyFrameLayout init");        setOnClickListener(this);        setOnTouchListener(this);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.d(TAG,"MyFrameLayout dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }        @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d(TAG,"MyFrameLayout onTouchEvent");        return super.onTouchEvent(event);    }    @Override    public void onClick(View view) {        // TODO Auto-generated method stub        Log.d(TAG,"MyFrameLayout onClick");    }    @Override    public boolean onTouch(View view, MotionEvent event) {        // TODO Auto-generated method stub        Log.d(TAG,"MyFrameLayout onTouch");        return false;    }}

package com.example.empty;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.View.OnClickListener;import android.view.View.OnTouchListener;import android.widget.ImageView;public class MyImageView extends ImageView implements OnClickListener,OnTouchListener{    private static final String TAG = "Event";    public MyImageView(Context context, AttributeSet attrs) {        super(context, attrs);        // TODO Auto-generated constructor stub        Log.d(TAG,"MyImageView init");        setOnClickListener(this);        setOnTouchListener(this);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        Log.d(TAG,"MyImageView dispatchTouchEvent");        return super.dispatchTouchEvent(event);    }        @Override    public boolean onTouchEvent(MotionEvent event) {        Log.d(TAG,"MyImageView onTouchEvent");        return super.onTouchEvent(event);    }        @Override    public boolean onTouch(View arg0, MotionEvent arg1) {        // TODO Auto-generated method stub        Log.d(TAG,"MyImageView onTouch");        return false;    }    @Override    public void onClick(View arg0) {        // TODO Auto-generated method stub        Log.d(TAG,"MyImageView onClick");    }}

例子很简单,下面我们让Log来告诉我们一些东西
当我们点击我们ImageView时

当我们再点击我们FrameLayout

从这个Log 我们可以得出2点结论
一、执行顺序来讲
dispatchTouchEvent > onTouch > onTouchEvent > onClick
二、事件分发顺序
点击图片时  我们触发了而且还是首先触发了FrameLayout的dispatchTouchEvent 

这里我们知道在Android中我们所有的控件都源自View甚至
public abstract class ViewGroup extends View

所以从底层来看我们的所有事件最终都是交给我们的View
目录(源码目录/frameworks/base\core\java\android\view/View.java)
public class View implements Drawable.Callback, KeyEvent.Callback,        AccessibilityEventSource {        ......     public interface OnClickListener {        /**         * Called when a view has been clicked.         *         * @param v The view that was clicked.         */        void onClick(View v);    }        ......    public interface OnTouchListener {        /**         * Called when a touch event is dispatched to a view. This allows listeners to         * get a chance to respond before the target view.         *         * @param v The view the touch event has been dispatched to.         * @param event The MotionEvent object containing full information about         *        the event.         * @return True if the listener has consumed the event, false otherwise.         */        boolean onTouch(View v, MotionEvent event);    }    ......    public boolean dispatchTouchEvent(MotionEvent event) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        if (DBG_MOTION) {            Xlog.d(VIEW_LOG_TAG, "(View)dispatchTouchEvent: event = " + event + ",this = " + this);        }        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo 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;    }    /**     * Filter the touch event to apply security policies.     *     * @param event The motion event to be filtered.     * @return True if the event should be dispatched, false if the event should be dropped.     *     * @see #getFilterTouchesWhenObscured     */    public boolean onFilterTouchEventForSecurity(MotionEvent event) {        //noinspection RedundantIfStatement        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {            // Window is obscured, drop this touch.            return false;        }        return true;    }    .....    public boolean onTouchEvent(MotionEvent event) {        final int viewFlags = mViewFlags;        if ((viewFlags & ENABLED_MASK) == DISABLED) {            /// M: we need to reset the pressed state or remove prepressed callback either up or cancel event happens.            final int action = event.getAction();            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {                if ((mPrivateFlags & PFLAG_PRESSED) != 0) {                    setPressed(false);                } else if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {                    Xlog.d(VIEW_LOG_TAG, "View onTouch event, if view is DISABLED & PFLAG_PREPRESSED, remove callback mPrivateFlags = "                                + mPrivateFlags + ", this = " + this);                    removeTapCallback();                }            }            // 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 (DBG_MOTION) {                        Xlog.d(VIEW_LOG_TAG, "(View)Touch up: prepressed = " + prepressed + ",this = " + this);                    }                    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();                    if (DBG_MOTION) {                        Xlog.d(VIEW_LOG_TAG, "(View)Touch down: isInScrollingContainer = "                                + isInScrollingContainer + ",this = " + this);                    }                    // 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:                    if (DBG_MOTION) {                        Xlog.d(VIEW_LOG_TAG, "(View)Touch cancel: this = " + this);                    }                    setPressed(false);                    removeTapCallback();                    removeLongPressCallback();                    break;                case MotionEvent.ACTION_MOVE:                    final int x = (int) event.getX();                    final int y = (int) event.getY();                    if (DBG_MOTION) {                        Xlog.d(VIEW_LOG_TAG, "(View)Touch move: x = " + x + ",y = " + y                                + ",mTouchSlop = " + mTouchSlop + ",this = " + this);                    }                    // 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;    }        }        

有了源码,其他自然一目了然。在这里我们发现onTouchListener 和onClickListener接口
很明显他们事件确定之后的回调(我自定义的View都实现了这两个接口)。
那么很明显他们和我们dispatchTouchEvent不会具有可比性
那么接下来我们把目光对准dispatchTouchEvent
   public boolean dispatchTouchEvent(MotionEvent event) {        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        if (DBG_MOTION) {            Xlog.d(VIEW_LOG_TAG, "(View)dispatchTouchEvent: event = " + event + ",this = " + this);        }        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo 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;    }    /**     * Filter the touch event to apply security policies.     *     * @param event The motion event to be filtered.     * @return True if the event should be dispatched, false if the event should be dropped.     *     * @see #getFilterTouchesWhenObscured     */    public boolean onFilterTouchEventForSecurity(MotionEvent event) {        //noinspection RedundantIfStatement        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {            // Window is obscured, drop this touch.            return false;        }        return true;    }
这里重点说明一下onFilterTouchEventForSecurity这个函数
它通过判断我们的窗口Window是否被遮蔽来判断是否舍弃本次事件
所以这就是为什么我们点击我们的ImageView时我们的FrameLayout也执行了dispatchTouchEvent但是却没有向下走了
那么我们继续回归dispatchTouchEvent中不是ViewGroup的情形
接下来,系统会自动判断我们是否实现了onTouchListener 这里就开始有分支了
当我们实现了onTouchListener
那么下一步我们的事件叫交给了onTouchListener .onTouch来处理
这里就又开始了分支
如果我们在onTouch中返回了true,那么就表明我们的onTouchListener 已经消化掉了本次的事件,本次事件完结。这就是为什么我们在onTouch中返回去就永运不会执行onClick,onLongClick了
如果我们在onTouch中返回了false,那么很明显了我们的事件就会被onTouchEvent处理
同理,当我们没有实现了onTouchListener,很明显了我们的事件就会被onTouchEvent处理
殊途同归,最终如果我们的事件没有被干掉,最终都交给了onTouchEvent
那么接下来我们继续来看onTouchEvent
那么我们的onTouchEvent又是用来干什么的呢(这里既然已经有onTouchListener了,他们似乎一模一样啊)
其实不然,说白了我们的onTouchEvent最终会用来分发onClick和onLongClick事件
如果你个人在这里还是不能理解onTouchEvent函数
推荐阅读
Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法
希望对你有帮助
这里我需要提到的是注意onTouchEvent对于View的State的判断和处理

2 0
原创粉丝点击