一、前言

android的事件分发,大多数人都是似懂非懂,很多时候就卡在事件冲突这一步。比如在按钮上不能滑动出侧边栏,比如说ViewPager和banner冲突。我之前也是这样,然后狠下心去看了一遍源码,并且看了很多大神的博客,然后以我自身的理解配合源码来查看一个事件的传递过程。源码用的是API-8的,因为版本越高,健壮性越好,代码阅读性越差。
因为篇幅比较长,所以更底层的代码我也不准备写了,日后有机会再研究。在看博客之前,我们需要先来了解一些事件分发的基本流程,然后再一步步的深入去研究。
这其中有三个关键方法,首先我们先来理解几个方法的方法名和它们的返回值所代表的意义。

  1. dispatchTouchEvent: 简单的理解就是分发Touch事件,如果return true,表示事件已经被消费,不继续分发。return false表示没有被消费,继续分发。
  2. onInterceptTouchEvent: 拦截Touch事件,ViewGroup在dispatchTouchEvent返回之前就会调用这个方法,根据onInterceptTouchEvent的返回值决定事件是否继续往下分发。onInterceptTouchEvent的默认返回值都是false,表示不拦截。想要拦截的话需要开发者自己去重载这个方法。
  3. onTouchEvent: 处理Touch事件,如果return ture,那么这个时间就消费掉了。

细心的网友发现了,dispatch无论return true还是false,都不往下继续分发,不对吧!
dispatchTouchEvent一般不会直接return true或false。而是将事件抛给onInterceptTouchEvent和onTouchevent处理,问它们需不需要,最后再由dispatchTouchEvent进行最终的返回。这是很多新手开发者的理解误区,包括以前的我……

二、事件分发的基本过程

事件是怎么产生的这种底层问题我们先不管,我们只从知道的地方开始说起——Activity。
当一个事件开始传递后,最先接收到的是当前的Activity。然后Activity调用public boolean dispatchTouchEvent(MotionEvent ev)开始分发事件。内部又会调用PhoneWindow的内部对象DecorView的superdispatchKeyEvent,也就是ViewGroup的dispatchTouchEvent开始事件分发,DecorView是最顶层的View。

在dispatchTouchEvent方法中,ViewGroup会遍历自身的child(move事件和up事件有点不同,不会遍历child,下面会另外说明)
,再去调用子child的dispathTouchEvent方法,直到该事件被消费。传递途中还有onInterceptTouchEvent和onTouchEvent方法参与。

事件是由最外层的View开始传递,然后结果从最底层往外层返回。
如下图:ViewGroupA,ViewGroupB ,View的关系: A 是B的parent, B是C的parent。
图中的call是“调用”

我们再用代码来看下大致流程。
我们先建一个项目,然后写几个类继承FrameLayout,重写dispatchEvnet、onInterceptTouchEvent,onTouchEvent几个方法,看log输出。

ViewGroupA,B一样。

package com.aitsuki.touchevent;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.FrameLayout;/** * Created by AItsuki on 2015/12/30. */public class ViewGroupA extends FrameLayout {    public ViewGroupA(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e("Event","===ViewGroup:A======onTouchEvent===============Down");                break;            case MotionEvent.ACTION_MOVE:                Log.e("Event","===ViewGroup:A======onTouchEvent===============Move");                break;            case MotionEvent.ACTION_UP:                Log.e("Event","===ViewGroup:A======onTouchEvent===============Up");                break;        }        return super.onTouchEvent(event);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e("Event","===ViewGroup:A======onInterceptTouchEvent======Down");                break;            case MotionEvent.ACTION_MOVE:                Log.e("Event","===ViewGroup:A======onInterceptTouchEvent======Move");                break;            case MotionEvent.ACTION_UP:                Log.e("Event","===ViewGroup:A======onInterceptTouchEvent======Up");                break;        }        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e("Event","===ViewGroup:A======dispatchTouchEvent========Down");                break;            case MotionEvent.ACTION_MOVE:                Log.e("Event","===ViewGroup:A======dispatchTouchEvent========Move");                break;            case MotionEvent.ACTION_UP:                Log.e("Event","===ViewGroup:A======dispatchTouchEvent========Up");                break;        }        return super.dispatchTouchEvent(ev);    }}

