Android进阶知识——View的事件体系

Posted ABded

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶知识——View的事件体系相关的知识,希望对你有一定的参考价值。

文章目录


本章我们将介绍android中十分重要的一个概念:View,它的应用十分广泛。比如说自定义控件和解决滑动冲突等,因此学好Veiw的事件体系对于我们开发者而言是十分必要的。

1.View的基础知识

本节我们将主要介绍的内容有:View的位置参数、MotionEvent和TouchSlop对象、VelocityTracker、GestureDetector和Scroller对象。

1.1什么是View

首先View是Android中所有控件的基类,无论是Button还是ListView,它们的共同基类都是View。所以,View是一种界面层的控件的一种抽象,它代表了一个控件。而ViewGroup则是一个控件组,也就是说ViewGroup内部包含了许多控件,即一组View。

在Android的设计中,ViewGroup也继承了View,这就意味着View本身就可以是单控件也可以是由多个控件组成的一组控件,通过这种关系就形成了View树的结构。

1.2View的位置参数

View的位置主要由它的四个顶点来决定,分别对应于View的四个属性:top、left、right、bottom,其中top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标。(这些坐标都是相对于View的父容器来说的,因此它是一种相对坐标;在Android中,x轴和y轴的正方向分别为右和下)

根据上述知识,我们很容易得到View的宽高和坐标的关系:

width = right - left
hight = bottom - top

上述四个参数的获取方式如下:

  • Left = getLeft();

  • Right = getRight();

  • Top = getTop();

  • Bottom = getBottom();

从Android3.0开始,View增加了额外的几个参数:x、y、translationX和translationY,其中x和y是View左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量。这四个参数也是相对于父容器的坐标,并且translationX和translationY的默认值是0。和View的四个基本位置参数一样,View也为它们提供了get/set方法,这些参数的换算关系如下所示:

x = left + translationX
y = top + translationY

注意:View在平移过程中,top和left表示的是原始左上角的位置信息,并且值并不会发生改变,此时发生改变的是x、y、translationX和translationY这四个参数。

1.3MotionEvent和TouchSlop

1.MotionEvent

在手指接触屏幕后所产生的一系列事件中,典型的事件类型有如下几种:

  • ACTION_DOWN——手指刚接触屏幕(按下)

  • ACTION_MOVE——手指在屏幕上移动(滑动)

  • ACTION_UP——手指从屏幕上松开的一瞬间(松开)

正常情况下,一次手指触摸屏幕的行为会触发一系列的点击事件,考虑如下几种情况:

  • 点击屏幕后松手离开,事件序列为DOWN->UP

  • 点击屏幕滑动一会再松开,事件序列为 DOWN->MOVE->…->MOVE->UP

我们还可以通过MotionEvent对象得到点击事件发生的x和y坐标。共有两组方法:getX/getY和getRawX/getRawY。(getX/getY返回的是相对于当前View左上角的x和y坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标)

2.TouchSlop

TouchSlop是系统所能识别出的被认为是滑动的最小距离,也就是说,当在手机屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。

这个常量和设备有关系,在不同设备上这个值可能是不同的,我们可以通过如下方式获取这个常量:ViewConfiguration.get(getContext()).getScaledTouchSlop()。我们可以利用这个常量来做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们就可以认为它们不是滑动,这样做可以有更好的用户体验。

1.4VelocityTracker、GestureDetector和Scroller

1.VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。它的使用过程如下:

  • 首先,在View的onTouchEvent方法中追踪当前单击事件的速度
    VelocityTracker velocityTracker=VelocityTracker.obtain();
    velocityTracker.addMovement(event);
    
  • 接着,调用get…方法获取当前的速度
    velocityTracker.computeCurrentVelocity(1000);
    int xVelocity= (int) velocityTracker.getXVelocity();
    int yVelocity= (int) velocityTracker.getYVelocity();
    

注意:获取当前速度之前必须先计算速度,即getXVelocity和getYVelocity这两个方法的前面必须调用computeCurrentVelocity方法;这里的速度是指一段时间内手指所滑动的像素数,比如将时间间隔设置为1000ms时,在1s内,手指在水平方向从左向右滑动100像素,那么水平速度就是100。注意速度可以为负数,当手指从右往左滑动时,水平方向速度即为负值。
速度的计算公式如下:

速度 = (终点位置 - 起点位置) / 时间段

另外,compeCurrentVelocity这个方法的参数表示的是一个时间单元或者说时间间隔,它的单位是毫秒(ms),计算速度时得到的速度就是在这个时间间隔内手指在水平或竖直方向上所滑动的像素点。

