Android进阶之View事件分发机制和源码详解

移动开发 Android
View的位置主要由它的四个顶点来决定,即它的四个属性:top、left、right、bottom,分别表示View左上角的坐标点( top,left) 以及右下角的坐标点( right,bottom)。

[[418059]]

本文转载自微信公众号「Android开发编程」,作者Android开发编程。转载本文请联系Android开发编程公众号。

前言

在Android 开发中事件分发是比较重要的,也是比较难理解的;

开发中会经常遇到滑动冲突(比如ScrollView或是SliddingMenu与ListView的嵌套)的问题,

需要我们深入的了解android事件响应机制才能解决,

事件响应机制已经是android开发者必不可少的知识。

那么今天我们就来详细讲解下事件分发,各位老铁们一起学习

一、view事件相关知识了解

1、View的位置参数

View的位置主要由它的四个顶点来决定,即它的四个属性:top、left、right、bottom,分别表示View左上角的坐标点( top,left) 以及右下角的坐标点( right,bottom)。

同时,我们可以得到View的大小:

width = right - left 
height = bottom - top 
  • 1.
  • 2.

而这四个参数可以由以下方式获取:

Left = getLeft(); 
Right = getRight(); 
Top = getTop(); 
Bottom = getBottom(); 
  • 1.
  • 2.
  • 3.
  • 4.

Android3.0后,View增加了x、y、translationX和translationY这几个参数。其中x和y是View左上角的坐标,而translationX和translationY是View左上角相对于容器的偏移量。他们之间的换算关系如下:

x = left + translationX; 
y = top + translationY; 
  • 1.
  • 2.

2、MotionEvent

我们对屏幕的点击,滑动,抬起等一系的动作都是由一个一个MotionEvent对象组成的。根据不同动作,主要有以下三种事件类型:

  • ACTION_DOWN:手指刚接触屏幕,按下去的那一瞬间产生该事件
  • ACTION_MOVE:手指在屏幕上移动时候产生该事件
  • ACTION_UP:手指从屏幕上松开的瞬间产生该事件

从ACTION_DOWN开始到ACTION_UP结束我们称为一个事件序列

正常情况下,无论你手指在屏幕上有多么骚的操作,最终呈现在MotionEvent上来讲无外乎下面两种;

  • 点击后抬起,也就是单击操作:ACTION_DOWN -> ACTION_UP
  • 点击后再风骚的滑动一段距离,再抬起:ACTION_DOWN -> ACTION_MOVE -> ... -> ACTION_MOVE -> ACTION_UP;
public class MotionEventActivity extends BaseActivity { 
    private Button mButton; 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_motion_event); 
        mButton = (Button) findViewById(R.id.button); 
        mButton.setOnTouchListener(new View.OnTouchListener() { 
            @Override 
            public boolean onTouch(View v, MotionEvent event) { 
                switch (event.getAction()) { 
                    case MotionEvent.ACTION_DOWN: 
                        e("MotionEvent: ACTION_DOWN"); 
                        break; 
                    case MotionEvent.ACTION_MOVE: 
                        e("MotionEvent: ACTION_MOVE"); 
                        break; 
                    case MotionEvent.ACTION_UP: 
                        e("MotionEvent: ACTION_UP"); 
                        break; 
                } 
                return false; 
            } 
        }); 
    } 
    public void click(View v) { 
        e("点击了按钮"); 
    } 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

3、Scroller

弹性滑动对象,用于实现View的弹性滑动。其本身无法让View弹性滑动,需要和View的computeScroll方法配合使用才能完成这个功能。使用方法:

Scroller scroller = new Scroller(mContext); 
//缓慢移动到指定位置 
private void smoothScrollTo(int destX,int destY){ 
    int scrollX = getScrollX(); 
    int delta = destX - scrollX; 
    //1000ms内滑向destX,效果就是慢慢滑动 
    mScroller.startScroll(scrollX,0,delta,0,1000); 
    invalidata(); 
}  
@Override 
public void computeScroll(){ 
    if(mScroller.computeScrollOffset()){ 
    scrollTo(mScroller.getCurrX,mScroller.getCurrY()); 
    postInvalidate(); 
    } 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

4、事件分发涉及方法了解

  • dispatchTouchEvent(MotionEvent ev):用来进行事件分发。如果事件能传递给当前的View,那么此方法一定会被调用。
  • onInterceptTouchEvent(MotionEvent ev):用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会再被调用。
  • onTouchEvent(MotionEvent ev):用来处理触摸事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接受事件。
  • onTouch(View view, MotionEvent motionEvent):用于处理触摸事件,通过setOnTouchListener设置,很常见的方法。
  • onClick(View view):用于处理点击事件,通过setOnClickListener设置,很常见的方法。
  • requestDisallowInterceptTouchEvent(boolean b):请求不拦截触摸事件。一般用于处理滑动冲突中,子控件请求父控件不拦截;
  • ACTION_DOWN以外的其他事件,ACTION_DOWN事件不受影响。

二、事件分发

当一个MotionEvent产生了以后,就是你的手指在屏幕上做一系列动作的时候,系统需要把这一系列的MotionEvent分发给一个具体的View。我们重点需要了解这个分发的过程,那么系统是如何去判断这个事件要给哪个View,也就是说是如何进行分发的呢?

事件分发需要View的三个重要方法来共同完成:

1、public boolean dispatchTouchEvent(MotionEvent event)

通过方法名我们不难猜测,它就是事件分发的重要方法。那么很明显,如果一个MotionEvent传递给了View,那么dispatchTouchEvent方法一定会被调用!

返回值:表示是否消费了当前事件。可能是View本身的onTouchEvent方法消费,也可能是子View的dispatchTouchEvent方法中消费。返回true表示事件被消费,本次的事件终止。返回false表示View以及子View均没有消费事件,将调用父View的onTouchEvent方法;

2、public boolean onInterceptTouchEvent(MotionEvent ev)

事件拦截,当一个ViewGroup在接到MotionEvent事件序列时候,首先会调用此方法判断是否需要拦截。特别注意,这是ViewGroup特有的方法,View并没有拦截方法;

返回值:是否拦截事件传递,返回true表示拦截了事件,那么事件将不再向下分发而是调用View本身的onTouchEvent方法。返回false表示不做拦截,事件将向下分发到子View的dispatchTouchEvent方法。

3、public boolean onTouchEvent(MotionEvent ev)

真正对MotionEvent进行处理或者说消费的方法。在dispatchTouchEvent进行调用;

返回值:返回true表示事件被消费,本次的事件终止。返回false表示事件没有被消费,将调用父View的onTouchEvent方法

上面的三个方法可以用以下的伪代码来表示其之间的关系。

public boolean dispatchTouchEvent(MotionEvent ev) { 
        boolean consume = false;//事件是否被消费 
        if (onInterceptTouchEvent(ev)){//调用onInterceptTouchEvent判断是否拦截事件 
            consume = onTouchEvent(ev);//如果拦截则调用自身的onTouchEvent方法 
        }else{ 
            consume = child.dispatchTouchEvent(ev);//不拦截调用子View的dispatchTouchEvent方法 
        } 
        return consume;//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

接下来我们来看一下View 和ViewGroup 在事件分发的时候有什么不一样的地方

ViewGroup是View的子类,也就是说ViewGroup本身就是一个View,但是它可以包含子View(当然子View也可能是一个ViewGroup),所以不难理解,上面所展示的伪代码表示的是ViewGroup 处理事件分发的流程。而View本身是不存在分发,所以也没有拦截方法(onInterceptTouchEvent),它只能在onTouchEvent方法中进行处理消费或者不消费。

通过下面的流程图,会更加清晰的帮助我们梳理事件分发机制

可以看出事件的传递过程都是从父View到子View。

子View可以通过requestDisallowInterceptTouchEvent方法干预父View的事件分发过程(ACTION_DOWN事件除外),而这就是我们处理滑动冲突常用的关键方法;

对于View(注意!ViewGroup也是View)而言,如果设置了onTouchListener,那么OnTouchListener方法中的onTouch方法会被回调。onTouch方法返回true,则onTouchEvent方法不会被调用(onClick事件是在onTouchEvent中调用)所以三者优先级是onTouch->onTouchEvent->onClick;

View 的onTouchEvent 方法默认都会消费掉事件(返回true),除非它是不可点击的(clickable和longClickable同时为false),View的longClickable默认为false,clickable需要区分情况,如Button的clickable默认为true,而TextView的clickable默认为false;

事件传递的机制,这里给出一些总结:

  • 一个事件系列以down事件开始,中间包含数量不定的move事件,最终以up事件结束;
  • 正常情况下,一个事件序列只能由一个View拦截并消耗;
  • 某个View拦截了事件后,该事件序列只能由它去处理,并且它的onInterceptTouchEvent不会再被调用;
  • 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件( onTouchEvnet返回false) ,那么同一事件序列中的其他事件都不会交给他处理,并且事件将重新交由他的父元素去处理,即父元素的onTouchEvent被调用;。
  • 如果View不消耗ACTION_DOWN以外的其他事件,那么这个事件将会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终消失的点击事件会传递给Activity去处理。
  • ViewGroup默认不拦截任何事件;
  • View没有onInterceptTouchEvent方法,一旦事件传递给它,它的onTouchEvent方法会被调用;
  • View的onTouchEvent默认消耗事件,除非他是不可点击的( clickable和longClickable同时为false) 。View的longClickable属性默认false,clickable默认属性分情况(如TextView为false,button为true);
  • View的enable属性不影响onTouchEvent的默认返回值;
  • onClick会发生的前提是当前View是可点击的,并且收到了down和up事件;
  • 事件传递过程总是由外向内的,即事件总是先传递给父元素,然后由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的分发过程,但是ACTION_DOWN事件除外;

三、滑动冲突的解决方式

1、外部拦截法

所谓外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。下面是伪代码:

public boolean onInterceptTouchEvent (MotionEvent event){ 
boolean intercepted = falseint x = (int) event.getX(); 
int y = (int) event.getY(); 
switch (event.getAction()) { 
case MotionEvent.ACTION_DOWN: 
    intercepted = false; 
    break; 
case MotionEvent.ACTION_MOVE: 
    if (父容器需要当前事件) { 
    intercepted = true; 
    } else { 
    intercepted = false; 
    }  
    break; 
case MotionEvent.ACTION_UP: 
    intercepted = false; 
    break; 
default :  
    break; 
}  
mLastXIntercept = x; 
mLastYIntercept = y; 
return intercepted; 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

针对不同冲突,只需修改父容器需要当前事件的条件即可。其他不需修改也不能修改。

ACTION_DOWN:必须返回false。因为如果返回true,后续事件都会被拦截,无法传递给子View;

ACTION_MOVE:根据需要决定是否拦截;

ACTION_UP:必须返回false。如果拦截,那么子View无法接受up事件,无法完成click操作。而如果是父容器需要该事件,那么在ACTION_MOVE时已经进行了拦截,根据上一节的结论3,ACTION_UP不会经过onInterceptTouchEvent方法,直接交给父容器处理;

2、内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗,否则就交由父容器进行处理。这种方法与Android事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。下面是伪代码:

public boolean dispatchTouchEvent ( MotionEvent event ) { 
int x = (int) event.getX(); 
int y = (int) event.getY(); 
switch (event.getAction) { 
case MotionEvent.ACTION_DOWN: 
    parent.requestDisallowInterceptTouchEvent(true); 
    break; 
case MotionEvent.ACTION_MOVE: 
    int deltaX = x - mLastX; 
    int deltaY = y - mLastY; 
    if (父容器需要此类点击事件) { 
        parent.requestDisallowInterceptTouchEvent(false); 
    }  
    break; 
case MotionEvent.ACTION_UP: 
    break; 
default :  
    break; 
}  
mLastX = x; 
mLastY = y; 
return super.dispatchTouchEvent(event); 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

除了子元素需要做处理外,父元素也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。因此,父元素要做以下修改:

public boolean onInterceptTouchEvent (MotionEvent event) { 
    int action = event.getAction(); 
    if(action == MotionEvent.ACTION_DOWN) { 
        return false; 
    } else { 
        return true; 
    } 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

四、事件分发机制-源码分析

从源码的角度来了解一下Android下的事件分发机制

1、首先看到Activity中的dispatchTouchEvent()方法源码:

public boolean dispatchTouchEvent(MotionEvent ev) { 
    // 如果是ACTION_DOWN事件会走这个语句,onUserInteraction()这个方法在系统中是空实现 
    if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
        onUserInteraction(); 
    } 
    /** 
     * 主要看一下这行代码 
     * getWindow()表示获取Window的子类PhoneWindow对象 
     * 也就是说调用PhoneWindow中的superDispatchTouchEvent(ev)方法,判断是否有控件处理事件 
     */ 
    if (getWindow().superDispatchTouchEvent(ev)) { 
        return true; 
    } 
    // 如果没有控件能处理事件,就走这一行代码,调用Activity的onTouchEvent()方法处理事件 
    return onTouchEvent(ev); 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

2、接着进入到PhoneWindow中的,查看superDispatchTouchEvent(ev)这个方法:

@Override 
public boolean superDispatchTouchEvent(MotionEvent event) { 
    return mDecor.superDispatchTouchEvent(event); 
} 
  • 1.
  • 2.
  • 3.
  • 4.

在PhoneWindow类的superDispatchTouchEvent(ev)方法中,直接调用了mDecor对象的superDispatchTouchEvent(ev)方法,mDecore其实就是继承至FrameLayout的DecorView的对象。在《Activity的组成》这篇博客中贴出了DecorView类的定义源码。

接着查看类中的superDispatchTouchEvent(ev)这个方法:

public boolean superDispatchTouchEvent(MotionEvent event) { 
    return super.dispatchTouchEvent(event); 
} 
  • 1.
  • 2.
  • 3.

只有一句代码,super.dispatchTouchEvent(event),调用父类的dispatchTouchEvent(event)方法,也就是FrameLayout的dispatchTouchEvent(event)方法,查看FrameLayout类会发现FrameLayout并没有重写dispatchTouchEvent(event)方法,那么就是使用的ViewGroup中的dispatchTouchEvent(event)方法。

到这里也就完全说明了Activity在做事件分发时调用的是ViewGroup中的dispatchTouchEvent()方法。

3、查看ViewGroup中的dispatchTouchEvent()方法:

@Override 
public boolean dispatchTouchEvent(MotionEvent ev) { 
    ... 
    boolean handled = false; 
    // 过滤触摸安全策略,如果是false(窗口或控件被遮住了时),直接跳出触摸事件 
    // 如果应该分发事件(调用onTouch()或onTouchEvdent()方法),则返回True;如果应该删除事件,则返回false 
    if (onFilterTouchEventForSecurity(ev)) { 
        ... 
        /** 
         * 如果是DOWN事件就先将mFirstTouchTarget设置为null, 
         * 然后在resetTouchState()方法中重置状态 
         */ 
        if (actionMasked == MotionEvent.ACTION_DOWN) { 
            cancelAndClearTouchTargets(ev); 
            resetTouchState(); 
        } 
        // 定义变量intercepted标记ViewGroup是否拦截Touch事件的传递. 
        final boolean intercepted; 
        // 事件为ACTION_DOWN或者mFirstTouchTarget不为null(有控件消费touch事件) 
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { 
            //判断disallowIntercept(禁止拦截)标志位 
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 
            //当没有禁止拦截时 
            if (!disallowIntercept) { 
                // 调用onInterceptTouchEvent(ev)方法,并将返回值赋给intercepted 
                intercepted = onInterceptTouchEvent(ev); 
                ev.setAction(action); 
            } else { 
                 //当禁止拦截时,指定intercepted = false,表示不拦截事件 
                intercepted = false; 
            } 
        } else { 
            //当事件不是ACTION_DOWN并且mFirstTouchTarget为null(没有控件消费touch事件)时 
            //设置 intercepted = true,表示ViewGroup执行Touch事件拦截的操作。 
            intercepted = true; 
        } 
        ... 
        // 事件分发 
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; 
        TouchTarget newTouchTarget = null; 
        boolean alreadyDispatchedToNewTouchTarget = false; 
        //不是ACTION_CANCEL事件并且intercepted为false(ViewGroup不拦截事件onInterceptTouchEvent()方法返回false) 
        if (!canceled && !intercepted) { 
            //处理ACTION_DOWN事件 
            if (actionMasked == MotionEvent.ACTION_DOWN 
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) 
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 
                final int actionIndex = ev.getActionIndex();  
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex):TouchTarget.ALL_POINTER_IDS; 
                removePointersFromTouchTargets(idBitsToAssign); 
                final int childrenCount = mChildrenCount; 
                if (childrenCount != 0) { 
                    // 依据Touch坐标寻找孩子控件来消费Touch事件 
                    final View[] children = mChildren; 
                    final float x = ev.getX(actionIndex); 
                    final float y = ev.getY(actionIndex); 
                    final boolean customOrder = isChildrenDrawingOrderEnabled(); 
                    // 遍历所有孩子控件,判断哪个消费Touch事件 
                    for (int i = childrenCount - 1; i >= 0; i--) { 
                        final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; 
                        final View child = children[childIndex]; 
                        if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { 
                            continue; 
                        } 
                        newTouchTarget = getTouchTarget(child); 
                        if (newTouchTarget != null) { 
                            // 找到消费Touch事件的孩子控件,跳出循环,并用newTouchTarget表示孩子控件 
                            newTouchTarget.pointerIdBits |= idBitsToAssign; 
                            break; 
                        } 
                        resetCancelNextUpFlag(child); 
                        // 没有跳出循环,走到这一步,就会调用dispatchTransformedTouchEvent()方法,将事件传给孩子控件做递归处理,第三个参数不为null 
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 
                           ... 
                        } 
                    } 
                } 
                /** 
                 * 如果在循环中没有孩子控件消费事件并且之前的mFirstTouchTarget不为空 
                 */ 
                if (newTouchTarget == null && mFirstTouchTarget != null) { 
                    // 将mFirstTouchTarget的赋给newTouchTarget 
                    newTouchTarget = mFirstTouchTarget; 
                    while (newTouchTarget.next != null) { 
                        newTouchTarget = newTouchTarget.next; 
                    } 
                    // newTouchTarget指向了最初的TouchTarget 
                    newTouchTarget.pointerIdBits |= idBitsToAssign; 
                } 
            } 
        } 
        /** 
         * 分发Touch事件至目标控件(target),以上过程主要针对ACTION_DOWN, 
         * 如果不是(上一步中判断intercepted变量),比如ACTION_MOVE和ACTION_UP,就是从此处开始执行 
         */ 
        if (mFirstTouchTarget == null) { 
            /** 
             * mFirstTouchTarget为null表示Touch事件未被消费或Touch事件被拦截了, 
             * 则调用ViewGroup的dispatchTransformedTouchEvent()方法,递归处理,第三个参数为null 
             */ 
            handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS); 
        } else { 
            /** 
             * mFirstTouchTarget不为null表示找到了可以消费Touch事件的子View 
             * 并且MOVE或UP事件可以传递到该子View 
             */ 
            TouchTarget predecessor = null; 
            // 将找到的可以消费事件的mFirstTouchTarget赋给目标控件(target) 
            TouchTarget target = mFirstTouchTarget; 
            while (target != null) { 
                final TouchTarget next = target.next; 
                // 如果已经分发到新的控件并且消费事件的目标控件就是新的控件 
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { 
                    handled = true; 
                } else { 
                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; 
                    // 否则调用dispatchTransformedTouchEvent()方法进行递归处理,第三个参数不为null 
                    if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { 
                        handled = true; 
                    } 
                   ... 
                } 
                predecessor = target; 
                target = next; 
            } 
        } 
        /** 
         * 如果是ACTION_UP和ACTION_CANCEL事件,还原状态 
         */ 
        if (canceled|| actionMasked == MotionEvent.ACTION_UP 
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 
            resetTouchState(); 
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { 
            ... 
        } 
    } 
    ... 
    return handled; 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.

我们可以看到在上面的方法中,调用的onInterceptTouchEvent()判断是否需要拦截事件。

查看ViewGroup中的dispatchTransformedTouchEvent()方法:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 
            View child, int desiredPointerIdBits) { 
    final boolean handled; 
    ... 
        if (child == null) { 
            // 如果孩子控件为空,就调用View的dispatchTouchEvent()方法,在View的dispatchTouchEvent()方法中会调用onTouchEvent()方法 
            handled = super.dispatchTouchEvent(event); 
        } else { 
            // 如果孩子控件不为空,就调用孩子控件的dispatchTouchEvent()方法 
            // 在此处孩子控件也还有可能是ViewGroup,所以就是继续调用ViewGroup的dispatchTouchEvent()方法 
            handled = child.dispatchTouchEvent(event); 
        } 
        event.setAction(oldAction); 
        return handled; 
    } 
   ... 
    transformedEvent.recycle(); 
    return handled; 
} 
查看ViewGroup中onInterceptTouchEvent()方法: 
public boolean onInterceptTouchEvent(MotionEvent ev) { 
    return false; 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

在ViewGroup中,没有重写onTouchEvent()方法,所以调用的是View中的onTouchEvent()方法。

4、在View类中,首先看一下View中的dispatchTouchEvent()方法:

public boolean dispatchTouchEvent(MotionEvent event) { 
    // If the event should be handled by accessibility focus first. 
    if (event.isTargetAccessibilityFocus()) { 
        // We don't have focus or no virtual descendant has it, do not handle the event. 
        if (!isAccessibilityFocusedViewOrHost()) { 
            return false; 
        } 
        // We have focus and got the event, then use normal event dispatch. 
        event.setTargetAccessibilityFocus(false); 
    } 
    boolean result = false; 
    if (mInputEventConsistencyVerifier != null) { 
        mInputEventConsistencyVerifier.onTouchEvent(event, 0); 
    } 
    // 如果是DOWN事件,重置状态 
    final int actionMasked = event.getActionMasked(); 
    if (actionMasked == MotionEvent.ACTION_DOWN) { 
        stopNestedScroll(); 
    } 
    // 过滤触摸安全策略,如果是false(窗口或控件被遮住了时),直接跳出触摸事件 
    // 如果应该分发事件(调用onTouch()或onTouchEvdent()方法),则返回True;如果应该删除事件,则返回false 
    if (onFilterTouchEventForSecurity(event)) { 
        ListenerInfo li = mListenerInfo; 
        if (li != null && li.mOnTouchListener != null 
                && (mViewFlags & ENABLED_MASK) == ENABLED 
                && li.mOnTouchListener.onTouch(this, event)) { 
            // 当前控件是可用(enabled)的并且View调用了setOnTouchListener()方法且返回了true,那么就设置result为true 
            result = true; 
        } 
        // result为false,表示没有调用setOnTouchListener()方法或该方法返回false,那么就调用 
        // View的onTouchEvent()方法 
        if (!result && onTouchEvent(event)) { 
            result = true; 
        } 
    } 
    if (!result && mInputEventConsistencyVerifier != null) { 
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); 
    } 
    // 如果是UP事件或CANCEL事件或者是DOWN事件但是该控件不能消费事件时,重置状态 
    if (actionMasked == MotionEvent.ACTION_UP || 
            actionMasked == MotionEvent.ACTION_CANCEL || 
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) { 
        stopNestedScroll(); 
    } 
    return result; 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.

最后查看View的onTouchEvent()方法:

public boolean onTouchEvent(MotionEvent event) { 
    final float x = event.getX(); 
    final float y = event.getY(); 
    final int viewFlags = mViewFlags; 
    final int action = event.getAction(); 
    // 判断是否有单击或长按事件 
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE 
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) 
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; 
    if ((viewFlags & ENABLED_MASK) == DISABLED) { 
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { 
            setPressed(false); 
        } 
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; 
        // 控件disabled 了,他还能消耗触摸事件,只是不相应她了 
        return clickable; 
    } 
    // 如果有代理,调用代理的方法 
    if (mTouchDelegate != null) { 
        if (mTouchDelegate.onTouchEvent(event)) { 
            return true; 
        } 
    } 
    // 对点击事件的具体处理,只要有点击事件,那么onTouchEvent()方法就返回了 true 
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { 
        switch (action) { 
            case MotionEvent.ACTION_UP: 
                // ... 
                    // mHasPerformedLongPress 表示长按事件的返回值,如果长按事件的的回调方法返回了true,那么在同一事件序列中,点击事件就不会调用了(否则会同时相应长按事件和点击事件) 
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { 
                        // This is a tap, so remove the longpress check 
                        removeLongPressCallback(); 
                        // Only perform take click actions if we were in the pressed state 
                        if (!focusTaken) { 
                            // Use a Runnable and post this rather than calling 
                            // performClick directly. This lets other visual state 
                            // of the view update before click actions start. 
                            if (mPerformClick == null) { 
                                mPerformClick = new PerformClick();  
                            } 
                            if (!post(mPerformClick)) { 
                                performClickInternal(); // 会调用 performClick()方法处理单击事件 
                            } 
                        } 
                    } 
                    if (mUnsetPressedState == null) { 
                        mUnsetPressedState = new UnsetPressedState(); 
                    } 
                    if (prepressed) { 
                        postDelayed(mUnsetPressedState, 
                                ViewConfiguration.getPressedStateDuration()); 
                    } else if (!post(mUnsetPressedState)) { 
                        // If the post failed, unpress right now 
                        mUnsetPressedState.run(); 
                    } 
                    removeTapCallback(); 
                } 
                mIgnoreNextUpEvent = false; 
                break; 
            case MotionEvent.ACTION_DOWN: 
                if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) { 
                    mPrivateFlags3 |= PFLAG3_FINGER_DOWN; 
                } 
                mHasPerformedLongPress = false; 
                if (!clickable) { 
                    checkForLongClick(0, x, y); 
                    break; 
                } 
                if (performButtonActionOnTouchDown(event)) { 
                    break; 
                } 
                // Walk up the hierarchy to determine if we're inside a scrolling container. 
                boolean isInScrollingContainer = isInScrollingContainer(); 
                // 根据是否在滚动容器中,使用不同方式调用长按事件的回调 
                if (isInScrollingContainer) { 
                    mPrivateFlags |= PFLAG_PREPRESSED; 
                    if (mPendingCheckForTap == null) { 
                        mPendingCheckForTap = new CheckForTap(); // 最终调用长按事件回调 
                    } 
                    mPendingCheckForTap.x = event.getX(); 
                    mPendingCheckForTap.y = event.getY(); 
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); 
                } else { 
                    // Not inside a scrolling container, so show the feedback right away 
                    setPressed(true, x, y); 
                    checkForLongClick(0, x, y); // 最终调用长按事件回调 
                } 
                break; 
            case MotionEvent.ACTION_CANCEL: 
                // ... 
                break; 
        } 
        // 只要有点击事件,那么onTouchEvent()方法就返回了 true 
        return true; 
    } 
    return false; 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.