MyView

package com.aitsuki.touchevent;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;/** * Created by AItsuki on 2015/12/30. */public class MyView extends View {    public MyView(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e("Event","===View=============onTouchEvent===============Down");                break;            case MotionEvent.ACTION_MOVE:                Log.e("Event","===View=============onTouchEvent===============Move");                break;            case MotionEvent.ACTION_UP:                Log.e("Event","===View=============onTouchEvent===============Up");                break;        }        return super.onTouchEvent(event);    }    @Override    public boolean dispatchTouchEvent(MotionEvent event) {        switch (event.getAction()) {            case MotionEvent.ACTION_DOWN:                Log.e("Event","===View=============dispatchTouchEvent=========Down");                break;            case MotionEvent.ACTION_MOVE:                Log.e("Event","===View=============dispatchTouchEvent=========Move");                break;            case MotionEvent.ACTION_UP:                Log.e("Event","===View=============dispatchTouchEvent=========Up");                break;        }        return super.dispatchTouchEvent(event);    }}

布局activity_main.xml

<?xml version="1.0" encoding="utf-8"?><FrameLayout    xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent">    <com.aitsuki.touchevent.ViewGroupA        android:layout_width="300dp"        android:layout_height="300dp"        android:background="#FF6A6A"        android:layout_gravity="center">        <com.aitsuki.touchevent.ViewGroupB            android:layout_width="200dp"            android:layout_height="200dp"            android:layout_gravity="center"            android:background="#9ACD32">            <com.aitsuki.touchevent.MyView                android:layout_width="100dp"                android:layout_height="100dp"                android:background="#1E90FF"                android:layout_gravity="center"/>        </com.aitsuki.touchevent.ViewGroupB>    </com.aitsuki.touchevent.ViewGroupA></FrameLayout>

布局预览:

红色:ViewGroupA
绿色:ViewGroupB
蓝色:View
现在我点击一下蓝色区域(View),看Log输出。

我们给ViewGroupA加上拦截之后再看看(让onInterceptTouchEvent返回true)

好了,基本流程和我们的图是一致的。
但是要注意 一、前言 的那段红字

三、 View的源码走读

大概理解了事件的传递过程之后,我们来看一下源码。
为什么先看View的源码而不看ViewGroup的原因有两点:
1. View是ViewGroup的父类,ViewGroup的onTouchEvent方法继承自View, 并没有重写。
2. View没有child,事件传递简单,不会打消各位的阅读源码积极性。
那么,开始吧。