最后,当不需要使用它的时候,需要调用clear方法来重置并回收内存:

velocityTracker.clear();
velocityTracker.recycle();

2.GestureDectector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。它的使用过程如下:

  • 首先,需要创建一个GestureDetector对象并实现OnGestureListener接口,根据需要我们可以实现OnDoubleTapListener从而能够监听双击行为

    GestureDetector mGestureDetector=new GestureDetector(new GestureDetector.OnGestureListener() 
    	//选择实现以下的方法
        @Override
        public boolean onDown(MotionEvent e) 
            return false;
        
        @Override
        public void onShowPress(MotionEvent e) 
        
        @Override
        public boolean onSingleTapUp(MotionEvent e) 
            return false;
        
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) 
            return false;
        
        @Override
        public void onLongPress(MotionEvent e) 
        
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) 
            return false;
        
    );
    //解决长按屏幕后无法拖动的现象
    mGestureDetector.setIsLongpressEnabled(false);
    
  • 接着,接管目标View的onTouchEvent方法,在待监听View的onTouchEvent方法中添加

    boolean consume=mGestureDetector.onTouchEvent(event);
    return consume;
    

接下来,我们来看看OnGetsureListener和OnDoubleTapListener中我们可以实现的方法:

上表中的方法有很多,而我们比较常用的方法有:onSingleTapUp(单击)、onFling(快速滑动)、onScroll(拖动)、onLongPress(长按)和onDoubleTap(双击)。

另外,关于是使用onTouchEvent还是使用GestureDectector。这里给大家一个建议:如果是监听滑动相关的,建议自己在onTouchEvent中实现,如果要监听双击这种行为的话,那么就使用GestureDectector。

3.Scroller

弹性滑动对象,用于实现View的弹性滑动。当使用View的scrollTo/scrollBy方法进行滑动时,其过程是一瞬间完成的,但这个没有过渡效果的滑动用户体验不好。而Scroller可以用来实现有过渡效果的滑动,其过程不是瞬间完成的,而是在一定的时间间隔内完成。Scroller和View的computeScroll方法配合使用才能共同完成这一功能。使用方法如下:

//注:以下代码都是写在自定义控件中的
Scroller scroller=new Scroller(getContext());

public void smoothScrollTo(int destX,int destY)//缓慢滚动到指定位置
    int scrollX=getScrollX();
    int delta=destX-scrollX;
    scroller.startScroll(scrollX,0,delta,0,1000);
    invalidate();


@Override
public void computeScroll()
    if(scroller.computeScrollOffset())
        scrollTo(scroller.getCurrX(),scroller.getCurrY());
        postInvalidate();
    

2.View的滑动

滑动在Android开发中具有很重要的作用,不管一些滑动效果多么绚丽,归根结底,它们都是由不同的滑动外加一些特效所组成的。因此,掌握滑动的方法是实现绚丽的自定义控件的基础。通过三种方式可以实现View的滑动:第一种是通过View本身提供的scrollTo/scrollBy方法来实现滑动;第二种是通过动画给View施加平移效果来实现滑动;第三种是通过改变View的LayoutParams使得View重新布局从而实现滑动。

2.1使用scrollTo/scrollBy

scrollBy本质上也是调用了scrollTo方法,它实现基于当前位置的相对滑动,而scrollTo则实现了基于所传递参数的绝对滑动。

scrollTo和scrollBy只能改变View内容的位置而不能改变View在布局中的位置;View边缘是指View的位置,由四个顶点组成,而View内容边缘是指View中的内容的边缘;mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,mScrollY的值总是等于View上边缘和View内容上边缘在竖直方向的距离;mScrollX和mScrollY的单位为像素;从左向右滑动,那么mScrollX为负值,反之为正值,从上往下滑动,那么mScrollY为负值,反之为正值。(正好和x、y轴的延伸方向相反)

2.2使用动画

通过动画我们能够让一个View进行平移,而平移就是一种滑动。使用动画来移动View,主要是操作View的translationX和translationY属性,既可以采用传统的View动画,也可以采用属性动画。

采用如下的View动画代码,可以在1000ms内将一个View从原始位置向右下角移动100个像素,代码如下:

//View动画
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:zAdjustment="normal">

    <translate
        android:duration="1000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toXDelta="100"
        android:toYDelta="100" />

</set>
//给控件应用View动画
Button button=findViewById(R.id.myButton);
Animation myAnimator= AnimationUtils.loadAnimation(this,R.anim.my_animation);
button.startAnimation(myAnimator);