从上面的代码来看,只要View的 CLICLABLE和LONG_CLICKABLE有一个为true,那么onTouchEvent()方法就会返回true,而且不管该View是否为DISABLE状态,其他情况返回false。true表示该控件可以消费事件,false表示该控件不能消费事件。现在,在回过头来看一下第一张图,是不是更加的清晰了呢。

5、最后,对于View 的 CLICKABLE 和 LONG_CLICKABLE默认值,LONG_CLICKABLE默认值为false,但是对于CLICKABLE就要根据具体的View来看了,确切的说是可点击的View的CLICKABLE值为true,如Button,不可点击的View的CLICKABLE值为false,比如TextView,但是当我们调用了View的 setOnClickListener(@Nullable OnClickListener l) 方法或者 setOnLongClickListener(@Nullable OnLongClickListener l) 方法就会将对应的值改为true。

public void setOnClickListener(@Nullable OnClickListener l) { 
    if (!isClickable()) { 
        setClickable(true); 
    } 
    getListenerInfo().mOnClickListener = l; 
} 
public void setOnLongClickListener(@Nullable OnLongClickListener l) { 
    if (!isLongClickable()) { 
        setLongClickable(true); 
    } 
    getListenerInfo().mOnLongClickListener = l; 
} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

总结

  • 对于ViewGroup和View的disatchTouchEvent()和onTouchEvent()方法,return true表示处理事件,事件终结;return false表示不处理事件,让事件回传到上一层的onTouchEvent()方法中,也就是父控件中的onTouchEvent()方法中;
  • 对于Activity的disatchTouchEvent()方法,如果没有重写,就会通过调用ViewGroup的disatchTouchEvent()方法开始分发事件,如果重写了,那么不管返回true还是false都会消费事件,不在将事件往下分发;
  • 对于dispatchTouchEvent()方法,如果开发者不重写,就会走系统中的默认实现,在ViewGroup中会调用ViewGroup的onInterceptTouchEvent()方法,而在View中会直接把事件分发给View的onTouchEvent()方法处理;
  • 对于ViewGroup而言,如果ViewGroup要自己处理事件,需要重写onInterceptTouchEvent()方法并且返回true,这样才会终止事件的传递,并调用ViewGroup的onTouchEvent()方法处理事件,否则调用系统onInterceptTouchEvent()方法,系统默认的返回值是false,不处理事件将事件传递下去;
  • 对于View而言,在View中是没有onInterceptTouchEvent()方法的,因为他没有孩子控件,不需要拦截,系统在dispatchTouchEvent()方法中默认会把事件分发给View的onTouchEvent()方法处理;
  • 在Android中,最开始获取到事件的是Activity,然后由Activity的dispatchTouchEvent()方法开始分发事件;
  • 如果一个事件由Activity开始下发,但是所有的控件都不处理事件,最终就会回到Activity的onTouchEvent()方法,如果Activity也不消费事件,那么这个事件就丢失了。

 

责任编辑:武晓燕 来源: Android开发编程
相关推荐

2017-02-21 12:20:20

Android事件分发机制实例解析

2021-09-01 06:48:16

AndroidGlide缓存

2023-10-08 08:23:44

Android事件逻辑

2021-09-02 07:00:01

Glide流程Android

2016-12-08 10:19:18

Android事件分发机制

2021-09-30 07:36:51

AndroidViewDraw

2021-08-10 20:41:33

AndroidApp流程

2011-06-23 14:05:32

Qt 事件机制

2021-09-09 06:55:43

AndroidViewDragHel原理

2021-10-03 15:08:32

Android

2021-09-03 07:27:38

AndroidGlide管理

2021-09-07 06:40:25

AndroidLiveData原理

2021-09-06 13:12:05

前端JavaScript编程

2021-09-16 06:44:04

Android进阶流程

2021-10-15 09:19:17

AndroidSharedPrefe分析源码

2021-08-12 16:28:10

AndroidHandleLooper

2021-08-05 20:39:34

AndroidKotlinStandard.kt

2010-08-06 10:03:42

Flex事件

2011-06-23 13:10:39

Python 对象机制

2016-12-12 14:55:01

AndroidAndroid Vie
点赞
收藏

51CTO技术栈公众号