http://blog.csdn.net/chenzujie/article/details/13277081

http://blog.csdn.net/chenzujie/article/details/13277081

http://blog.csdn.net/chenzujie/article/details/13277081

http://blog.csdn.net/chenzujie/article/details/13277081





Android之Touch事件分发机制

分类:android 2727人阅读 评论(9) 收藏 举报 android onTouchEvent dispatchTouchEvent onInterceptTouchEven onTouch

直接进入正题,在android开发中,手势触碰是经常使用到的。这两天刚好在看这方面的资料,在这里写篇文章做个小总结。

首先必须知道五大布局Layout:LineaLayout,RelativeLayout,FrameLayout,AbsoluteLayout都继承自ViewGroup,而TableLayout继承LinearLayout,也相当于继承于ViewGroup。

而ViewGroup继承自View。

而我们最终在屏幕上显示控制的控件如TextView,Button等都直接或间接继承自View。

View中有两个方法参与到Touch事件分发

onDispatchTouchEvent(MotionEvent event)和onTouch(MotionEvent event)

ViewGroup有三个方法参与到Touch事件分发

onDispatchTouchEvent(MotionEvent event),onInterceptTouchEvent(MotionEvent ev),onTouch(MotionEvent event)


当我们手指出门屏幕,手势事件最早被传递到Layout布局的dispatchTouchEvent,即ViewGroup的dispatchTouchEvent

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;    final Rect frame = mTempRect;    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;    if (action == MotionEvent.ACTION_DOWN) {        if (mMotionTarget != null) {            mMotionTarget = null;        }        if (disallowIntercept || !onInterceptTouchEvent(ev)) {            ev.setAction(MotionEvent.ACTION_DOWN);            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);                    if (frame.contains(scrolledXInt, scrolledYInt)) {                        final float xc = scrolledXFloat - child.mLeft;                        final float yc = scrolledYFloat - child.mTop;                        ev.setLocation(xc, yc);                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                        if (child.dispatchTouchEvent(ev))  {                            mMotionTarget = child;                            return true;                        }                    }                }            }        }    }    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||            (action == MotionEvent.ACTION_CANCEL);    if (isUpOrCancel) {        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;    }    final View target = mMotionTarget;    if (target == null) {        ev.setLocation(xf, yf);        if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {            ev.setAction(MotionEvent.ACTION_CANCEL);            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;        }        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);        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);}

此段代码叫长,挑重点看,第9行,但是ACTION_DOWN事件时,会进入到第13行的判断,第一个参数 disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。而第二个参数!onInterceptTouchEvent(ev)即是否进行拦截的判断,如果我们不复写这个方法,默认为false,就会进入if判断,并获得点击区域有的view,在第29行将手势事件传递给被点击到的view,如果view的dispatchTouchEvent(ev)返回true,表示手势事件被这个view消费了(view的dispatchTouchEvent后续分析),并把该view置给mMotionTarget后返回true。

这里,正是因为有view消费了ACTION_DOWN事件,使得当后续的ACTION_MOVE ACTION_UP传递到这个ViewGroup的时候,mMotionTarget不为空,使得47行的target不为空,最后都会执行74行的target.dispatchTouchEvent(ev),即ACTION_DOWN事件被哪个view的onTouch或者onTouchEvent返回了true,那么后续的ACTION_MOVE,ACTION_UP事件将会直接传递给这个view执行。(onTouch和onTouchEvent后续分析)

继续分析这个ViewGroup的dispatchTouchEvent,如果13行的onInterceptTouchEvent被我们复写返回true代表ACTION_DOWN被这个ViewGroup拦截下来,将不会进入if判断,因此mMotionTarget将为null使得target也为null,最后执行了50行的super.dispatchTouchEvent即ViewGroup的父类View的dispatchTouchEvent。并在其中调用onTouch或者onTouchEvent。

如果我们不复写onInterceptTouchEvent让其进入if判断,但如果child的dispatchTouchEvent返回的是false,表示child没有把这个事件消费了,最终也还是调用了50行的super.dispatchTouchEvent(ev)。


