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