我们通过一个示例来分析Touch事件的分发过程。

示例:

布局文件:

 <?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:id="@+id/rootview" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.maimingliang.test.view.TestTouchActivity">    <TextView  android:id="@+id/txt" android:layout_width="match_parent" android:gravity="center" android:layout_height="55dp" android:text="textView"/>    <ImageView  android:id="@+id/img" android:layout_marginTop="20dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@mipmap/ic_launcher"/></LinearLayout>

Activity:

 public class TestTouchActivity extends AppCompatActivity {    private static final String TAG = "TestTouchActivity";    @Bind(R.id.txt)    TextView tv;    @Bind(R.id.img)    ImageView img;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_test_touch);        ButterKnife.bind(this);        initView();    }    private void initView() {        tv.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.d(TAG,"-------> tv Onclick");            }        });        tv.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.d(TAG, "-------> tv onTouch");                return false;            }        });        img.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.d(TAG, "--------> img onClick");            }        });        img.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.d(TAG, "--------> img onTouch");                return true;            }        });    }

点击图片,现象

可以看到onTouch事件比onClick事件优先级高。

再看看把setOnTouchListener事件的返回值改为true:

可以看到onClick事件没有了。这是为什么?我们透过源码来看看这个现象。

事件分发机制源码分析

当我们触摸屏幕上的某个控件时,底层的设备硬件传递给InputManager经过一 定的处理后,传递给AmS,再经过AmS的处理后就传递到我们的Activity,接着传递Window,最后传递到顶级View。

触摸事件的分发过程有三个重要的方法:

public boolean dispatchTouchEvent(MotionEvent ev)

用来分发事件的,如果当前事件能传递到该View,该 方法一定调用,View的onTouchEvent方法会调用,而该方法的返回值所onTouchEvent影响。

public boolean onInterceptHoverEvent(MotionEvent event)

用来拦截事件的,如果返回值为true,表示拦截。否则不拦截。

public boolean onTouchEvent(MotionEvent event)

处理当前事件的。如果返回值为true表示消耗该事件。否则无法再接收同一个序列的事件。

同一个序列的事件是;DOWN事件--》多个MOVE事件--》UP事件。

Activity触摸事件分发过程

当触摸事件传递到Activity,Activity的dispatchTouchEvent()方法就会调用,我们去看看:

 public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            onUserInteraction();        }        if (getWindow().superDispatchTouchEvent(ev)) {            return true;        }        return onTouchEvent(ev);    }

如果当前事件是DOWN事件,调用了onUserInteraction方法,该方法是一个空方法,我们可以重载该方法,在DOWN事件做一些处理。接着就把事件传递给Window来处理该事件。如果返回true,表示有View处理该事件,onTouch Event()方法返回了true,整个事件处理完成。否则Activity的onTouchEvent方法就会被调用。

Window触摸事件的分发过程

Window类是abstract的,唯一的具体实现类是PhoneWindow类,我们去看看PhoneWindow的superDispatchTouchEvent()方法:

 @Override    public boolean superDispatchTouchEvent(MotionEvent event) {        return mDecor.superDispatchTouchEvent(event);    }    private DecorView mDecor;

DecorView类继承于FrameLayout:

  private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {....} 

因此就是调用了DecorView的superDispatchTouchEvent方法:

public boolean superDispatchTouchEvent(MotionEvent event) {            return super.dispatchTouchEvent(event);        }

可以看到,其实就是调用了父类的dispatchTouchEvent()方法。DecorView继承于FrameLayout,FrameLayout继承于ViewGroup。因此就是调用了ViewGroup的dispatchTouchEvent()方法。

DecorView就是我们的顶层View,当我们通过setContentView()方法设置的是顶层View的一个子View。DecorView组成为:


可以看出,事件传递的大概过程:

Activity--》Window--》View。某个View的onTouchEvent()方法被调用。如果返回true,传递会Window,Window再传递会Activity,事件处理结束。否则返回false,再同样的传递会Activity。

顶层View事件分发的过程

