作为一名Android开发工程师,事件传递肯定是需要了解清楚的,简单的来说也就是你手指在屏幕上操作的时候,这个事件在内部是怎么传递的呢,今天我们需要详细的了解下。(此文章是在巨人的肩膀上编写的)
1,触摸时间的几种类型
- ACTION_DOWN
- ACTION_MOVE
- ACTION_UP
很简单也就是你手指的按下 移动 松开的这三个核心的操作!
2,触摸事件的三个核心方法
1,分发 Dispatch 也就是对应着 dispatchTouchEvent 方法。
一般来说,这个方法表明当前的这个视图是处理这个事件还是继续分发这个事件,当这个方法的返回值为True的时候表明,事件已经被消费了,不会再继续的往下传递了。但是这个视图是ViewGroup或者它的子类,则会调用onInterceptTouchEvent来判断是否继续分发该事件。
2,拦截 Intercept
事件拦截对应着onInterceptTouchEvent方法,这个方法只在ViewGroup及其子类中存在,在activity和view中是不存在这类方法的。
3 消费 Consume
事件消费对应着onTouchEvent这个方法,一般事件的逻辑就是在这里面进行处理的。
总结一下:
activity中有 dispatchTouchEvent 和 onTouchEvent这两个方法
viewGroup和它的子类 拥有dispatchTouchEvent,onInterceptTouch和onTouchEvent这个三个方法
view只有dispatchTouchEvent和onTouchEvent两个方法。
3,View事件的传递机制
//activity 部分代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG,"dispatchTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"onTouchEvent Action_DOWN");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG,"onTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (v.getId()){
case R.id.textView:
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"MyTextView onTouch Action_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"MyTextView onTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"MyTextView onTouch ACTION_UP");
break;
default:
break;
}
default:
break;
}
return false;
}
//MyTextView部分代码
//分配事件的方法,在这里决定是否要分配事件
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_BUTTON_PRESS:
Log.e(TAG,"dispatchTouchEvent ACTION_BUTTON_PRESS");
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"dispatchTouchEvent ACTION_DOWN");
case MotionEvent.ACTION_UP:
Log.e(TAG,"dispatchTouchEvent ACTION_UP");
case MotionEvent.ACTION_CANCEL:
Log.e(TAG,"dispatchTouchEvent ACTION_CANCEL");
default:
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
// 如果点击
// performClick();
Log.e(TAG,"onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG,"onTouchEvent ACTION_CANCEL");
break;
default:
break;
}
return super.onTouchEvent(event);
}
从打印出来的Log来看,点击事件的
第一步是走Activity的dispatchTouchEvent的ACTION_DOWN->再进入MyTextView的dispatchTouchEvent
第二步是走Activity的onTouchEvent的onTouch ACTION_DOWN-再进入MyTextView的onTouch ACTION_DOWN
第二步是走Activity的onTouchEvent的onTouch ACTION_MOVE-再进入MyTextView的onTouch ACTION_MOVE
这很明显的阐述了点击事件和触摸事件的传递顺序
1,通过上面的图我们可以分析到,触摸事件的传递流程是从 dispatchTouchEvent开发的,如果不进行人为干预,(也就是默认返回父类的同名函数),则事件将会依照嵌套层次从外层向内层传递,达到最内的View层View, 就有它的onTouchEvent方法进处理。该方法如果能消费该事件,则返回true,如果处理不了,则返回false,这时候事件就会向外层传递,并有外层的View的onTouchEvent方法进行处理。
2,如果事件在向内层传递的过程中由于人为干预的原因,事件处理函数返回true,则会导致事件提前被消费掉,内层View将不会受到这个事件。
3,View控件的事件出发顺序是先执行onTouch方法,最后才执行onClick方法的,如果onTouch返回True,则事件不会继续传递,最后也不会调用onClick方法,如果onTouch返回为False,则事件会继续传递下去。
4,ViewGroup 事件的传递机制
ViewGroup是作为View控件的容器,我们常见的ViewGroup有:LinnerLayout,Relativeyout, ListView, ScrollView.这里我们来实现一个例子。
//在上面的基础上我们自定义了RelativeRelayout这个相对布局
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;
public class MyRelativeLayout extends RelativeLayout {
private static final String TAG = "MyRelayoutLayout";
public MyRelativeLayout(Context context) {
super(context);
}
public MyRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG,"MyRelativeLayout dispatchTouch ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyRelaytiveLayout dispatchTouch ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG,"MyRelativeLayout dispatchTouch ACTION_UP");
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
/**
*这个方法是ViewGroup特有的方法
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyRelativeLayout onInterceptTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyRelativeLayout onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyRelativeLayout onInterceptTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyRelativeLayout onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyRelativeLayout onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyRelativeLayout onTouchEvent ACTION_UP");
break;
default:
break;
}
return super.onTouchEvent(event);
}
}
这里我么进使用自己的相对布局
<?xml version="1.0" encoding="utf-8"?>
<com.example.administrator.testactivity.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.administrator.testactivity.MyTextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.181"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.15" />
<Button
android:id="@+id/button"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="44dp"
android:layout_marginTop="8dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/textView"
app:layout_constraintTop_toTopOf="parent" />
</com.example.administrator.testactivity.MyRelativeLayout>
触摸屏幕上的TextView,我们可以看到打印出来的Log信息如下
其实细心的读者可以发现,中间也就是多了一道门而已也就是RelativeLayout
,这个我们做个ViewGroup的事件传递的总结:
ViewGroup通过onInterceptTouch方法对时间进行拦截,如果该方法返回True,则事件不会继续传递给子View,如果返回false或者是super.onInterceptTouch,则会继续传递给子View。
在子View中对事件进行消费后ViewGroup将接受不到任何事件, 那么当我这里插一句嘴,当我们再开发嵌套View的时候,比如说是在ScrollView中嵌套了一个ListView,那么我们是否可以采取这种方法,当ListView接收了触摸事件后就不会继续向下传递,那么就不会出现滑动冲突这样的问题了。