当前位置: 代码迷 >> Android >> Android Touch事件散发响应机制
  详细解决方案

Android Touch事件散发响应机制

热度:67   发布时间:2016-04-27 23:22:23.0
Android Touch事件分发响应机制

概述

在Android中,事件包括了点按、长按、拖拽、滑动等,这些事件才能让Android响应用户的各种操作。但是归根结底,所有的这些事件都是以如下三个部分作为基础的:

  1. ACTION_DOWN(按下)
  2. ACTION_MOVE(移动)
  3. ACTION_UP(抬起)

所有的操作事件首先必须执行ACTION_DOWN(按下)操作,之后所有的操作都是以按下操作为前提,当按下操作完成后,接下来可能是一段ACTION_MOVE然后ACTION_UP,或者直接ACTION_UP。

所有操作事件的执行顺序必须是:ACTION_DOWN -> ACTION_MOVE -> ACTION_UP。(是否包含ACTION_MOVE取决于用户手势中是否包含了移动操作)

本文基于Android2.2.3版本进行分析(主要是因为:2.2.3版本代码清晰简单一些,而且原理是通用的)


Activity事件传递

Touch事件的产生涉及到硬件和Linux内核部分,虽然我在硬件组,但是我也不想去深究这块内容。目前只需要知道Touch事件产生后,最先相应它的是Activity的dispatchTouchEvent方法。

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

其中,onUserInteraction()是一个空方法,开发者可以根据自己的需求override这个方法,这个方法在一个Touch事件的周期中肯定是第一个被调用。

接着分析getWindow().superDispatchTouchEvent(ev),这个方法其实最终是调用了ViewGroup的dispatchTouchEvent方法。

小结:

通过上面的分析,我们至少可以知道,一个Touch事件首先经过Activity的dispatchTouchEvent方法处理,然后分配给了ViewGroup的dispatchTouchEvent方法。


ViewGroup响应Touch事件

重点就是分析dispatchTouchEvent()方法,2.2.3版本这个方法还算简单,添加中文注释的源码如下:

public boolean dispatchTouchEvent(MotionEvent ev) {    final int action = ev.getAction();    final float xf = ev.getX();    final float yf = ev.getY();    // mScrollX代表视图起始坐标x轴方向偏移量    // mScrollY代表视图起始坐标y轴方法偏移量    final float scrolledXFloat = xf + mScrollX;    final float scrolledYFloat = yf + mScrollY;    final Rect frame = mTempRect;    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    // 处理初始的ACTION_DOWN事件    if (action == MotionEvent.ACTION_DOWN) {        // 当行为为ACTION_DOWN时,应该将mMotionTarget置为null        // 因为,ACTION_DOWN代表一个新的Touch事件        if (mMotionTarget != null) {            mMotionTarget = null;        }        // 判断当前ViewGroup是否对touch事件进行拦截        if (disallowIntercept || !onInterceptTouchEvent(ev)) {            ev.setAction(MotionEvent.ACTION_DOWN);            // 从ViewGroup的子View中找到应该处理该touch事件的控件            final int scrolledXInt = (int) scrolledXFloat;            final int scrolledYInt = (int) scrolledYFloat;            final View[] children = mChildren;            final int count = mChildrenCount;            for (int i = count - 1; i >= 0; i --) {                final View child = children[i];                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {                    // 获取当前子控件的矩形区域坐标                    child.getHitRect(frame);                    // 判断当前的Touch事件是否位于child的矩形区域中                    if (frame.contains(scrolledXInt, scrolledYInt)) {                        // 获取Touch事件相对于View控件的偏移                        final float xc = scrolledXFloat - child.mLeft;                        final float yc = scrolledYFloat - child.mTop;                        ev.setLocation(xc, yc);                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                        // 调用child view的dispatchTouchEvent方法对Touch事件进行处理                        if (child.dispatchTouchEvent(ev)) {                            // 设置touch事件之后的处理View                            mMotionTarget = child;                            return true;                        }                    }                }            }        }    }    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL);    if (isUpOrCancel) {        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;    }    // 之前处理ACTION_DOWN的时候,已经确定了需要处理后续touch事件的子View    final View target = mMotionTarget;    // 这里说明点击的是ViewGroup的空白区域,这时的Touch事件就需要由ViewGroup自己来处理了    if (target == null) {        ev.setLocation(xf, yf);        // ViewGroup的父类是View,所以这里其实也是调用了View的dispatchTouchEvent方法        return super.dispatchTouchEvent(ev);    }    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);        // 交给子View来处理Touch事件        if (!target.dispatchTouchEvent(ev)) {        }        mMotionTarget = null;        return true;    }    if (isUpOrCancel) {        mMotionTarget = null;    }    final float xc = scrolledXFloat - (float) target.mLeft;    final float yc = scrolledYFloat - (float) target.mTop;    ev.setLocation(xc, yc);    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {        ev.setAction(MotionEvent.ACTION_CANCEL);        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;        mMotionTarget = null;    }    return target.dispatchTouchEvent(ev);}

由于ViewGroup默认的onInterceptTouchEvent的返回值为false,所以ViewGroup并不对TouchEvent进行拦截。

从代码中,我们可以看到,如果ViewGroup本身不对Touch事件进行拦截,则Touch事件最终是交给相应的子View去处理的。

TODO:补充流程图。


View响应Touch事件

从ViewGroup响应Touch事件的源码分析来看,ViewGroup在不拦截Touch事件的前提下,是将Touch事件分发给Child View实现的。假设这里的Child View是一个Button,那我们来研究一下Touch事件在View里的传递规则。

查看Button的源码,我们是找不到dispatchTouchEvent方法实现的。但是,由于android.widget.Button继承自android.widget.TextView->android.view.View,最终我们发现dispatchTouchEvent方法是在View类中实现的。源码如下:

public boolean dispatchTouchEvent(MotionEvent event) {    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&        mOnTouchListener.onTouch(this, event)) {        return true;    }    return onTouchEvent(event);}