如果采用属性动画的话,就更简单了,以下代码可以将一个View在1000ms内从原始位置向右平移100像素。

ObjectAnimator.ofFloat(button,"translationX",0,100).setDuration(1000).start();

View动画是对View的影像做操作,它并不能真正改变View的位置参数,包括宽/高,并且如果希望动画后的状态得以保留还必须将fillAfter属性设置为true,否则动画完成后其动画结果会消失。而使用属性动画并不会存在上述问题。

上面提到View动画并不能真正改变View的位置,这会带来一个很严重的问题。比如我们通过View动画将一个Button向右移动100px,并且这个View设置的有单击事件,然后你会惊奇的发现,单击新位置无法触发onClick事件,而单击原始位置仍然可以触发onClick事件,尽管Button已经不再原始位置了。这个问题还是比较好理解的,因为Button的位置信息(四个顶点和宽高)并不会随着动画而改变,因此在系统眼里,这个Button并没有发生任何改变。

从Android3.0开始,使用属性动画可以解决上面的问题。而在Android3.0以下我们这里给出一个简单的解决方法。针对上述的View动画问题,我们可以在新位置预先创建一个和目标Button一模一样的Button,它们不但外观一样连onClick事件也一样。当目标Button完成平移动画后,就把目标Button隐藏,同时把预先创建好的Button显示出来,通过这种间接的方式我们解决了上面的问题。

2.3改变布局参数

改变布局参数,即改变LayoutParams。比如我们想要把一个Button向右平移100px,我们只需要将这个Button的LayoutParams里的marginLeft参数的值增加100px即可。

还有一种情形,为了达到移动Button的目的,我们可以在Button的左边放置一个空View,这个空View的默认宽度为0,当我们需要向右移动Button时,只需要重新设置空View的宽度即可,当空View的宽度增大时(假设Button的父容器是水平方向的LinearLayout),Button就自动被挤向右边,即实现了向右平移的效果。而该怎样重新设置一个View的LayoutParams呢?代码如下所示:

Button button=findViewById(R.id.myButton);
ViewGroup.MarginLayoutParams params=(ViewGroup.MarginLayoutParams) button.getLayoutParams();
params.leftMargin+=300;
button.setLayoutParams(params);
//或button.requestLayout();

2.4各种滑动方式的对比

上面我们共介绍了三种不同的滑动方式,它们都能实现View的滑动,接下来我们就来分析一下它们之间的差别。

  • scrollTo/scrollBy这种方式:它可以比较方便的实现滑动效果并且不影响内部元素的单击事件,但是它的缺点也是很明显的:它只能滑动View的内容,并不能滑动View本身。

  • 动画这种方式:如果是使用属性动画,那么这种方式没有明显的缺点;如果是使用View动画或者是在Android3.0以下使用属性动画,均不能改变View本身的属性。在实际使用中,如果动画元素不需要响应用户的交互,那么使用动画来做滑动是比较合适的,否则就不合适了。但是动画有一个很明显的优点,那就是一些复杂的效果必须要通过动画才能实现。

  • 改变布局这种方式:它除了用起来麻烦点以外,也没有明显的缺点。它的主要适用对象是一些具有交互性的View,因为这些View需要和用户交互,直接通过动画去实现会有问题。

针对上述分析,我们再来总结一下:

  • scrollTo/scrollBy:操作简单,适合对View内容的滑动;

  • 动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果;

  • 改变布局参数:操作稍微复杂,适用于有交互的View。

2.5用属性动画来实现跟手滑动的效果

下面我们将用属性动画的方式来实现一个跟手滑动的效果,拖动它可以让它在整个屏幕上随意滑动。代码如下:

public class ScrollerLayout extends LinearLayout 

    int mLastX=0;
    int mLastY=0;

    public ScrollerLayout(Context context, @Nullable AttributeSet attrs) 
        super(context, attrs);
    

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) 
        int x=(int) event.getRawX();
        int y=(int) event.getRawY();
        switch (event.getAction())
            case MotionEvent.ACTION_DOWN:
                mLastX=x;
                mLastY=y;
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX=x-mLastX;
                int deltaY=y-mLastY;
                int translationX=(int) this.getTranslationX()+deltaX;
                int translationY=(int) this.getTranslationY()+deltaY;
                this.setTranslationX(translationX);
                this.setTranslationY(translationY);
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        

        mLastY=y;
        mLastX=x;
        return true;
    

3.弹性滑动

上面我们讲的几种滑动方式都是比较生硬地滑动过去,这种方式的用户体验实在太差,因此我们要来实现渐进式滑动——弹性滑动。实现弹性滑动的方法有很多,但他们都有一个共同思想:将一次大的滑动分成若干次小滑动并在一个时间段内完成。