DecorView继承与FrameLayout,是一个ViewGroup,ViewGroup继承于View,继承图:

ViewGroup重载了dispatchTouchEvent()方法。那我们去看看该方法:

    @Override   public boolean dispatchTouchEvent(MotionEvent ev) {        ....        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            1.            // Handle an initial down.            if (actionMasked == MotionEvent.ACTION_DOWN) {                // Throw away all previous state when starting a new touch gesture.                // The framework may have dropped the up or cancel event for the previous gesture                // due to an app switch, ANR, or some other state change.                cancelAndClearTouchTargets(ev);                resetTouchState();            }            2.            // Check for interception.            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;                if (!disallowIntercept) {                    intercepted = onInterceptTouchEvent(ev);                    ev.setAction(action); // restore action in case it was changed                } else {                    intercepted = false;                }            } else {                // There are no touch targets and this action is not an initial down                // so this view group continues to intercept touches.                intercepted = true;            }            ....            3.            if (!canceled && !intercepted) {                ....                    final int childrenCount = mChildrenCount;                    if (newTouchTarget == null && childrenCount != 0) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        ....                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = customOrder                                    ? getChildDrawingOrder(childrenCount, i) : i;                            final View child = (preorderedList == null)                                    ? children[childIndex] : preorderedList.get(childIndex);                            ....                            if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                continue;                            }                            ....                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.                                mLastTouchDownTime = ev.getDownTime();                                if (preorderedList != null) {                                    // childIndex points into presorted list, find original index                                    for (int j = 0; j < childrenCount; j++) {                                        if (children[childIndex] == mChildren[j]) {                                            mLastTouchDownIndex = j;                                            break;                                        }                                    }                                } else {                                    mLastTouchDownIndex = childIndex;                                }                                mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }                           ....                        }                    }                    ....                }            }            ....        return handled;    }

这个方法很长我们分几部分来分析。代码中标有1.2.3…..。

1.ViewGroup对DOWN事件重置状态的操作。

  private void resetTouchState() {        clearTouchTargets();        resetCancelNextUpFlag(this);        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        mNestedScrollAxes = SCROLL_AXIS_NONE;    }

标志FLAG_DISALLOW_INTERCEPT可以通过requestDisallowInterceptTouchEvent方法设置。因此在DOWN事件该方法不影响该标志,简单来说,就是不影响ViewGroup处理DOWN事件的操作。

2.判断是否拦截事件。

首先判断是否DOWN事件或者mFirstTouchTarget != null。

mFirstTouchTarget的意思是,如果ViewGroup的有子元素成功处理,mFirstTouchTarget就会指向该元素。

如果当前事件是DOWN:FLAG_DISALLOW_INTERCEPT不影响ViewGroup对DOWN事件的处理,因此调用了onInterceptTouchEvent()方法。是否拦截取决于该方法的返回值。

如果onInterceptTouchEvent()返回true,说明ViewGroup拦截事件,mFirstTouchTarget为null,同一序列的事件都由它处理,onInterceptTouchEvent也不会再调用了,因为actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null条件都不满足。如果子 View调用了requestDisallowInterceptTouchEvent()方法后,ViewGroup将无法拦截除DOWN事件以外的其他事件。该方法不影响ViewGroup的DOWN事件。

3.如果ViewGroup不拦截,ViewGroup遍历所有的子View,判断子View是否满足当前的事件。满足的条件有两个:子View是否播放动画和事件的坐标是否在子View的区域。

如果满足条件,调用了dispatchTransformedTouchEvent()方法。去看看:

  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,            View child, int desiredPointerIdBits) {        final boolean handled;        // Canceling motions is a special case. We don't need to perform any transformations        // or filtering. The important part is the action, not the contents.        final int oldAction = event.getAction();        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {            event.setAction(MotionEvent.ACTION_CANCEL);            if (child == null) {                handled = super.dispatchTouchEvent(event);            } else {                handled = child.dispatchTouchEvent(event);            }            event.setAction(oldAction);            return handled;        }    ....}

其实就是调用了子View的dispatchTouchEvent()方法。如果返回了true,就会通过addTouchTarget()方法对mFirstTouchTarget赋值并停止遍历子View。

  private TouchTarget addTouchTarget(View child, int pointerIdBits) {        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);        target.next = mFirstTouchTarget;        mFirstTouchTarget = target;        return target;    }