现在再来看看上面分析中经常说到的View的dispatchTouchEvent

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

非常简短的几行代码,是不是比看了ViewGroup的dispatchTouchEvent轻松多了!

首先先判断mOnTouchListener是否为空,这个mOnTouchListener是啥呢,看下源码就知道了

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

显然,就是我们设置OnTouchListener的接口实例,并在其中复写了onTouch方法。

if判断的第二个参数用来判断当前view是否是enable为状态。而第三个参数就是我们复写的onTouch方法了,当我们返回了true,这个view的dispatchtouchEvent将返回true,从而使得调用这个dispatchTouchEvent的ViewGroup的dispatchTouchEvent确定了mMotionTarget,确定了消费了ACTION_DOWN事件的view。

而如果我们没有设置OnTouchEvent接口或者onTouch返回false则调用了onTouchEvent。

继续看下view的onTouchEvent

public boolean onTouchEvent(MotionEvent event) {    final int viewFlags = mViewFlags;    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));    }    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) {                    // 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 (!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) {                        mPrivateFlags |= PRESSED;                        refreshDrawableState();                        postDelayed(mUnsetPressedState,                                ViewConfiguration.getPressedStateDuration());                    } else if (!post(mUnsetPressedState)) {                        // If the post failed, unpress right now                        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:                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                int slop = mTouchSlop;                if ((x < 0 - slop) || (x >= getWidth() + slop) ||                        (y < 0 - slop) || (y >= getHeight() + slop)) {                    // 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;}

这段代码同样很长,同样挑重点看。

第14行当这个view是可点击的就会进入if判断,否则直接返回false,因此很有可能这个onTouchEvent的代码如果在第14行进入了if判断,很可能里面会有点击事件的执行。我们往下看,第38行执行ACTION_UP手势时,执行的performClick

public boolean performClick() {    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);    if (mOnClickListener != null) {        playSoundEffect(SoundEffectConstants.CLICK);        mOnClickListener.onClick(this);        return true;    }    return false;}


没错,performClick确实执行了mOnClickListener.onClikc(this);就是执行我们设置的onClick事件。并且在onTouchEvent的89行返回了true。


在这部分分析中我们也发现了一个小秘密,如果我们复写了onTouchListener中的onTouch让它返回false,这样dispatchTouchEvent将不会执行到onTouchEvent方法,使得onClick事件永远得不到执行。


好了,全部分析完毕,做几点小总结

1、onTouch优先于onTouchEvent执行,且onTouch返回true将不执行onTouchEvent,并导致onTouchEvent里的点击事件得不到执行

2、手势事件从最外部的ViewGroup的dispatchTouchEvent开始分发,如果onInterceptTouchEvent拦截了该手势事件,即返回true,手势事件将不会往下分发,即不执行view的dispatchTouchEvent,而是让该ViewGroup的的父类dispatchTouchEvent来消费该手势事件。

3、如果View的dispatchTouchEvent消费了ACTION_DOWN事件,即对ACTION_DOWN返回true,后续的的ACTION_MOVE和ACTION_UP将直接被分发到这个view来消费。


最后感谢Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

两篇文章的作者,我就是在看了这两篇文章后,再看了些资料才了解了android手势分发事件。


更多相关文章

  1. Android之键盘点击事件
  2. Android实现再按一次(返回键)实现退出(应用程序)
  3. 在android中处理各种触摸事件
  4. Android之键盘点击事件
  5. Android(安卓)模拟按键
  6. CaysnPrinter开发包接口说明文档 - PrinterLibs For Android
  7. android字体闪烁动画(线程)
  8. Android(安卓)事件
  9. android程序执行adb shell命令

随机推荐

  1. Android 各个版本号WebView
  2. android:adjustViewBounds 、android:cro
  3. Android的AlertDialog详解
  4. Android输入框自动提示
  5. Compile android NDK without Eclipse
  6. android activity设置全屏以及Android获
  7. Android系统启动之Zygote
  8. Android学习笔记3之基本组件API
  9. Android客户端、服务端、数据库开发流程
  10. 解决android下创建avd设备不成功.txt