我们在日常开发中,会经常遇到嵌套滑动视图,比如ViewPager中fragment加入横向滚动的banner控件,如果不做处理就会导致banner控件滑动失效,所以要深入了解android触摸事件的传递机制就能友好的处理好这类问题。

1.1 触摸事件的类型

触摸事件对应的是MotionEvent类,类型主要有如下三种:

● ACTION_DOWN:

用户手指按下操作,标志着触摸事件的开始。

● ACTION_MOVE:

在用户触发"ACTION_DOWN"之后且在松开手指之前,这期间手指的移动距离超过一定的阀值就会被系统判定为 ACTION_MOVE。

● ACTION_UP:

用户手指离开屏幕的操作,标志着触摸事件的结束;

正常的普通操作情况下,每次触摸事件的结束,ACTION_DOWN及ACTION_UP都会形成一个闭环。既是一定会有这两个类型事件;而MOVE则视情况而定,比如用户只是操作一个点击操作,手指点下去抬起来,中间移动距离未超过系统判断的阀值,便不会触发MOVE事件。

1.2 事件的传递

● 事件分发(Dispatch):事件分发对应着dispatchTouchEvent方法,方法原型如下:

public boolean dispatchTouchEvent(MotionEvent ev)

如果这里返回ture则表示事件被当前视图消费掉,不再分发事件;如果返回值为super.dispatchTouchEvent则表示继续分发该事件。(由系统去判断有没有必要继续分发)如果当前视图是ViewGroup及其子类,则调用onInterceptTouchEvent方法判定是否进行事件拦截。

● 事件拦截(Intercept):事件的拦截对应着onInterceptTouchEvent 方法,这个方法只在ViewGroup及其子类存在,在View和Activity中是不存在的。方法原型如下:

public boolean onInterceptTouchEvent(MotionEvent ev);

如果返回ture则表示拦截当前事件,不继续分发给子视图,同时交由自身onTouchEvent方法进行消费;返回false或者super.onInterceptTouchEvent表示不对事件进行拦截,继续传递给子视图。

● 事件消费(Consume):事件的消费对应着onTouchEvent方法,方法原型如下:

public boolean onTouchEvent(MotionEvent ev)

这里返回true则表示当前视图可以处理对应事件,事件将不会向上传递给父视图;返回false则表示当前视图不处理,事件会被传递给父视图的onTouchEvent 方法进行处理。

在android 系统中,拥有事件传递处理能力的类有三种:

1.Activity: dispatchTouchEvent、onTouchEvent 两个方法;

2.ViewGroup:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent 三个方法;

3.View :dispatchTouchEvent、onTouchEvent 两个方法;

1.3 View的事件传递机制

直接上代码:

//自定义MyTextView
public class MyTextView extends TextView {
private static final String TAG = "MyTextView";
public MyTextView(Context context) {
super(context);
}
public MyTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent: MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent: MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent: MotionEvent.ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent: MotionEvent.ACTION_CANCEL");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_CANCEL");
break;
}
return super.onTouchEvent(event);
}
}
Activity里的内容:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
private static final String TAG = "MainActivity ";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_motion_event);
MyTextView textView = findViewById(R.id.myTv);
textView.setOnClickListener(this);
textView.setOnTouchListener(this);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent: MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent: MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent: MotionEvent.ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent: MotionEvent.ACTION_CANCEL");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_CANCEL");
break;
}
return super.onTouchEvent(event);
}
@Override
public void onClick(View v) {
Log.e(TAG, "onClick: MyTextView" );
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (v.getId()){
case R.id.myTv:
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent: MotionEvent.ACTION_CANCEL");
break;
}
break;
}
return false;
}
}
logcat打印:
E/MainActivity: dispatchTouchEvent: MotionEvent.ACTION_DOWN
E/MyTextView: dispatchTouchEvent: MotionEvent.ACTION_DOWN
E/MainActivity: onTouchEvent: MotionEvent.ACTION_DOWN
E/MyTextView: onTouchEvent: MotionEvent.ACTION_DOWN
E/MainActivity: dispatchTouchEvent: MotionEvent.ACTION_UP
E/MyTextView: dispatchTouchEvent: MotionEvent.ACTION_UP
E/MainActivity: onTouchEvent: MotionEvent.ACTION_UP
E/MyTextView: onTouchEvent: MotionEvent.ACTION_UP
E/MainActivity: onClick: MyTextView
流程图如下:
view事件传递.png
从以上流程图可以得出以下结论。
触摸事件的传递流程是从dispatchTouchEvent开始的,如果不进行人为的干预,则事件将会依照嵌套层次从外层向内层传递,到达最内层的View时,就由它的onTouchEvent方法处理,该方法如果能够消费该事件,则返回true,如果处理不了则返回false,这时事件会重新向外层传递,并由外层View的onTouchEvent方法进行处理,以此类推。
如果事件在内层传递过程中由于人为干预,事件处理函数返回true,则会导致事件提前被消费掉,内层View将不会收到这个事件。
View控件的事件出发顺序是先执行onTouch方法,在最后才执行onClick方法,如果onTouch返回true,则事件不会继续传递,最后也不会调用onClick方法;如果onTouch返回false,则事件继续传递。