谈到android事件处理,最复杂的就是对Touch事件的处理,因为Touch事件包括:down, move, up, cancle和多点触摸等多种情况,多点触摸的情况先不讨论,因为Touch有这么多的状态,所以Touch相对来说是最难处理的,下面就来讨论一下android系统是如何处理Touch事件的.
1.说到事件处理,首先我们要明白,为什么要处理事件,要了解android系统本身对事件的一个处理过程.在实际的开发中,我们如果都用系统的基本控件,那是不需要去处理事件的,但是如果我们用复杂的布局嵌套去做一些特殊的需求,例如:ScrollView中嵌套ListView,ScrollView嵌套ViewPager等,则会产生事件冲突,所以,由于事件冲突的存在,我们要去处理这些冲突,只有了解android的事件处理机制,才能有效的去处理事件冲突.还有就是如果我们要新开发一个组件,则组件的所有事件都要我们自己去做处理,这种情况也需要我们去处理事件.所以:由于存在以上说到的两种情况,我们要自己处理事件.
2.有了处理事件的动机后,接下来就要了解android系统本身是如何处理复杂的事件的.android系统为所有的事件提供了三个相关的方法,以下只以Touch事件为例说明.
这三个方法分别是:
dispatchTouchEvent(MotionEvent ev); (Activity, ViewGroup, View都有此方法)
onInterceptTouchEvent(MotionEvent ev); (只有ViewGroup有)
onTouchEvent(MotionEvent); (Activity, ViewGroup, View都有此方法)
要想了解android系统是如何对事件进行一步一步的处理,这三个方法是必须要掌握的.其中:dispatchTouchEvent(MotionEvent ev);方法是用来对事件进行分发的,即将事件分发到目标控件,onInterceptTouchEvent(MotionEvent ev)是用来过滤事件的,即进行事件的拦截,也就是是否要向下传递事件,onTouchEvent(MotionEvent ev)才是最终用来处理事件的,也就是说我们平常重写onTouchEvent时,其实,系统已经默认帮我们调用了前两个方法.下面就来详细分析一下三个方法.
首先要提的是,android系统对本件的处理是一层一层向下传递处理(树形处理).那这棵树是从那来的呢..就是我们的布局树,一个布局,无论是代码编写的布局还是xml生成的布局,android系统对它进行解析时都是将其组装成一棵UI树,最外层布局是整个UI树的根.知道这个以后,再来分析事件的处理.
处理流程:当我们的手指触摸到手机屏幕时,当前处于onStart()状态的Activity最先接收到此Touch事件下的ACTON_DOWN,然后开始调用它自己dispatchTouchEvent()开始进行DOWN事件分发,如果此方法返回true,则Activity不向下分发事件,则整个布局都不会收到DOWN事件,TouchEvent直接到Activity的onTouchEvent()方法进行事件处理.如果返回false,则表示DOWN是要被分发到下层的,此时DOWN事件被直接分发(因为没有过滤方法)到UI树的根布局(即最外层的布局),根布局拿到DOWN事件时,执行自己的dispatchTouchEvent方法,返回true,则事件直接交到根布局的onTouchEvent()中进行处理,false则表示还得向下分发,此时事件被传递到根布局的onInterceptTouchEvent()方法中,如果此方法返回true,表示要对此事件进行过滤,则此DOWN事件又直接进行到根布局的onTouchEvent()方法直接处理,false则,要根布局不对事件进行过滤,DOWN事件继续向下传递,直到达到目标组件后,目标组件调用自己的dispatchTouchEvent()方法,由于是目标组件,直接分发事件到自己的onTouchEvent方法中,目标组件如果处理完这个DOWN事件后返回true,表示该事件被消费完毕,不再向上层传递,如果返回false,则表示没有消费完这个DOWN事件,DOWN向上传递到自己的父组件中,父组件再进行DOWN事件的处理.一直向上传递直到事件被扔到虚拟机.DOWN事件才算处理完成,接着调用MOVE,MOVE完了UP,整个流程与DOWN是一樣的.
这里要强调一点的是:如果一个组件没有接收到DOWN事件,那么一定接收不到MOVE,UP事件。
通过以上的流程,我们可以明白:android系统对任何一个事件的处理都是这样的,分发事件,过滤事件,处理事件,下一个事件, 分发事件,过滤事件,处理事件……一直这样循环去处理所有的事件的。即:事件的分发,过滤是从根到叶的,处理则是从叶再到根的。
下面是我将上面的文字流程画的一张处理流程图:
从图上看,我们可以更直观的感受整个Touch事件的处理流。
3.讲了android系统是如何处理事件的整个流程,那我们实际工作中如何去处理事件呢……下面将我之前处理过的一个例子来分析。
首先我们从根到叶去处理事件,代码如下:
package com.micen.buyers.view.category;上述代码,我们是重写了ScrollView的dispatchTouchEvent()达到对事件的一个特殊处理,如果满足了我们的规则,则直接到onTouchEvent()中处理,否则,事件被发送到 onInterceptTouchEvent()中执行过滤,由于此方法中没有过滤,则下发传递事件.
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ScrollView;
import com.micen.buyers.util.Util;
public class MyScrollView extends ScrollView {
private float mLastMotionY;
private float mLastMotionX;
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
/**
* 通过重写此方法,达到对事件的处理
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMotionX = x;
mLastMotionY = y;
break;
case MotionEvent.ACTION_MOVE:
if (Math.abs(y - mLastMotionY) > Util.dip2px(20)
&& Math.abs(x - mLastMotionX) < Util.dip2px(5)) {
return true; // 如果MOVE事件的纵坐标超过20px, 横向小于5dp
// 则认为是滑动scrollview,返回true,则事件不向下分发,直接传入到
// onTouchEvent方法中,否则,认为滑动事件不属于scrollview处理,允许分发
}
break;
case MotionEvent.ACTION_UP:
break;
case MotionEvent.ACTION_CANCEL:
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.e("--------->", "user want to scroll");
return super.onTouchEvent(ev);
}
}
同样的效果,我们从叶子开始处理事件冲突,代码如下:
package com.micen.buyers.view.category;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.micen.buyers.util.Util;
public class MyViewPager extends ViewPager
{
private boolean flag = true;
private float mLastMotionY;
private float mLastMotionX;
public MyViewPager(Context context)
{
super(context);
}
public MyViewPager(Context context, AttributeSet attrs)
{
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
final float x = ev.getX();
final float y = ev.getY();
switch (ev.getAction())
{
case MotionEvent.ACTION_DOWN:
setPullToScrollViewStatus(true); //先默认父控件不接收滑动事件,事件直接传递到子滑动组件
flag = true;
mLastMotionX = x;
mLastMotionY = y;
mHandler.sendEmptyMessage(1);
break;
case MotionEvent.ACTION_MOVE:
if (flag)
{
if (Math.abs(y - mLastMotionY) > Util.dip2px(20) && Math.abs(x - mLastMotionX) < Util.dip2px(5))
{
flag = false;
setPullToScrollViewStatus(false); //满足条件,父滑动控件将事件过滤掉了,不再传到ViewPager中了.
}
}
break;
case MotionEvent.ACTION_UP:
setPullToScrollViewStatus(false);
case MotionEvent.ACTION_CANCEL:
setPullToScrollViewStatus(false);
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event)
{
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
return super.onTouchEvent(event);
}
private void setPullToScrollViewStatus(boolean disallowIntercept)
{
//调用父控件的requestDisallowInterceptTouchEvent()方法,传入true,则等将于父控件的onTInterceptTouchEvent返回false,
//不过滤,否则,父控件过滤掉事件,不再向下传递.
getParent().getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
总结:android事件处理流程,是每一个搞android的人应该熟练掌握的.