3.1使用Scroller

Scroller的典型使用方法,代码如下:

//注:以下代码都是写在自定义控件中的
Scroller scroller=new Scroller(getContext());

//缓慢滚动到指定位置
public void smoothScrollTo(int destX,int destY)
    int scrollX=getScrollX();
    int delta=destX-scrollX;
    //1000ms内滑向delta,效果就是慢慢滑动
    scroller.startScroll(scrollX,0,delta,0,1000);
    invalidate();


@Override
public void computeScroll()
    if(scroller.computeScrollOffset())
        scrollTo(scroller.getCurrX(),scroller.getCurrY());
        postInvalidate();
    
owmenx

我们先来看一下它的工作原理:当我们构造一个Scroller对象并且调用它的startScroll方法时,Scroller内部其实什么都没做,它只是保存了我们传递的几个参数。startScroll的源码如下所示:

//规划滑动路线
public void startScroll(int startX, int startY, int dx, int dy, int duration) 
    mMode = SCROLL_MODE;
    mFinished = false;
    mDuration = duration;
    mStartTime = AnimationUtils.currentAnimationTimeMillis();
    mStartX = startX;
    mStartY = startY;
    mFinalX = startX + dx;
    mFinalY = startY + dy;
    mDeltaX = dx;
    mDeltaY = dy;
    mDurationReciprocal = 1.0f / (float) mDuration;

这个方法的参数含义:startX和startY表示的是滑动的起点,dx和dy表示的是要滑动的距离,而duration表示的是滑动时间,即整个滑动过程完成所需要的时间。(注意:这里的滑动是指View内容的滑动而非View本身位置的改变)

通过上面的分析,我们可以看到,仅仅调用startScroll方法是无法让View滑动的,因为它内部并没有做滑动相关的事,而到底是什么让View滑动的呢?

答案就是startScroll方法下面的invalidate方法。invalidate方法会导致View重绘,在View的draw方法中又会去调用computeScroll方法,computeScroll方法在View中是一个空实现,因此需要我们自己去实现,在上面的代码中就已经实现了computeScroll方法。正是因为这个computeScroll方法,View才能实现弹性动画。这么说可能不太好理解,换句话说就是:当View重绘后会在draw方法中调用computeScroll,而computeScroll又会去向Scroller获取当前的scrollX和scrollY;然后通过scrollTo方法实现滑动;接着又调用postInvalidate方法来进行第二次重绘,这一次重绘的过程和第一次重绘一样,还是会导致computeScroll方法被调用;然后继续向Scroller获取当前的scrollX和scrollY,并通过scrollTo方法滑动到新的位置,如此反复,直到整个滑动过程结束。

我们再来看一下Scroller的computeScrollOffset方法的实现,如下所示:

//循环小幅度移动,从而实现弹性滑动
public boolean computeScrollOffset() 
    ...
    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
    if (timePassed < mDuration) 
        switch (mMode) 
        case SCROLL_MODE:
            final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
            mCurrX = mStartX + Math.round(x * mDeltaX);
            mCurrY = mStartY + Math.round(x * mDeltaY);
            break;
        ...
        
    
    return true;

该方法会根据时间的流逝来计算出当前的scrollX和scrollY的值,这样就将一次大的滑动分成了若干次小滑动并在一个时间段内完成。这个方法的返回值也很重要,它返回true表示滑动还未结束,false则表示滑动已经结束,因此当这个方法返回true时,我们要继续进行View滑动。

这里我们再来概括一下Scroller的工作原理:Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。(Scroller:规划滑动路线;computeScroll:计算出当前的滑动位置;scrollTo:实现滑动)

3.2通过动画

动画本身就是一种渐近的过程,因此通过它来实现的滑动天然就具有弹性效果。而我们还可以利用动画的特性来实现一些动画不能实现的效果。接下来我们就用动画来模仿Scroller来实现View的弹性滑动,代码如下:

final int startX=0;
final int deltaX=100;

ValueAnimator animator=ValueAnimator.ofInt(0,1).setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() 
    @Override
    public void onAnimationUpdate(ValueAnimator animation) 
        float fraction=animation.getAnimatedFraction();//动画进行的百分比
        button.scrollTo(startX+(int)(deltaX*fraction),0);
    
);
animator.start();

在上述的代码中,我们的动画本质上没有作用于任何对象,它只是在1000ms内完成了整个动画过程。根据这一特性,我们就可以在动画的每一帧到来时获取动画完成的比例,然后根据这个比例计算出当前View所要滑动的距离。(注意:这里的滑动针对的是View的内容而非View本身)