可以看到,mFirstTouchTarget是一个单链表的数据结构。

如果遍历全部的子View都没有成功处理的,mFirstTouchTarget成员变量为null,当该成员变量为null,就会调用:

 if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            } 

因为第三个参数为null,就会调用super.dispatchTouchEvent()方法,调用到了View的dispatchTouchEvent()方法。

View的事件分发过程

dispatchTouchEvent方法如下:

/** * 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) {        ....        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                result = true;            }            if (!result && onTouchEvent(event)) {                result = true;            }        }    ....        return result;    }

从上面的代码可以看出,判断了是否设置了setOnTouchListener,是否为ENABLED,onTouch是否返回了true。

ENABLED对这个判断没有影响。
但onTouch返回true,onTouchEvent方法就不会执行了。而onClick的方法是在onTouchEvent()方法执行的。因此onTouch事件的优先级比onClick事件高,而且还当onTouch方法返回了true,onClick事件就不会调用了。说明了上面的示例的现象。

我们去看看onClick事件是否在onTouch Event方法中执行的。

    public boolean onTouchEvent(MotionEvent event) {        final float x = event.getX();        final float y = event.getY();        final int viewFlags = mViewFlags;        final int action = event.getAction();        if ((viewFlags & ENABLED_MASK) == DISABLED) {            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {                setPressed(false);            }            // 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)                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);        }        if (mTouchDelegate != null) {            if (mTouchDelegate.onTouchEvent(event)) {                return true;            }        }        if (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {            switch (action) {                case MotionEvent.ACTION_UP:                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;                    if ((mPrivateFlags & PFLAG_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.                            setPressed(true, x, y);                       }                        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)) {                                    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;      .....      .....            }            return true;        }        return false;    }

从上述代码看到,判断了viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE判断是否可点击或者长点击。只要有一个为true,就会返回true,表示消耗此事件。
CLICKABLE和LONG_CLICKABLE的值可以在清单文件中通过android:clickable和 android:longClickable属性设置,也可以通过setOnclickListener()和setLongClickListener()方法设置。

当设置了点击事件调用了performClick()方法:

   public boolean performClick() {        final boolean result;        final ListenerInfo li = mListenerInfo;        if (li != null && li.mOnClickListener != null) {            playSoundEffect(SoundEffectConstants.CLICK);            li.mOnClickListener.onClick(this);            result = true;        } else {            result = false;        }        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);        return result;    }

可以看到回调了我们设置的onClick方法。由此看出onClick事件是在onTouch Event方法执行的。

这就是事件分发的大概流程。

我们根据上面的示例走一下整个触摸事件的分发流程。

我们从顶View开始分析:

整个View树的结构如下:

上面的示例,我们点击了图片。

首先由顶层View(FrameLayout)的dispatchTouch()方法根据点击图片等坐标首先分发到第一个LinearLayout的,然后调用了ViewGroup的dispatchTouch()方法,又根据点击图片等坐标,把事件分发给图片来处理这个事件。

END

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. android上一些方法的区别和用法的注意事项
  5. android实现字体闪烁动画的方法
  6. Android(安卓)Wifi模块分析(三)
  7. Android中dispatchDraw分析
  8. Android四大基本组件介绍与生命周期
  9. Android(安卓)MediaPlayer 常用方法介绍

随机推荐

  1. Android按钮美化
  2. 解决WARNING: APP_PLATFORM android-19 i
  3. Android用户界面与布局
  4. 【eoeAndroid社区索引】android 条形码的
  5. Android应用程序开发入门
  6. android底部栏中间按钮突出
  7. Android(安卓)输入限制
  8. android adb shell 命令大全
  9. android studio 编译的时候出现的错误和
  10. 实现Android简单动画旋转案例