先来看View的dispatchTouchView方法,我直接在上面注释。
mViewFlags:一种通过位运算记录开关的方式。mViewFlags一个32位的int值,用每一位的0或1记录属性。比如第1位的0和1记录focusable(是否可以获取焦点)

    /** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */    public boolean dispatchTouchEvent(MotionEvent event) {        // 如果这个View设置了触摸监听onTouchListener并且View是可用的,并且onTouch返回的是true        // 那么事件就消费掉了,传递结束。        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&                mOnTouchListener.onTouch(this, event)) {            return true;        }        // 否则,交给onTouchEvent处理(View中没有onInterceptTouchEvent方法,因为它没有        // child了,不需要有拦截方法)        return onTouchEvent(event);    }// 注意看谷歌工程师的注释,它们称消费事件的View为target view,如果这里的dispatch或者说// onTouchEvent返回true,那么这个View就是target View了。先记下来

继续看onTouchEvent

 /** * Implement this method to handle touch screen motion events. * * @param event The motion event. * @return True if the event was handled, false otherwise. */    public boolean onTouchEvent(MotionEvent event) {        final int viewFlags = mViewFlags;        // 如果View不可用,但它却是可点击的(clickable属性),那么仍然消费这个事件(但是不执行任何        // 操作,也就是不会响应)。也就是说,只要有clickable属性,那么这个点击事件就必然被消费掉。        if ((viewFlags & ENABLED_MASK) == DISABLED) {            // A disabled view that is clickable still consumes the touch            // events, it just doesn't respond to them.            return (((viewFlags & CLICKABLE) == CLICKABLE ||                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));        }        // 这个是触摸代理,就是点击另一个View,这个view会响应点击事件。默认是null,开发者可以通过        // setTouchDelegate设置。详情请自行查看TouchDelegate        if (mTouchDelegate != null) {            if (mTouchDelegate.onTouchEvent(event)) {                return true;            }        }        // 如果View是可点击的,那么消费掉这个事件,否则返回给上层处理(parent)        if (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {            //...... 此处省略N行代码,没有return语句,我们pass。            return true;        }        return false;    }

好了,View处理事件的源码就这么点。一会就看完了,挺简单的。
从上面这两段代码可以得出的结论:

结论1:onTouch优先于onTouchEvent执行,并且onTouch消费掉事件后,onTouEvent不会再执行。
结论2:如果View是可点击的(clickable),那么事件一定会被消费掉,不会再继续传递。

我们来验证一下结论,用的还是 二、事件分发的基本过程 的那个项目。
我们给MyView设置touchListener并return true,然后点击蓝色区域看看log输出

activity代码:

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        MyView view = (MyView) findViewById(R.id.view);        view.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                switch (event.getAction()) {                    case MotionEvent.ACTION_DOWN:                        Log.e("Event","===View=============onTouch====================Down");                        break;                    case MotionEvent.ACTION_MOVE:                        Log.e("Event","===View=============onTouch====================Move");                        break;                    case MotionEvent.ACTION_UP:                        Log.e("Event","===View=============onTouch====================Up");                        break;                }                return true;            }        });    }}


结论1:onTouchEvent没有执行,验证正确。

将MainActivity设置侦听的代码注释掉,给View设置clickable属性,测试结论3

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        MyView view = (MyView) findViewById(R.id.view);        view.setClickable(true);// view.setOnTouchListener(new View.OnTouchListener() {// @Override// public boolean onTouch(View v, MotionEvent event) {// switch (event.getAction()) {// case MotionEvent.ACTION_DOWN:// Log.e("Event","===View=============onTouch====================Down");// break;// case MotionEvent.ACTION_MOVE:// Log.e("Event","===View=============onTouch====================Move");// break;// case MotionEvent.ACTION_UP:// Log.e("Event","===View=============onTouch====================Up");// break;// }// return true;// }// });    }}


View执行了onTouchEvent,消费掉了事件,不再传递,结论3验证正确

请各位网友理解了View的事件传递的结论消化之后再继续往下看,因为ViewGroup中多次调用到super.dispatchTouchEvent, 其实也就是调用View的dispatchTouchEvent,因为View就是ViewGroup的父类。

四、ViewGroup源码走读

因为注释比较多,所以有点影响阅读性,最好可以配合没有注释的源码(api-8 Android2.2)一起阅读。同时思考一下我的分析是否和你的一样,有什么错误也可以在评论中指出。

我们只需要看dispatch的源码就可以了,onInterceptTouchEvent默认都是return false,没有onTouchEvent。
和View一样,也是使用注释的方式来说明。

