在我们 Android 开发中经常会遇到多个View、ViewGroup嵌套的问题,例如:当我们在一个ViewPager里面嵌套Fragment,而又在Fragment再次嵌套一个ViewPager的时候,那么两个ViewPager就可能发生冲突,这时候就要我们对分发事件进行处理了。
在一次的完整的事件传递中,主要包括了三个阶段:事件分发、拦截、消费。
触摸事件的类型
首先要有事件的传递,那么先有事件的产生才行。那么事件的产生无疑就是通过手指对屏幕触摸,在触摸后,就会产生一系列的触摸事件,触摸事件对应的MotionEvent类,其类型主要有以下三种:
MotionEvent.ACTION_DOWN 按下View,是所有事件的开始
MotionEvent.ACTION_MOVE 滑动事件
MotionEvent.ACTION_UP 与down对应,表示抬起
正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,通常有如下情况:
点击屏幕后立即松开,事件序列为Down -> Up ,
点击屏幕滑动滑动一会在松开,事件序列为Down -> Move => .... => Move -> up .
事件分发的三个阶段
在了解了触摸事件的三种主要类型之后,在讲解Activity、View、ViewGroup事件分发的具体实现之前,先要讲述事件分发分发的三个阶段。
分发(Dispatch):
在Android中,所有的触摸事件都是通过以下方法来分发事件的。
public boolean dispatchTouchEvent (MotionEvent ev)
在这个方法中,根据当前视图的具体实现逻辑,来决定这个事件是直接消耗还是继续分发给子视图。
方法返回true表示事件被当前视图消耗掉,不再继续分发事件;方法返回值为 super.dispatchTouchEvent 表示继续分发这个事件。
如果当前视图是ViewGroup或者是ViewGroup的子类,则会调用 onInterceptTouchEvent 方法判断是否拦截该事件。
拦截(Intercept):
public boolean onInterceptTouchEvent (MotionEvent ev)
这个方法只在ViewGroup或者是ViewGroup的子类中存在,View和Activity中不存在。
同理:返回true表示事件被当前视图消耗掉,不再继续分发事件给子视图,同时交由自身的onTouchEvent方法进行消费;返回 false 或者返回 super.onInterceptTouchEvent 表示继续分发这个事件。
消费(Consume):
public boolean onTouchEvent (MotionEvent ev)
事件的消费对应着 onTouchEvent 方法。
返回 true 表示当前视图可以处理对应的事件,事件不会向上传递给父视图;
返回 false 表示当前视图不处理这个事件,事件会传递给父视图的onTouchEvent方法进行处理。
用例子来深入说明一下吧:
View事件分发
新建一个View , 继承 TextView
public class MyView extends TextView {
private String TAG = "MyView" ;
public MyView(Context context) {
this(context,null);
}
public MyView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatchTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatchTouchEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatchTouchEvent: ACTION_UP");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onTouchEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "onTouchEvent: ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
编写 MyActivity ,也实现 onTouchEvent 与 dispatchTouchEvent 方法。同时也为 mTvMyView 设置点击事件与触碰事件。代码如下:
public class MyActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
private TextView mTvMyView;
private String TAG = "MyActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mTvMyView = (TextView) findViewById(R.id.tv_my_view);
mTvMyView.setOnClickListener(this);
mTvMyView.setOnTouchListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tv_my_view:
Log.i(TAG, "MyView onClick ");
break ;
}
}
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (view.getId()){
case R.id.tv_my_view:
switch (motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "MyView onTouch: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "MyView onTouch: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "MyView onTouch: ACTION_UP");
break;
}
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "onTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onTouchEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "onTouchEvent: ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.i(TAG, "dispatchTouchEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "dispatchTouchEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.i(TAG, "dispatchTouchEvent: ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
}
布局如图所示,只是在一个布局中添加了自己的自定义控件而已。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_my"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zwr.androideventdispatch.view.MyView
android:id="@+id/tv_my_view"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_centerInParent="true"
android:background="#868062"
android:gravity="center"
android:text="MyView"
android:textSize="35sp" />
</RelativeLayout>
运行 并 点击 MyView 。
运行结果如下:
MyActivity: dispatchTouchEvent: ACTION_DOWN
MyView: dispatchTouchEvent: ACTION_DOWN
MyActivity: MyView onTouch: ACTION_DOWN
MyView: onTouchEvent: ACTION_DOWN
MyActivity: dispatchTouchEvent: ACTION_UP
MyView: dispatchTouchEvent: ACTION_UP
MyActivity: MyView onTouch: ACTION_UP
MyView: onTouchEvent: ACTION_UP
MyActivity: MyView onClick
在onTouchEvent 与dispatchTouchEvent返回的值有以下三种情况:
- 返回true
- 返回false
- 返回父类的同名方法
不同的返回值,最后事件的分发也会受一定的影响,所以可以画出以下的流程图:
- 由上图可知,只要事件被拦截了,就不会再继续分发了。
- 先执行onTouch方法,然后执行onClick方法。如果onTouch方法返回了true,那么onClick方法将不会被调用。
ViewGroup的事件分发机制
-
现在只要自定义一个ViewGroup并实现onTouchEvent、dispatchTouchEvent、onInterceptTouchEvent方法并
-
包裹在上述的自定义View
-
进行相似的操作。
public class MyViewGroup extends RelativeLayout { private String TAG = "MyViewGroup"; public MyViewGroup(Context context) { this(context,null); } public MyViewGroup(Context context, AttributeSet attrs) { this(context, attrs,0); } public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG, "dispatchTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "dispatchTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG, "dispatchTouchEvent: ACTION_UP"); break; } return super.dispatchTouchEvent(event); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG, "onTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "onTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG, "onTouchEvent: ACTION_UP"); break; } return super.onTouchEvent(event); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: Log.i(TAG, "onInterceptTouchEvent: ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "onInterceptTouchEvent: ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.i(TAG, "onInterceptTouchEvent: ACTION_UP"); break; } return super.onInterceptTouchEvent(ev); } }
运行结果如下:
MyActivity: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: dispatchTouchEvent: ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: ACTION_DOWN
MyView: dispatchTouchEvent: ACTION_DOWN
MyActivity: MyView onTouch: ACTION_DOWN
MyView: onTouchEvent: ACTION_DOWN
MyActivity: dispatchTouchEvent: ACTION_UP
MyViewGroup: dispatchTouchEvent: ACTION_UP
MyViewGroup: onInterceptTouchEvent: ACTION_UP
MyView: dispatchTouchEvent: ACTION_UP
MyActivity: MyView onTouch: ACTION_UP
MyView: onTouchEvent: ACTION_UP
MyActivity: MyView onClick
相似的,返回不同的结果,事件的分发就有所不同。总结成流程图如下:
事件的分发由Activity到ViewGroup,再由ViewGroup到子View。
网友评论