我们能够发现,这个方法的思想其实和Scroller比较类似,都是通过改变一个百分比配合scrollTo方法来完成View的滑动。需要说明的是,采用这种方法除了能够完成弹性滑动以外,还可以实现其他动画效果,我们完全可以在onAnimationUpdate方法中加上我们想要的其他操作。

3.3使用延时策略

使用延时策略实现弹性滑动,它的核心思想是通过发送一系列延时消息从而达到一种渐进式的效果。具体来说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。

对于postDelayed方法来说,我们可以通过它来延时发送一个消息,然后在消息中来进行View的滑动,如果接连不断地发送这种延时消息,那么就可以实现弹性滑动的效果。对于sleep方法来说,通过while循环中不断地滑动View和sleep,就可以实现弹性滑动的效果。

下面我们采用Handler来做个示例,其他方法请读者自行尝试,思想都是类似的。代码如下:

private static final int MESSAGE_SCROLL_TO=1;
private static final int FRAME_COUNT=30;
private static final int DELAYED_TIME=33;
private int mCount=0;

@SuppressLint("HandlerLeak")
private Handler mHandler=new Handler()
    @Override
    public void handleMessage(@NonNull Message msg) 
        switch (msg.what)
            case MESSAGE_SCROLL_TO:
                mCount++;
                if(mCount<=FRAME_COUNT)
                	//核心思想还是:将一次大的滑动分成若干次小滑动并在一个时间段内完成。
                    float fraction=mCount/(float) FRAME_COUNT;
                    int scrollX=(int) (fraction*100);
                    button.scrollTo(scrollX,0);
                    mHandler.sendEmptyMessageDelayed(MESSAGE_SCROLL_TO,DELAYED_TIME);
                
            default:
                break;
        
    
;

上面几种弹性滑动的实现方法,在介绍中侧重更多的是实现思想,在实际使用中可以对其灵活的进行扩展从而实现更多复杂的效果。

4.View的事件分发机制

本节将介绍View的一个核心知识点:事件分发机制,事件分发机制不仅仅是核心知识点更是难点。另外,View的另一大难题滑动冲突,它的解决方法的理论基础就是事件分发机制,因此掌握好View的事件分发机制是十分重要的。

4.1点击事件的传递规则

首先我们这里要分析的对象就是MotionEvent,即点击事件。所谓点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递过程就是分发过程。点击事件的分发过程由三个很重要的方法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent,下面我们再来逐一介绍这几个方法。

  • public boolean dispatchTouchEvent(MotionEvent ev):用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

  • public boolean onInterceptTouchEvent(MotionEvent event):在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

  • public boolean onTouchEvent(MotionEvent event):在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再次接收到事件。

为了更加清楚地描述上面三个方法的区别和关系,我们可以借助如下的伪代码帮助理解:

public boolean dispatchTouchEvent(MotionEvent event)
    boolean consume=false;
    if(onInterceptTouchEvent(event))
        consume=onTouchEvent(event);//如果拦截就调用onTouchEvent处理点击事件
    else 
        consume=child.dispatchTouchEvent(event);//如果不拦截就将点击事件分发给子View
    
    
    return consume;

现在我们来总结一下点击事件的传递规则:对于一个根ViewGroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,如果这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用;如果这个ViewGroup的onInterceptTouchEvent方法返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。(由ViewGroup向子View分发)

当一个View需要处理事件时,如果设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理还要看onTouch的返回值,如果返回false则当前View的onTouchEvent方法会被调用;如果返回true,那么onTouchEvent方法将不会被调用。由此可见,给View设置的OnTouchListener,其优先级比onTouchEvent要高。在onTouchEvent方法中,如果设置的有OnClickListener,那么它的onClick方法会被调用。可以看出,平时我们常用的OnClickListener,其优先级最低,即处于点击事件传递的尾端。(先执行onTouchListener,再执行onTouchEvent,最后执行OnClick)

当一个点击事件产生后,它的传递顺序遵循如下顺序:Activity->Window->View,即事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给顶级View。顶级View接收到事件后,就会按照事件分发机制去分发事件。考虑一种情况,如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent就会被调用,依次类推。如果所有的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用。(如果拦截了,但onTouchEvent返回false,那么就会一步步的向上逐级调用onTouchEvent;如果直接就不拦截的话,那么点击事件就会向下(子View)进行分发)

关于事件传递的机制,这里给出一些结论,根据这些结论可以更好地理解整个传递机制,如下所示。