@Override    public boolean dispatchTouchEvent(MotionEvent ev) {        // 获取事件的类型和触摸坐标        final int action = ev.getAction();        final float xf = ev.getX();        final float yf = ev.getY();        final float scrolledXFloat = xf + mScrollX;        final float scrolledYFloat = yf + mScrollY;        // mTempRect初始是null的,这个Rect对象主要是用来记录child的可点击范围。        final Rect frame = mTempRect;        // mGroupFlags和mViewFlags一样是记录当前控件的状态。这里记录的是“是否允许拦截”这个属性。        // 可以通过requestDisallowInterceptTouchEvent这个方法进行设置,如果设置了这个属性,        // 那么ViewGroup就不会拦截child的事件了。        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;        // 如果是down事件,就遍历child进行分发。        if (action == MotionEvent.ACTION_DOWN) {            // 两位谷歌工程师在聊天么=。=,大概意思就是:            // 为什么一个View响应完down事件后还没有消失,继续响应了第二次down事件。            // xxx: 我们可能应该发送一个up事件,而不是down……            // target:在之前也说过了,响应了down事件的那个View就是target,而在up或者cancel            // 事件执行后,这个target应该会被重置为null。            // 但是这里居然不是空的。博主我也不知道什么回事=。=            // 如果不是null,那么就让它重置为null。            if (mMotionTarget != null) {                // this is weird, we got a pen down, but we thought it was                // already down!                // XXX: We should probably send an ACTION_UP to the current                // target.                mMotionTarget = null;            }            // If we're disallowing intercept or if we're allowing and we didn't             // intercept            // 这里判断disallowIntercept有点多余,因为执行up或者cancel之后,这个属性就会被重            // 置为false(往下找)。而requestDisallowInterceptTouchEvent方法一般在child的            // dispatchTouchEvent中调用,但是事件还没有传到chid,那么这个方法也就不会执行,            // disallowIntercept肯定也只能是false。            // 我们无视掉这里的disallowIntercept,这里判断是否拦截child的事件。            if (disallowIntercept || !onInterceptTouchEvent(ev)) {                // reset this event's action (just to protect ourselves)                // 重置这个事件为donw事件,只是为了保证健壮性。上面那个disallowIntercept也是                // 为了健壮性么=。=                ev.setAction(MotionEvent.ACTION_DOWN);                // We know we want to dispatch the event down, find a child                // who can handle it, start with the front-most child.                // 不得不说谷歌工程师的注释也是很详细的=。=                // 我们想要分发这个down事件,遍历子View看谁可以持有它,从最上层的子View开始。                final int scrolledXInt = (int) scrolledXFloat;                final int scrolledYInt = (int) scrolledYFloat;                final View[] children = mChildren;                final int count = mChildrenCount;                // 这里开始遍历所有child                for (int i = count - 1; i >= 0; i--) {                    final View child = children[i];                    // 如果child是可见的,或者child正在进行动画(动画中的View这个我没细看                    // ,无视掉好了)。                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE                            || child.getAnimation() != null) {                        // 获得child的有效点击范围,判断点击事件是否点在此child中。                        child.getHitRect(frame);                        if (frame.contains(scrolledXInt, scrolledYInt)) {                            // offset the event to the view's coordinate system                            // 获取到事件的坐标是屏幕的绝对坐标,要转成child的相对坐标。                            final float xc = scrolledXFloat - child.mLeft;                            final float yc = scrolledYFloat - child.mTop;                            ev.setLocation(xc, yc);                            // 如果点击事件在此child中,重置属性:CANCEL_NEXT_UP_EVENT                            // 该属性的描述:Indicates whether the view is temporarily                             // detached。                            // 标记哪一个View是暂时和parent分离,就是和当前这个ViewGroup分                            // 离。                            // 有什么应用场景我也不知道,不过abslistView中是用到了,那边的源码                            // PS:位运算没忘吧=。=,比如CANCEL_NEXT_UP_EVENT = 0000 1000                             // 取反 1111 0111。 与上这个数就是取消 CANCEL_NEXT_UP_EVENT                            // 这个属性……                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                            // 然后这里调用child的事件分发                            // 这里会出现几种情况                            // 1:child是View,那么回想一下View的源码吧(三、View源码走读)                            // 如果child消费了此down事件,那么这个child就是target了。                            // 如果不消费,那么返回给当前的ViewGroup的onTouchEvent消费。                            // 2:child是ViewGroup,那么继续调用child的dispatch,                            // 继续遍历child的child(噗,孙子),直到有响应这个事件的。                            // 如果最底层的child也是ViewGroup,那么请直接跳过这个遍历,                            // 往下看…… if(target == null)那里。                            // 调用super.dispatchEvent。                            // 也就是说,如果最底层的child是ViewGroup,那么将它作为View处理。                            if (child.dispatchTouchEvent(ev))  {                                // Event handled, we have a target now.                                // 如果响应了down事件,那么这个child就是target                                mMotionTarget = child;                                return true;                            }                            // The event didn't get handled, try the next view.                            // Don't reset the event's location, it's not                            // necessary here.                        }                    }                }            }        }// 思考:dispatch中调用child的dispatch方法,这看起来是不是像递归遍历。直到有child响应down事件,// 否则将所有child遍历完后这个事件流产。// 注意:child有可能是View,也有可能是ViewGroup。它们两个的dispatch方法是不同的。如果是View,// 就代表这个事件分发已经到了最底下的View了。//===================================================================================        // 判断事件是否是up或者cancel        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||                (action == MotionEvent.ACTION_CANCEL);        // 如果是up或者cancel事件,将FLAG_DISALLOW_INTERCEPT(disallowIntercept是通过        // 这个值计算的)这个属性移除。        // 就是说,既然是up事件和cancel事件已经接收到,那么就代表这在target上的事件结束了,        // 重置这个属性,不然会影响到下一个事件。        if (isUpOrCancel) {            // Note, we've already copied the previous state to our local            // variable, so this takes effect on the next event            // 我们已经复制了上一个的状态到变量,所以这里就影响了下一个事件。            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        }        // The event wasn't an ACTION_DOWN, dispatch it to our target if        // we have one.        // 如果traget存在,并且这个事件不是down,那么就将事件交给target处理        // (谁响应了down事件,后续事件就交给谁处理)        final View target = mMotionTarget;        if (target == null) {            // We don't have a target, this means we're handling the            // event as a regular view.            // 如果target为null,那么代表down事件没有被响应(也可能是target的后续事件被拦截,            // 那么target也会被重置为null)            ev.setLocation(xf, yf);            // 如果当前的View或者说ViewGroup已经从parent中分离,那么将事件改为cancel            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {                ev.setAction(MotionEvent.ACTION_CANCEL);                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;            }            // 这里关键了,如果这个ViewGroup没有child,那么它就会走到这里,然后调用View的事件            // 分发, 将自己作为一个View处理。            // 当返回dispatch返回true之后,这个ViewGroup本身就是target了,不过这个target引用            // 是在parent中,不是当前的=。=            return super.dispatchTouchEvent(ev);        }        // 代码能走到这里的话就代表,这不是一个down事件。        // if have a target, see if we're allowed to and want to intercept its        // events        // 如果我们有一个target,我们就会将后续的事件都交给target处理(跳过这个if判断直接往        // 下看)。        // 结合前面分析,从这里看出,如果某个child请求了requestDisallow,那么child就肯定能        // 接收到move和up事件,前提是没有拦截down事件。        // 假设我们拦截了后续事件(不拦截child的down事件,但是拦截了child的其他事件)        if (!disallowIntercept && onInterceptTouchEvent(ev)) {            final float xc = scrolledXFloat - (float) target.mLeft;            final float yc = scrolledYFloat - (float) target.mTop;            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;            ev.setAction(MotionEvent.ACTION_CANCEL);            ev.setLocation(xc, yc);            // 将事件改成cancel,然后交给target的dispatch处理。(这里可以看出,如果有            // target响应的down事件,但是target后续事件被拦截,那么就会传一个cancel给target)            // 不管它有没有响应,我们都将target置为空,就是说,后续事件当做没有target来处理。            if (!target.dispatchTouchEvent(ev)) {                // target didn't handle ACTION_CANCEL. not much we can do                // but they should have.            }            // clear the target            mMotionTarget = null;            // Don't dispatch this event to our own view, because we already            // saw it when intercepting; we just want to give the following            // event to the normal onTouchEvent().            return true;        }        // 如果事件是up或者cancel,那么将target重置为null。因为响应down事件的就是target,        // 既然已经抬起手指了,那target就没了。        if (isUpOrCancel) {            mMotionTarget = null;        }        // finally offset the event to the target's coordinate system and        // dispatch the event.        // 将坐标转成target的相对坐标。        final float xc = scrolledXFloat - (float) target.mLeft;        final float yc = scrolledYFloat - (float) target.mTop;        ev.setLocation(xc, yc);        // 如果target已经被分离出去(相当于remove),那么将事件改成cancel。        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {            ev.setAction(MotionEvent.ACTION_CANCEL);            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;            mMotionTarget = null;        }        // 将所有后续事件都交给target处理。        return target.dispatchTouchEvent(ev);    }

注释太多看起来有点乱,不过没关系,大家配合无注释的代码一起看就行了。我只是将每一行的代码的作用注释出来,各种结论还是需要推测。

五、结论

现在我们来总结一下分析源码得出的一些结论:

结论1:onTouch优先于onTouchEvent执行,并且onTouch消费掉事件后,onTouEvent不会再执行。

结论2:如果View是可点击的(clickable),那么事件一定会被消费掉,不会再继续传递。

结论3: 如果ViewGroup在onInterceptTouchEvent中拦截了child的事件,那么这个事件会交给ViewGroup的onTouchEvent处理。

结论4:onInterceptTouchEvent方法在一次事件序列(down到up或者cancel的过程)中,只要返回true就不会再调用,或者说只要拦截过一次之后就不会再调用,直到下一次down事件开始前。

结论5:响应了down事件的View被称为target,Android会将后续事件都交给target处理。

结论6:在结论5的基础上,如果onInterceptTouchEvent中拦截了target的其他事件,比如move或up,那么target就会接受到一个cancel事件,并且将target置为null,后续的事件交给target的parent处理。(比如,我可以让child响应down事件,然后只拦截它的move事件,那么我就可以接受到除了down的所有后续事件了,而child则会接收到一个cancel事件,这样就可以解决滑动事件和按钮冲突的问题了。)

结论7:在结论6的基础上,child可以通过requestDisallowInterceptTouchEvent请求parent不拦截他的事件,前提是child能响应到down事件。(例如:parent在onIntercept中拦截了事件,child就没机会请求了。再例如:parent不拦截down事件,但是拦截了move和up事件,这时候requestDisallowInterceptTouchEvent就派上用场了)

结论8: 如果一个View在处理一个事件序列(down到up或者cancel的过程)的时候,parent将他remove掉了,那么这个View会接收到一个cancel事件。

结论的验证过程都很简单,因为文章篇幅已经太长了不打算贴出来,请自行验证结论的正确性,也可惜选择相信我的验证结果。

六、写在后面

这博客真的很难写,花了两天多的时间。不知道应该怎么写才能更加简洁易懂,最后干脆以注释的方式了。
虽然写了七八个结论,但是这并不是全部,我也很难将所有结论一个一个列出来。

分析源码,读懂源码的好处就是,当你发现事件冲突的时候,不会像无头苍蝇一样在百度胡乱搜方案,最后还搞得一头雾水。而是让你自己有能力解决冲突,能找到冲突的源头。

至于解决事件冲突的一些案例,我有空的话可能会整理几个出来,但是写着博客有种身心疲惫的感觉_(:з」∠)_,休息一段时间再说,就这样,下次见。

更多相关文章

  1. android 短信接收流程分析——为更好的拦截短信做准备
  2. 键盘按下和抬起事件(keydown,keyup)——原创
  3. 关于Android滑动冲突的解决方法(二)
  4. Android:分析onXXX事件监听器中的两个参数position和id
  5. android HOME、back(按钮、事件)截取获得,综合解决方案和分析,包含an
  6. Android事件管理源码剖析
  7. Android小部件布局大小和点击事件
  8. Android(安卓)API Guides---Drag and Drop
  9. 滑轮控件研究二、GestureDetector的深入研究

随机推荐

  1. Android下载apk文件并安装
  2. Android(安卓)设计模式 之 观察者模式
  3. Android学习笔记_4_单元测试
  4. Android(安卓)recovery 下恢复备份文件
  5. AndroidManifest.xml文件详解(instrumenta
  6. Android(安卓)Studio 1.2 编码问题
  7. listview 中Item中含有Button 等造成Item
  8. Android(安卓)NDK r6 windows ,Cygwin 1.
  9. Android(安卓)之LayerDrawable层叠样式la
  10. 支付宝转账小demo(不需要库)