package com.example.toucheventdemo; import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; public class MainActivity extends Activity { private CustomButton mButton_top; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton_top = (CustomButton) this.findViewById(R.id.cusbutton_top); mButton_top.setOnClickListener(this); mButton_top.setOnTouchListener(this); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("MainActivity--dispatchTouchEvent:" + "---MotionEvent.ACTION_DOWN---"); break; case MotionEvent.ACTION_MOVE: System.out.println("MainActivity--dispatchTouchEvent:" + "---MotionEvent.ACTION_MOVE---"); break; case MotionEvent.ACTION_UP: System.out.println("MainActivity--dispatchTouchEvent:" + "---MotionEvent.ACTION_UP---"); break; default: break; } return super.dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: System.out.println("MainActivity--onTouchEvent:" + "---MotionEvent.ACTION_DOWN---"); break; case MotionEvent.ACTION_MOVE: System.out.println("MainActivity--onTouchEvent:" + "---MotionEvent.ACTION_MOVE---"); break; case MotionEvent.ACTION_UP: System.out.println("MainActivity--onTouchEvent:" + "---MotionEvent.ACTION_UP---"); break; default: break; } return super.onTouchEvent(event); } }xml布局文件为空,不添加控件,运行程序,点击屏幕,LogCat输出如下:
/** * 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); }从源码中可以看到,dispatchTouchEvent方法只处理了ACTIONDOWN事件,前面提到过,所有的事件都是以按下为起点的,所以,Android认为当ACTIONDOWN事件没有执行时,后面的事件都是没有意义的,所以这里首先判断ACTION_DOWN事件。如果事件成立,则调用了onUserInteraction方法,代码如下:
public void onUserInteraction() {}可以看到该方法是一个空方法,所以其实可以在Activity中被重写,在事件被分发前会调用该方法。该方法的返回值是void型,不会对事件传递结果造成影响,接着会判断getWindow().superDispatchTouchEvent(ev)的执行结果,看看它的源码:
/** * Used by custom windows, such as Dialog, to pass the touch screen event * further down the view hierarchy. Application developers should * not need to implement or call this. * */ public abstract boolean superDispatchTouchEvent(MotionEvent event);通过源码注释我们可以了解到这是个抽象方法,用于自定义的Window,例如自定义Dialog传递触屏事件,并且提到开发者不需要去实现或调用该方法,系统会完成,如果我们在MainActivity中将dispatchTouchEvent方法的返回值设为true,那么这里的执行结果就为true,从而不会返回执行onTouchEvent(ev),如果这里返回false,那么最终会返回执行onTouchEvent方法,由此可知,接下来要调用的就是onTouchEvent方法了。这也和打印输出的log一致。
System.out.println( "super.dispatchTouchEvent(ev):"+ super.dispatchTouchEvent(ev)+"" );log输出如下:
package com.example.toucheventdemo; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.Button; public class CustomButton extends Button { public CustomButton(Context context) { super(context); // TODO Auto-generated constructor stub } public CustomButton(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } public CustomButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } @Override public boolean dispatchTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i("CustomButton--dispatchTouchEvent", "MotionEvent.ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i("CustomButton--dispatchTouchEvent", "MotionEvent.ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i("CustomButton--dispatchTouchEvent", "MotionEvent.ACTION_UP"); break; default: break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i("CustomButton--onTouchEvent", "MotionEvent.ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i("CustomButton--onTouchEvent", "MotionEvent.ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i("CustomButton--onTouchEvent", "MotionEvent.ACTION_UP"); break; default: break; } return super.onTouchEvent(event); } }在Button里面,重写了 dispatchTouchEvent(MotionEvent event) 以及onTouchEvent(MotionEvent event)方法,并在里面加入了日志输出。运行程序,点击自定义的Button,输出如下(感觉Android Log输出比java System方便观看,之后使用Log):
package com.example.toucheventdemo; import android.os.Bundle; import android.app.Activity; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnTouchListener; public class MainActivity extends Activity implements OnClickListener, OnTouchListener { private CustomButton mButton_top ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout. activity_main); mButton_top = (CustomButton) this.findViewById(R.id.cusbutton_top); mButton_top.setOnClickListener(this); mButton_top.setOnTouchListener(this); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log. i("MainActivity--dispatchTouchEvent", "MotionEvent.ACTION_DOWN" ); break; case MotionEvent.ACTION_MOVE: Log. i("MainActivity--dispatchTouchEvent", "MotionEvent.ACTION_MOVE" ); break; case MotionEvent.ACTION_UP: Log. i("MainActivity--dispatchTouchEvent", "MotionEvent.ACTION_UP" ); break; default: break; } return super .dispatchTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log. i("MainActivity--onTouchEvent", "MotionEvent.ACTION_DOWN" ); break; case MotionEvent.ACTION_MOVE: Log. i("MainActivity--onTouchEvent", "MotionEvent.ACTION_MOVE" ); break; case MotionEvent.ACTION_UP: Log. i("MainActivity--onTouchEvent", "MotionEvent.ACTION_UP"); break; default: break; } return super .onTouchEvent(event); } @Override public boolean onTouch(View v, MotionEvent event) { switch (v.getId()) { case R.id.cusbutton_top : switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log. i("MainActivity--onTouch", "MotionEvent.ACTION_DOWN" ); break; case MotionEvent.ACTION_MOVE: Log. i("MainActivity--onTouch", "MotionEvent.ACTION_MOVE" ); break; case MotionEvent.ACTION_UP: Log. i("MainActivity--onTouch", "MotionEvent.ACTION_UP" ); break; default: break; } break; default: break; } return false ; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.cusbutton_top : Log. i("MainActivity--onClick", "clicked"); break; default: break; } } }可以看到,在activity里面,也加入了onTouch以及onClick方法,然后运行程序,点击button,log输出如下:
/** * 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 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; }挑选关键代码进行分析,可以看到这里有几个条件,当几个条件都满足时该方法就返回true,当条件li.mOnTouchListener不为空时,通过在源码中查找,发现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; }这个方法就已经很熟悉了,就是在MainActivity.java中为CustomButton设置的onTouchListener,条件(mViewFlags & ENABLED_MASK) == ENABLED判断的是当前View是否是ENABLE的,默认都是ENABLE状态的。接着就是li.mOnTouchListener.onTouch(this, event)条件,这里调用了onTouch方法,该方法的调用就是在MainActivity.java中为CustomButton设置的监听回调,如果该方法返回true,则整个条件都满足,dispatchTouchEvent就返回true,表示该事件就不继续向下分发了,因为已经被onTouch消费了。如果onTouch返回的是false,则这个判断条件不成立,接着执行onTouchEvent(event)方法进行判断,如果该方法返回true,表示事件被onTouchEvent处理了,则整个事件分发dispatchTouchEvent就返回true。到目前为止,ACTION_DOWN的事件经过了从Activity到CustomButton的分发,然后经过onTouch和onTouchEvent的处理,最终,ACTION_DOWN事件交给了CustomButton得onTouchEvent进行处理。从屏幕抬起时,会发生ACTION_UP事件。从之前输出的日志中可以看到,ACTION_UP事件同样从Activity开始到CustomButton进行分发和处理,最后,由于注册了onClick事件,当onTouchEvent执行完毕后,就调用了onClick事件,那么onClick是在哪里被调用的呢?继续回到View.java的源代码中寻找。由于onTouchEvent在View.java中的源码比较长,这里贴重点,通过源码阅读,在ACTION_UP的处理分支中可以看到一个performClick()方法,从这个方法的源码中可以看到执行了哪些操作。
/** * Call this view‘s OnClickListener, if it is defined. Performs all normal * actions associated with clicking: reporting accessibility event, playing * a sound, etc. * * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. */ public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); return true; } return false; }在if分支里可以看到执行了li.mOnClickListener.onClick(this);这句代码,这里就执行了CustomButton实现的onClick方法,onClick是在onTouchEvent中被执行的,并且,onClick要后于onTouch的执行。
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }该方法的实现很简单,只返回了一个false。这说明在默认情况下,这个方法是不会拦截消息的。这个方法的存在也是容器控件和显示控件(如TextView、Button、ImageView等)的一个重要区别。容器控件代码如下:
package com.example.toucheventdemo; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.widget.RelativeLayout; public class CustomRelativeLayout extends RelativeLayout { public CustomRelativeLayout(Context context) { super(context); } public CustomRelativeLayout(Context context, AttributeSet attrs) { super(context, attrs); } public CustomRelativeLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i("CustomRelativeLayout--dispatchTouchEvent", "MotionEvent.ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i("CustomRelativeLayout--dispatchTouchEvent", "MotionEvent.ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i("CustomRelativeLayout--dispatchTouchEvent", "MotionEvent.ACTION_UP"); break; default: break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i("CustomRelativeLayout--onInterceptTouchEvent", "MotionEvent.ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i("CustomRelativeLayout--onInterceptTouchEvent", "MotionEvent.ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i("CustomRelativeLayout--onInterceptTouchEvent", "MotionEvent.ACTION_UP"); break; default: break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i("CustomRelativeLayout--onTouchEvent", "MotionEvent.ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i("CustomRelativeLayout--onTouchEvent", "MotionEvent.ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i("CustomRelativeLayout--onTouchEvent", "MotionEvent.ACTION_UP"); break; default: break; } return super.onTouchEvent(event); } }xml布局文件如下:
<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= ".MainActivity" > <com.example.toucheventdemo.CustomButton android:id="@+id/cusbutton_top" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_margin="10dp" android:background="@android:color/holo_red_dark" android:text="CustomButton" /> <com.example.toucheventdemo.CustomRelativeLayout android:id="@+id/layout_rl2" android:layout_width="300dp" android:layout_height="300dp" android:layout_below="@id/cusbutton_top" android:layout_centerHorizontal="true" android:background="@android:color/holo_green_dark" android:orientation="vertical" > <com.example.toucheventdemo.CustomButton android:id="@+id/cusbutton_middle1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_margin="10dp" android:background="@android:color/holo_red_dark" android:text="CustomButton1" /> <com.example.toucheventdemo.CustomButton android:id="@+id/cusbutton_middle2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/cusbutton_middle1" android:layout_centerHorizontal="true" android:layout_margin="10dp" android:background="@android:color/holo_red_dark" android:padding="10dp" android:text="CustomButton2" /> <com.example.toucheventdemo.CustomLinearLayout android:id="@+id/layout_rl3" android:layout_width="100dp" android:layout_height="100dp" android:layout_below="@id/cusbutton_middle2" android:layout_centerHorizontal="true" android:background="@android:color/holo_blue_bright" android:orientation="vertical" > <com.example.toucheventdemo.CustomButton android:id="@+id/cusbutton_middle3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/cusbutton_middle1" android:layout_centerHorizontal="true" android:layout_margin="10dp" android:background="@android:color/holo_red_dark" android:text="CustomButton3" /> </com.example.toucheventdemo.CustomLinearLayout> </com.example.toucheventdemo.CustomRelativeLayout > </RelativeLayout>运行程序界面如下:
// 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; }从这部分代码中可以看到onInterceptTouchEvent调用后返回值被赋值给intercepted,该变量控制了事件是否要向其子控件分发,所以它起到拦截的作用,如果onInterceptTouchEvent返回false则不拦截,如果返回true则拦截当前事件。之前介绍过,dispatchTouchEvent()用于事件的分发,onInterceptTouchEvent()用于事件的拦截,onTouchEvent()用于事件的处理。这好比一条河流,在源头处分发水源,河流两侧有水闸,在不需要的地方进行拦截,在需要的地方进行处理。如果把onInterceptTouchEvent()返回值改为true,也就是消费了消息,按照经验应该是在CustomRelativeLayout里面不会传递到onTouch()方法,而是直接返回到Activity的onTouch()方法处理。看结果如何:
Touch 事件相关方法 | 方法功能 | ViewGroup | View | Activity |
public boolean dispatchTouchEvent(MotionEvent ev) | 事件分发 | Yes | Yes | Yes |
public boolean onInterceptTouchEvent(MotionEvent ev) | 事件拦截 | Yes | No | No |
public boolean onTouchEvent(MotionEvent ev) | 事件处理 | Yes | Yes | Yes |
@Override public boolean dispatchTouchEvent(MotionEvent event) { if (_flipDetector.onTouchEvent(event)) { event.setAction(MotionEvent.ACTION_CANCEL); } return super.dispatchTouchEvent(event); }于是效果实现。也就是在分发之前便进行手势检测处理,若检测成功,则取消下层的一切处理过程。
Android 中Touch(触屏)事件传递机制,布布扣,bubuko.com
原文:http://blog.csdn.net/wangjinyu501/article/details/22584465