大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
- 按下(ACTION_DOWN) //表示用户按下了屏幕
- 移动(ACTION_MOVE) //表示用户在屏幕移动
- 抬起(ACTION_UP) //表示用户离开屏幕
- 取消手势(ACTION_CANCEL) //表示,不会由用户产生,而是由程序产生的
- 划出屏幕(ACTION_OUTSIDE)//表示滑出屏幕了
2、 public boolean onTouchEvent(MotionEvent event)
3、 public boolean onInterceptTouchEvent(MotionEvent event)
在View和ViewGroup中都存在dispatchTouchEvent和onTouchEvent方法,特别的,在ViewGroup中还有一个onInterceptTouchEvent方法。这些方法的返回值全部都是boolean型,都返回true或者是false,这是因为事件传递的过程就是一个接一个,某一个点后根据方法boolean的返回值判断是否要继续往下传递。在Android中,所有的事件都是从开始经过传递到完成事件的消费,这些方法的返回值就决定了某一事件是否是继续往下传,还是被拦截了,或是被消费了。
- dispatchTouchEvent方法用于事件的分发,Android中所有的事件都必须经过这个方法的分发,然后决定是自身消费当前事件还是继续往下分发给子控件处理。返回true表示不继续分发,事件没有被消费。返回false则继续往下分发,如果是ViewGroup则分发给onInterceptTouchEvent进行判断是否拦截该事件。
- onInterceptTouchEvent是ViewGroup中才有的方法,View中没有,它的作用是负责事件的拦截,返回true的时候表示拦截当前事件,不继续往下分发,交给自身的onTouchEvent进行处理。返回false则不拦截,继续往下传。这是ViewGroup特有的方法,因为ViewGroup中可能还有子View,而在Android中View中是不能再包含子View的。
- onTouchEvent方法用于事件的处理,返回true表示消费处理当前事件,返回false则不处理,交给子控件进行继续分发。Android中事件的构成以及事件处理方法的基本概念介绍到这,接下来就通过一系列的测试来验证以及梳理总结。
测试的时候,分为以下几种情况,不同的情况下事件的传递机制是不一样的,但是事件传递原理都一样,所以不要混淆。
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输出如下:
流程图如下:
Activity.java
/**
* 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一致。
流程图如下:
可以看到,触屏消息已经不会传递到OnTouchEvent(MotionEvent event)方法里面了,因为
dispatchTouchEvent方法返回
true,说明消息不会继续分发到onTouchEvent()方法
。然后在这个两个方法的返回之前加入输出,也就是把父类方法的返回值打印出来,
System.out.println( "super.dispatchTouchEvent(ev):"+ super.dispatchTouchEvent(ev)+"" );
log输出如下:
这说明默认情况下,如果消息没有被消费(处理),会返回false,接着事件会向下传递。如果最后也没有被处理消费,消息会向上返回回去,直到完成一个传递的过程。
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):
ACTION_DOWN以及ACTION_UP事件传递流程图如下:
重点是第三个矩形,这个是在把CustomButton中的onTouchEvent返回改为false,也就是button不消费点击事件。那么会由Activity的onTouchEvent方法接受处理,以后消息也不会再由CustomButton处理了,而是由activity来处理,注意看和第一个矩形的区别。
当把CustomButton中的onTouchEvent返回改为true的情况下,其实是和默认情况下一样的,这也说明默认情况下button消费了点击事件。log输出如下:
紧接着,把对onTouchEvent的更改取消,把CustomButton的dispatchTouchEvent方法的返回值设为true,也就是让消息到这里就不往下传递了。log输出如下:
可以看到,CustomButton的dispatchTouchEvent方法已经把消息处理了,然后返回到activity里处理。下面再把activity的dispatchTouchEvent方法返回值改为true,不难猜出结果是消息被activity处理了,看log输出:
结果确实也是这样。截止到这里先进行一个总结,在消息传递的过程中,首先由Activity的dispatchTouchEvent方法进行事件分发,如果返回值为true,则消息不往下分发,之后只由activity处理。如果返回值为false,则消息往下传递,传递到view 视图里面,再由容器或者控件来处理。具体流程如下图所示:
在开发中,经常会在Activity中设置setOnTouchListener或者setOnClickListener,下面就在MainActivity里面添加。
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输出如下:
到CustomButton中的dispatchTouchEvent看看View中的源码是如何处理的。
/**
* 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()方法处理。看结果如何:
虽然情况复杂了一点,但无非也就是多了一个容器控件的消息的判断与传递的过程。
onTouch事件要先于onClick事件执行,onTouch在事件分发方法dispatchTouchEvent中调用,而onClick在事件处理方法onTouchEvent中被调用,onTouchEvent要后于dispatchTouchEvent方法的调用。其他控件事件处理过程同上。另外如果控件是clickable 表示其能处理Touch事件。
- 隧道方式:从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递。
- 冒泡方式:从最内层子元素依次往外传递直到根元素或在中间某一元素中由于某一条件停止传递。
android对Touch Event的分发逻辑是View从上层分发到下层(dispatchTouchEvent函数)类似于隧道方式,然后下层优先开始处理Event(先mOnTouchListener,再onTouchEvent)并向上返回处理情况(boolean值),若返回true,则上层不再处理,类似于冒泡方式。
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
|
Touch 事件发生时 Activity 的 dispatchTouchEvent(MotionEvent ev) 方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法,并由该 View 的 dispatchTouchEvent(MotionEvent ev) 方法对事件进行分发。dispatchTouchEvent 的事件分发逻辑如下:
- 如果 return true,事件会分发给当前 View 并由 dispatchTouchEvent 方法进行消费,同时事件会停止向下传递;
- 如果 return false,事件分发分为两种情况:
- 如果当前 View 获取的事件直接来自 Activity,则会将事件返回给 Activity 的 onTouchEvent 进行消费;
- 如果当前 View 获取的事件来自外层父控件,则会将事件返回给父 View 的 onTouchEvent 进行消费。
如果返回系统默认的 super.dispatchTouchEvent(ev),事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。
事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
在外层 View 的 dispatchTouchEvent(MotionEvent ev) 方法返回系统默认的 super.dispatchTouchEvent(ev) 情况下,事件会自动的分发给当前 View 的 onInterceptTouchEvent 方法。onInterceptTouchEvent 的事件拦截逻辑如下:
- 如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由当前 View 的 onTouchEvent 进行处理;
- 如果 onInterceptTouchEvent 返回 false,则表示将事件放行,当前 View 上的事件会被传递到子 View 上,再由子 View 的 dispatchTouchEvent 来开始这个事件的分发;
- 如果 onInterceptTouchEvent 返回 super.onInterceptTouchEvent(ev),事件默认不会被拦截。
在 dispatchTouchEvent 返回 super.dispatchTouchEvent(ev) 并且 onInterceptTouchEvent 返回 true 或返回 super.onInterceptTouchEvent(ev) 的情况下 onTouchEvent 会被调用。onTouchEvent 的事件响应逻辑如下:
- 如果事件传递到当前 View 的 onTouchEvent 方法,而该方法返回了 false,那么这个事件会从当前 View 向上传递,并且都是由上层 View 的 onTouchEvent 来接收,如果传递到上面的 onTouchEvent 也返回 false,这个事件就会“消失”,而且接收不到下一次事件。
- 如果返回了 true 则会接收并消费该事件。
- 如果返回 super.onTouchEvent(ev) 默认处理事件的逻辑和返回 false 时相同。
难题:ViewFlipper的flip手势检测需要的MotionEvent会被各种子View的触摸检测给拦截了。比如界面上有一个Button,则当手指按下Button(还没有抬起)然后flip出Button,则最上层的flip手势检测无效。
原因:android对Touch Event的分发逻辑是View从上层分发到下层(dispatchTouchEvent函数),然后下层优先开始处理Event(先mOnTouchListener,再onTouchEvent)并向上返回处理情况(boolean值),若返回true,则上层不再处理。
于是难题出现了,你若把Touch Event都想办法给传到上层了(只能通过返回false来传到上层),那么下层的各种子View就不能处理后续事件了。
解决方案:
开始仅着眼于Touch Event处理完后的回传过程,想了N久不得,毕竟我想实现的是一个需要打破android事件处理逻辑的效果(就是一个连续性操作,只有不满足上层要求时,才轮到下层处理)。然后突然想到事件的分发过程,便豁然开朗:
覆写最上层的View的dispatchTouchEvent函数,代码如下:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (_flipDetector.onTouchEvent(event)) {
event.setAction(MotionEvent.ACTION_CANCEL);
}
return super.dispatchTouchEvent(event);
}
于是效果实现。也就是在分发之前便进行手势检测处理,若检测成功,则取消下层的一切处理过程。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/195267.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...