了解Android控件的触摸事件传递与处理对我们日常开发中自定义控件和触摸事件冲突解决有重大意义。Android控件的触摸事件传递和处理主要有以下几个方法,下面一一介绍。
一、与触摸事件有关的几个方法
boolean dispatchTouchEvent(MotionEvent ev); | 接收到触摸事件时,是否分发事件到下面的View 返回true:分发触摸事件 返回false:不分发,下面的View就拿不到触摸事件 |
boolean onInterceptTouchEvent(MotionEvent ev); | 接收到触摸事件时,是否拦截事件 返回true:拦截,则调用onTouchEvent方法处理事件 返回false:不拦截,事件继续往子View传 |
boolean onTouchEvent(MotionEvent ev); | 是否响应事件 返回true:响应 返回false:不响应 |
boolean onTouch(View v, MotionEvent event); | 是否响应事件,当View调用了setOnTouchListener方法设置了触摸监听器,则事件响应的时候先调用onTouch方法 返回true:响应,则onTouchEvent方法不执行 返回false:不响应,并调用onTouchEvent方法 |
void requestDisallowInterceptTouchEvent(boolean disallowIntercept); | 请求父控件是否不拦截事件 返回true:不允许父控件的onInterceptTouchEvent调用 返回false:允许调用 |
二、拥有这些方法的类
父类 | 子类 | 拥有的方法 |
Activity | Activity | dispatchTouchEvent、onTouchEvent |
ViewGroup | RelativeLayout, LinearLayout... | dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent、requestDisallowInterceptTouchEvent |
View | Button、TextView... | dispatchTouchEvent、onTouchEvent |
三、事件处理规则
触摸事件是从Activity分发(只是分发,还没有处理)到父控件,父控件先判断是否拦截,如果不拦截事件,则继续分发到子控件,然后一直往下分发。但处理就刚好相反,由子控件先处理事件,如果子控件没有处理事件,则交给到父控件处理,一直往上处理,直到哪个控件处理了触摸事件,就事件处理就到此结束。
1.当用户触摸屏幕的时候,从按下到移动,最后到抬起,会依次产生ACTION_DOWN、ACTION_MOVE、ACTION_UP三种触摸事件,事件先传到Activity,然后Activity调用分发事件方法dispatchTouchEvent,如果返回true,则事件就会传给Activity的第一个父控件。
2.父控件拿到事件之后,也会调用分发事件方法dispatchTouchEvent,如果返回true,则继续调用拦截方法onInterceptTouchEvent,如果返回true,则父控件拦截了事件,并调用父控件的onTouchEvent方法,下面的子控件就不会再响应onTouchEvent,onTouch的方法。
3.子控件拿到事件之后,先判断是否设置了OnTouchListener, 如果设置了,则调用OnTouchListener的onTouch方法,如果返回true,事件已经处理到此结束,则跳过onTouchEvent方法,否则调用onTouchEvent方法,当onTouchEvent方法返回true,则事件处理到此结束,上面的父控件就不会再调用onTouchEvent方法。
4.如果某一个控件响应了ACTION_DOWN事件,则后续的ACTION_MOVE、ACTION_UP事件就会直接交给该控件处理,除非它的父控件拦截了后续的事件,但可以在处理ACTION_DOWN事件时,调用requestDisallowInterceptTouchEvent禁止父控件的拦截。如果控件没有处理ACTION_DOWN事件,则后续的事件就不会再传到该控件中,当下一次的ACTION_DOWN事件产生时,还是会传给该View的。
四、下面来做一个演示
定义MainActivity、ParentView、ChildView,OnTouchListener
MainActivity——>妈妈
ParentView——>爸爸
ChildView——>我
OnTouchListener——>老婆
Event——>苹果(一次来三个苹果,模拟ACITON_DWON、ACTION_MOVE、ACTION_UP事件)
用一家人吃苹果这个案例,模拟触摸事件的处理。
妈妈MainActivity源码:
package com.test.activity;import com.example.javawebtest.R;import android.os.Bundle;import android.util.Log;import android.view.MotionEvent;import android.app.Activity;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub boolean eat = false; Log.i(getClass().getSimpleName(), "妈妈" + (eat ? "吃了苹果" : "没有吃,扔了")); return eat; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub Log.i(getClass().getSimpleName(), "妈妈想分给爸爸苹果"); return super.dispatchTouchEvent(ev); }}
爸爸ParentView的源码:
package com.test.view;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.RelativeLayout;public class ParentView extends RelativeLayout { public ParentView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } @Override public boolean dispatchTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub Log.i(getClass().getSimpleName(), "爸爸想分给我苹果"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub boolean intercept = false; Log.i(getClass().getSimpleName(), "爸爸" + (intercept ? "想吃" : "不想吃") + "苹果"); return intercept ? true : super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub boolean eat = false; Log.i(getClass().getSimpleName(), "爸爸" + (eat ? "吃了苹果" : "没有吃,给了妈妈")); return eat; }}
我ChildView、老婆OnTouchListener的源码:
package com.test.view;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.View.OnTouchListener;public class ChildView extends View implements OnTouchListener { public ChildView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub setOnTouchListener(this); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub boolean eat = false; Log.i(getClass().getSimpleName(), "我" + (eat ? "吃了苹果" : "没有吃苹果,给了爸爸")); return eat; } @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub boolean eat = false; Log.i(getClass().getSimpleName(), "老婆" + (eat ? "吃了苹果" : "没有吃苹果,给了我")); return eat; } }
场景一:大家谁也没吃苹果,效果如下图
刚开始,第一个苹果一直往下传,谁也没吃,后面妈妈知道我们都不喜欢吃苹果,第二、第三个苹果都没有传给我们,就扔了...
场景二:我吃了苹果,将ChildView的onTouchEvent的eat置为true
我吃了苹果,不过每次吃之前都要询问老婆,要是老婆吃了,我就不能吃...
场景三:爸爸抢了苹果,将onInterceptTouchEvent的intercept置为true
结果苹果被老爸吃了,然后再没有我的事情了...