最近在开发中遇到view滑动冲突的问题,由于一开始就知道这个问题与view事件分发有关,之后在网上看了几篇关于事件分发的资料后,开发中遇到的问题很快便得到解决。 
在这里总结一下我对view事件分发的理解。
首先,看下事件分发流程图: 
在对view的事件分发机制进行分析前,我们可以通过一个demo看看Button的事件处理的流程。 
在布局文件中添加一个button控件,然后在代码中实现Button的setOnClickListener和setOnTouchListener方法,注册Click监听和Touch监听。
/**
 * button事件
 */
private void showButtonTouch() {
    mBtn = (Button) findViewById(R.id.btn);
    mBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.e(TAG, "Button onClick");
        }
    });
    mBtn.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "Button onTouch " + "ACTION_UP");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "Button onTouch " + "ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "Button onTouch " + "ACTION_DOWN");
                    break;
            }
            return false;
        }
    });
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button事件测试"/>
</LinearLayout>
demo运行起来之后点击button,看下log日志(在点击时移动一下保证ACTION_MOVE事件能被触发): 
在这里通过日志可以看出事件处理的流程。
在View的源码中,我们可以看到dispatchTouchEvent方法,这个方法可以理解为是View事件分发的入口。(代码基于4.0.3即API 15,建议大家在理解源码时不要看太高的版本,高版本源码会有过多的优化,会妨碍我们对于代码主要功能逻辑的理解)
以下是View中dispatchTouchEvent方法代码:
public boolean dispatchTouchEvent(MotionEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onTouchEvent(event, 0);
    }
    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        //这个是View事件分发的主要逻辑判断
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        if (onTouchEvent(event)) {
            return true;
        }
    }
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}
在这整个方法中,事件分发的关键就在这个判断中 (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) 。
如果这个判断为true,那么该方法返回true;否则,执行onTouchEvent()方法并根据onTouchEvent方法的返回值返回具体结果。
li != null:代码中ListenerInfo对象li,是View内定义的一个静态类,这个类内部定义了View中所有Listener相关的类,一般情况下这个类不为空,在这里不做过多解释(2.3版本源码中没有该对象)。
li.mOnTouchListener != null:是否为空,mOnTouchListener 对象就是我们通过mBtn.setOnTouchListener(new View.OnTouchListener() {})设置的,所以这里不为空。
(mViewFlags & ENABLED_MASK) == ENABLED:判断控件是否是enable,很显然为true。
li.mOnTouchListener.onTouch(this, event):最后就是判断onTouch方法中返回值是否为true,onTouch方法就是我们在Button控件注册Touch监听时@Override的onTouch方法。
在前面的demo中,因为mOnTouchListener.onTouch(this, event)方法的返回值为false,我们在log中看到button先执行touch事件在执行click事件。在这里我们将onTouch方法的返回值改为true,可以看到以下log: 
在log中可以看到click方法没有被执行,这又是为什么呢?
其实在这里大家通过阅读dispatchTouchEvent方法代码可以想到,因为onTouch返回值为true,所以这个判断条件成立即dispatchTouchEvent方法返回true。if (onTouchEvent(event))->onTouchEvent方法没有被执行,会不会是由于这个原因导致click方法没有被执行呢? 很显然我们要看看onTouchEvent方法的源码。
以下是onTouchEvent方法的源码:
public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PRESSED) != 0) {
            mPrivateFlags &= ~PRESSED;
            refreshDrawableState();
        }
        // 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));
    }
    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }
    //这是onTouchEvent代码主要逻辑功能
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
                if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
                    // take focus if we don‘t have it already and we should in
                    // touch mode.
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }
                    if (prepressed) {
                        // The button is being released before we actually
                        // showed it as pressed.  Make it show the pressed
                        // state now (before scheduling the click) to ensure
                        // the user sees it.
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                   }
                    if (!mHasPerformedLongPress) {
                        // 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)) {
                                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();
                }
                break;
            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;
                if (performButtonActionOnTouchDown(event)) {
                    break;
                }
                // Walk up the hierarchy to determine if we‘re inside a scrolling container.
                boolean isInScrollingContainer = isInScrollingContainer();
                // For views inside a scrolling container, delay the pressed feedback for
                // a short period in case this is a scroll.
                if (isInScrollingContainer) {
                    mPrivateFlags |= PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    mPrivateFlags |= PRESSED;
                    refreshDrawableState();
                    checkForLongClick(0);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
                removeTapCallback();
                break;
            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();
                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PRESSED) != 0) {
                        // Remove any future long press/tap checks
                        removeLongPressCallback();
                        // Need to switch from pressed to not pressed
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
        }
        return true;
    }
    return false;
}
对于onTouchEvent方法,主要的功能逻辑我们只需要从第23行代码开始阅读便可。 
首先判断View是否是可点击的,因为Button默认是可点击的,所以这个条件成立,我们可以进入到switch分支判断中。在这里给大家留下一个疑问,如果这个条件不成立即就是控件是不可点击的,会出现什么样的情况呢?
MotionEvent.ACTION_UP:在这个分支判断中我们可以看到在58行有个performClick()方法,我们进入到这个方法体中可以看到:
public boolean performClick() {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        return true;
    }
    return false;
}
在该方法中可以看到li.mOnClickListener.onClick(this)被调用了,到此处我们可以确认Button的click事件就是在onTouchEvent方法中调用的。
在onTouchEvent中曾留下一个疑问,如果判断控件是否可点击为false,会出现什么样的情况呢?
为了验证这个问题我们可以通过TextView和Button进行比较一下,因为TextView默认不可点击,Button默认可点击的。
首先在xml中添加TextView和Button,在代码中分别为他们注册setOnTouchListener监听,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button事件测试"/>
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_green_light"
        android:text="TextView事件测试" />
</LinearLayout>
/**
 * button事件
 */
private void showButtonTouch() {
    mBtn = (Button) findViewById(R.id.btn);
    mBtn.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "Button onTouch " + "ACTION_UP");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "Button onTouch " + "ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "Button onTouch " + "ACTION_DOWN");
                    break;
            }
            return false;
        }
    });
}
/**
 * TextView事件
 */
private void showImageTouch() {
    mTv = (TextView) findViewById(R.id.tv);
    mTv.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    Log.e(TAG, "ImageView onTouch " + "ACTION_UP");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e(TAG, "ImageView onTouch " + "ACTION_MOVE");
                    break;
                case MotionEvent.ACTION_DOWN:
                    Log.e(TAG, "ImageView onTouch " + "ACTION_DOWN");
                    break;
            }
            return false;
        }
    });
}
因为我们需要进入到onTouchEvent方法中,所以必须让setOnTouchListener.onTouch()方法的返回值为false,在这里我们看看demo运行起来后分别点击Button和TextView打印的log: 
发现Button的log和之前演示的一样,但是TextView在点击->移动->抬起的过程中只打印一次ACTION_DOWN的log,这是什么原因呢? 
在之前的onTouchEvent方法中,我们可以看到:
而当控件不可点击时,方法会直接到132行,return false。
这里需要指明onTouchEvent方法的返回值是touch事件层级传递的关键,说明如下:
现在就能解释上面TextView为什么只打印一句log了,因为onTouchEvent返回值为false,系统会认为ACTION_DOWN事件没有被执行完成,那么其他的touch事件就不能被触发了。
现在大家再去看事件分发流程图中的View部分想必就能看懂了吧!!!
原文:http://blog.csdn.net/kaifa1321/article/details/50999758