Android2.2.3版本,superDispatchTouchEvent方法实现非常简单,但是已经足够说明问题。接下来,我们逐个分析if语句中的代码实现。

第一,mOnTouchListener是如何赋值的?

解答:从View中我们能找到如下方法:

public void setOnTouchListener(OnTouchListener l) {    mOnTouchListener = l;}

这个方法我们应该是非常熟悉,当开发者给控件注册touch事件响应对象的时候,mOnTouchListener就已经被赋值了。

第二,mOnTouchListener.onTouch()算是回调方法吗?

解答:必须算啊,我们在对控件注册onTouch事件对象的时候,都会重写onTouch方法,这里就是触发我们注册的ouTouch方法。并且,我们可以看到,如果我们在重写的onTouch方法中返回true,则该Touch事件就已经被处理完成,否则,就继续调用View的onTouchEvent()方法。

那接下来,我们就继续分析一下View的onTouchEvent()方法,源码如下:

public boolean onTouchEvent(MotionEvent event) {    final int viewFlags = mViewFlags;    // 被disable的View不响应Touch事件    if ((viewFlags & ENABLED_MASK) == DISABLED) {        return ((viewFlags & CLICKABLE) == CLICKABLE ||             (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));    }    if (mTouchDelegate != null) {        if (mTouchDelegate.onTouchEvent(event)) {            return true;        }    }    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) {                boolean focusTaken = false;                if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                    focusTaken = requestFocus();                }                if (!mHasPerformedLongPress) {                    removeLongPressCallback();                    if (!focusTaken) {                        if (mPerformClick == null) {                            mPerformClick = new PerformClick();                        }                        if (!post(mPerformClick)) {                            performClick();                        }                    }                }                if (mUnsetPressedState == null) {                    mUnsetPressedState = new UnsetPressedState();                }                if (prepressed) {                    mPrivateFlags |= PRESSED;                    refreshDrawableState();                    postDelayed(mUnsetPressedState, ViewConfiguration.getPressedStateDuration());                } else if (!post(mUnsetPressedState)) {                    mUnsetPressedState.run();                }                removeTapCallback();            }            break;            case MotionEvent.ACTION_DOWN:            // 主要是响应一下按下事件,例如修改控件的颜色等            if (mPendingCheckForTap == null) {                mPendingCheckForTap = new CheckForTap();            }            mPrivateFlags |= PREPRESSED;            mHasPerformedLongPress = false;            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());            break;            case MotionEvent.ACTION_CANCEL:            break;            case MotionEvent.ACTION_MOVE:            final int x = (int) event.getX();            final int y = (int) event.getY();            // 判断是否越界            int slop = mTouchSlop;            if ((x < 0 - slop) || (x >= getWidth() + slop) ||                (y < 0 - slop) || (y >= getHeight() + slop)) {                removeTapCallback();            }            break;        }    }}

onTouchEvent()方法看起来还是挺复杂的。这里,我们关注重点,特别是对ACTION_UP的处理。其中,对ACTION_UP的处理最终会调用到PerformClick类的实例化。接下来,我们看一下该类是如何实例化的:

private final class PerformClick implements Runnable {    public void run() {        performClick();    }}public boolean performClick() {    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);    if (mOnClickListener != null) {        playSoundEffect(SoundEffectConstants.CLICK);        mOnClickListener.onClick(this);        return true;    }    return false;}

之前我们分析过mOnTouchListener是在onTouch事件赋值的,那mOnClickListener肯定是在onClick事件进行赋值的,所以这里是在ACTION_UP事件中回调了onClick方法。

“`


参考资料

  1. Android Deeper-Touch事件分发响应机制
  2. Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
  3. Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案