第一句话总是最重要的:
Android的拦截机制是一个自顶向下的事件分发与自底向上的事件响应机制
自顶向下的分发,就是我从View树的顶部开始向下分发事件
自底向上的响应,就是当事件传递到View树的底层,那么他就开始往上层层响应

View

View有2个方法 dispatchTouchEvent和onTouchEvent,源码如下:

3363        public boolean dispatchTouchEvent(MotionEvent event) {3364            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&3365                    mOnTouchListener.onTouch(this, event)) {3366                return true;3367            }3368            return onTouchEvent(event);3369        }

根据我上边的话,可以这么理解
dispatchTouchEvent 是用来 事件分发的
onTouchEvent 是用来事件响应的

View的onTouchEvent源码

3792        public boolean onTouchEvent(MotionEvent event) {3793            final int viewFlags = mViewFlags;3794    3795            if ((viewFlags & ENABLED_MASK) == DISABLED) {3796                // A disabled view that is clickable still consumes the touch3797                // events, it just doesn't respond to them.3798                return (((viewFlags & CLICKABLE) == CLICKABLE ||3799                        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));3800            }3801    3802            if (mTouchDelegate != null) {3803                if (mTouchDelegate.onTouchEvent(event)) {3804                    return true;3805                }3806            }3807    3808            if (((viewFlags & CLICKABLE) == CLICKABLE ||3809                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {3810                switch (event.getAction()) {3811                    case MotionEvent.ACTION_UP:3812                        if ((mPrivateFlags & PRESSED) != 0) {3813                            // take focus if we don't have it already and we should in3814                            // touch mode.3815                            boolean focusTaken = false;3816                            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {3817                                focusTaken = requestFocus();3818                            }3819    3820                            if (!mHasPerformedLongPress) {3821                                // This is a tap, so remove the longpress check3822                                if (mPendingCheckForLongPress != null) {3823                                    removeCallbacks(mPendingCheckForLongPress);3824                                }3825    3826                                // Only perform take click actions if we were in the pressed state3827                                if (!focusTaken) {3828                                    performClick();3829                                }3830                            }3831    3832                            if (mUnsetPressedState == null) {3833                                mUnsetPressedState = new UnsetPressedState();3834                            }3835    3836                            if (!post(mUnsetPressedState)) {3837                                // If the post failed, unpress right now3838                                mUnsetPressedState.run();3839                            }3840                        }3841                        break;3842    3843                    case MotionEvent.ACTION_DOWN:3844                        mPrivateFlags |= PRESSED;3845                        refreshDrawableState();3846                        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {3847                            postCheckForLongClick();3848                        }3849                        break;3850    3851                    case MotionEvent.ACTION_CANCEL:3852                        mPrivateFlags &= ~PRESSED;3853                        refreshDrawableState();3854                        break;3855    3856                    case MotionEvent.ACTION_MOVE:3857                        final int x = (int) event.getX();3858                        final int y = (int) event.getY();3859    3860                        // Be lenient about moving outside of buttons3861                        int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();3862                        if ((x < 0 - slop) || (x >= getWidth() + slop) ||3863                                (y < 0 - slop) || (y >= getHeight() + slop)) {3864                            // Outside button3865                            if ((mPrivateFlags & PRESSED) != 0) {3866                                // Remove any future long press checks3867                                if (mPendingCheckForLongPress != null) {3868                                    removeCallbacks(mPendingCheckForLongPress);3869                                }3870    3871                                // Need to switch from pressed to not pressed3872                                mPrivateFlags &= ~PRESSED;3873                                refreshDrawableState();3874                            }3875                        } else {3876                            // Inside button3877                            if ((mPrivateFlags & PRESSED) == 0) {3878                                // Need to switch from not pressed to pressed3879                                mPrivateFlags |= PRESSED;3880                                refreshDrawableState();3881                            }3882                        }3883                        break;3884                }3885                return true;3886            }3887    3888            return false;

如果消化了点击事件的Action_Down,则返回true。若不消耗则返回false,返回false则对应的dispatchTouchEvent也返回fasle。 则表示不拦截,继续向下分发事件。至于怎么继续向下分发呢,这个放在ViewGroup的事件分发机制来说。返回True 则表示这个View拦截这个事件,这样这个完整的点击事件(Action_Down到Up)都交给这个View来处理。

ViewGroup:
ViewGroup有三个方法dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent
dispatchTouchEvent 先看看源码里的一小段,就能马上理解ViewGroup的分发机制了

if (child.dispatchTouchEvent(ev))  {// Event handled, we have a target now.    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.

依次调用ViewGroup里所包含的childView的dispatchTouchEvent,倘若该ViewGroup里的childView,有一个选择了拦截,那么这个ViewGroup的dispatchTouchEvent就返回True,而事件的传递也就会在拦截的childView的onTouchEvent 里停止。如果ViewGroup里所有的childView的dispatchTouchEvent都返回false,即子View都不拦截,那么就相当于这个点击事件已经传递到底了,只能逐层向上响应,即调用ViewGroup的onTouchEvent,直到某个ViewGroup的onTouchEvent返回True,否则一直向上响应,直至rootView。

废话一堆不如代码来的实在。请看例子!
先写2个自定义的ViewGroup,ViewGroupB代码同ViewGroupA,只是在三个事件拦截的方法里加了打印信息

package com.dongua.toucheventtest;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.RelativeLayout;public class ViewGroupA extends RelativeLayout {    public ViewGroupA(Context context, AttributeSet attrs, int defStyle)    {        super(context, attrs, defStyle);    }    public ViewGroupA(Context context)    {        super(context);    }    public ViewGroupA(Context context, AttributeSet attrs)    {        super(context, attrs);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.i("click", "dispatchTouchEvent: ViewGroupA");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        Log.i("click", "onInterceptTouchEvent: ViewGroupA");        return super.onInterceptTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.i("click", "onTouchEvent: ViewGroupA");        return super.onTouchEvent(event);    }}

和一个自定义的View,这里先继承自TextView,因为TextView对点击事件是默认不响应,当然如果熟悉的话你继承一个Button,然后手动的在dispatchTouchEvent里返回fasle,使button不拦截也是ok 的~

package com.dongua.toucheventtest;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.widget.TextView;/** * Created by dongua on 2016/11/21. */public class ViewC extends TextView {    public ViewC(Context context) {        super(context);    }    public ViewC(Context context, AttributeSet attrs) {        super(context, attrs);    }    public ViewC(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.i("click", "dispatchTouchEvent: ViewC");        return super.dispatchTouchEvent(ev);    }    @Override    public boolean onTouchEvent(MotionEvent event) {        Log.i("click", "onTouchEvent: ViewC");        return super.onTouchEvent(event);    }}

然后写一下布局

<?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"    tools:context="com.dongua.toucheventtest.MainActivity">    <com.dongua.toucheventtest.ViewGroupA        android:id="@+id/A"        android:background="#0000ff"        android:layout_width="200dp"        android:layout_height="200dp">        <com.dongua.toucheventtest.ViewGroupB            android:id="@+id/B"            android:background="#00ff00"            android:layout_width="100dp"            android:layout_height="100dp">            <com.dongua.toucheventtest.ViewC                android:id="@+id/C"                android:background="#ff0000"                android:layout_width="60dp"                android:layout_height="60dp"                android:text="View C 文本"/>        </com.dongua.toucheventtest.ViewGroupB>    </com.dongua.toucheventtest.ViewGroupA></LinearLayout>

布局如图:

然后试着点击一下TextView,看看打印消息

11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewGroupA11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: onInterceptTouchEvent: ViewGroupA11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewGroupB11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: onInterceptTouchEvent: ViewGroupB11-22 18:11:36.770 7412-7412/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewC11-22 18:11:36.771 7412-7412/com.dongua.toucheventtest I/click: onTouchEvent: ViewC11-22 18:11:36.771 7412-7412/com.dongua.toucheventtest I/click: onTouchEvent: ViewGroupB11-22 18:11:36.771 7412-7412/com.dongua.toucheventtest I/click: onTouchEvent: ViewGroupA

这就很好理解了吧 这次我们让TextView强行拦截,即ViewC的dispatchTouchEvent返回一个true,我们在看看打印的消息

@Override    public boolean dispatchTouchEvent(MotionEvent ev) {        Log.i("click", "dispatchTouchEvent: ViewC");        return true;    }
11-22 18:17:55.295 15435-15435/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewGroupA11-22 18:17:55.295 15435-15435/com.dongua.toucheventtest I/click: onInterceptTouchEvent: ViewGroupA11-22 18:17:55.296 15435-15435/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewGroupB11-22 18:17:55.296 15435-15435/com.dongua.toucheventtest I/click: onInterceptTouchEvent: ViewGroupB11-22 18:17:55.296 15435-15435/com.dongua.toucheventtest I/click: dispatchTouchEvent: ViewC

可以看到它只到ViewC的dispatchTouchEvent就结束了整个事件的拦截。因为ViewC我们手动设置了拦截,但ViewC继承自TextView,如果我们对onTouchEvent也强行返回true,那么就会执行到ViewC的onTouchEvent方法。
同理的大家可以试试在ViewGroupB的dispatchTouchEvent直接返回true,这就使得事件向下的传递到此为止。对onTouchEvent直接返回true,这就使得事件向上的响应到此为止。

例子就这些,大家想要熟悉的话肯定要自己去手动实现一遍,我觉得一定要在反复的看几遍文字的描述之后,再去写代码才能理解其中深意,盲目的堆代码只会让你的了解停留在表面。之前看完了老是不自己写一遍,结果一阵子之后又忘了,如果多做笔记,在做笔记的过程中无疑又是一次学习的过程。
以上为个人学习笔记,如有错误,请不吝赐教~

更多相关文章

  1. [置顶] 【小超_Android】2014年框架类源码年度精品汇总
  2. 从源码角度理解HandlerThread和IntentService
  3. emulator启动编译源码
  4. 收藏android源码项目
  5. Ubuntu 12.04(64位)下载并编译 Android(安卓)4.1 源码
  6. 开源直播系统源码Android中activity跳转动画效果
  7. ANDROID关于亮屏和暗屏还有解锁的监听事件http://blog.csdn.net/
  8. Android应用程序键盘(Keyboard)消息处理机制分析(6)
  9. Android(安卓)4.0源码下载

随机推荐

  1. mysql事务的默认隔离级别
  2. Linux安装MySQL的两种方法 先卸载之前版
  3. Mysql5.7下的三种循环
  4. Mysql存储过程、索引
  5. MySQL查询时有时候需要某条记录置顶或者
  6. 阿里云服务器CentOS7.3上通过Docker安装M
  7. hibernate(*.hbm.xml)中新添加的字段被标记
  8. 浅谈:如何用java写一个简单的基于MySQL的J
  9. flash如何自动连接mysql数据库
  10. Windows下重置MySQL密码【MYSQL】