Android事件分发一之事件传递

Android事件分发二之ViewGroup如何处理事件

Android事件分发三之View

Android事件分发四总结

Android滑动冲突一内部拦截外部拦截简介
Android滑动冲突二内部拦截法详情

 

本文接上篇Android滑动冲突一内部拦截外部拦截简介

一 ViewPager嵌套ListView的滑动冲突,内部拦截法为何ViewPager的onInterceptTouchEvent要做判断而不是直接返回true?

 

我们重温下Android事件分发二之ViewGroup如何处理事件中ViewGroup事件分发方法的源码。注意这个ViewGroup对应到我们例子的ViewPager

class:ViewGroup:   @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        ......//省略        boolean handled = false;        if (onFilterTouchEventForSecurity(ev)) {     //判断屏幕是否隐藏等            final int action = ev.getAction();            final int actionMasked = action & MotionEvent.ACTION_MASK;            // Handle an initial down.            //该方法在事件冲突的内部拦截法当中有重要作用,这里暂不解析            if (actionMasked == MotionEvent.ACTION_DOWN) {                cancelAndClearTouchTargets(ev);                resetTouchState();            }//**重点 intercepted就是判断该ViewGroup是否需要直接处理事件的标记            // Check for interception.            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) {                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;                            if (!disallowIntercept) {//    用内部拦截法时,down事件的时候,我们在子类的requestDisallowInterceptTouchEvent传入了true,就是希望父类不要拦截我,从而不进入这个判断。//但是坑的是,在down的时候,在上面的if判断中对disallowIntercept进行了重置为false,不受子类requestDisallowInterceptTouchEvent的影响了,也就是说肯定会进入这个判断                    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;            }}

 

ViewGroup的事件拦截方法第一步就是给intercpted赋值,当intercepted为true时,就不会通过for循环去找子View分发事件。我们看看在上述源码里面发现在ACTION_DOWN的时候,对disallowIntercept进行了赋值->true,不受子类方法requestDisallowInterceptTouchEvent(请求父类不要拦截我)的影响。

那么本来我们希望的是在action_down的时候希望父类不要拦截我,不进入if (!disallowIntercept) {}这个判断从而让intercepted为false。现在进入if (!disallowIntercept) {}了这个判断-> intercepted = onInterceptTouchEvent(ev),所以我们正确的方法是应该在父类ViewPager的onInterceptTouchEvent加一个判断,DOWN时返回false,其它true。

 

  • ACTION_CANCEL什么时候调用?

我们在事件分发Android事件分发一之事件传递当中讲述了DOWN、MOVE、UP、CANCEL四个方法的调用时机。其中CANCEL在被上层事件拦截的时候调用,看看源码分析其原因

ViewGroup:  @Override    public boolean dispatchTouchEvent(MotionEvent ev) {  //...省略 // Dispatch to touch targets.            if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            } else {                // Dispatch to touch targets, excluding the new touch target if we already                // dispatched to it.  Cancel touch targets if necessary.                TouchTarget predecessor = null;                TouchTarget target = mFirstTouchTarget;                while (target != null) {                    final TouchTarget next = target.next;                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                        handled = true;                    } else {                        //    move时进入这里                        final boolean cancelChild = resetCancelNextUpFlag(target.child)                                || intercepted;                        if (dispatchTransformedTouchEvent(ev, cancelChild,                                target.child, target.pointerIdBits)) {                            handled = true;                        }                        if (cancelChild) {                            if (predecessor == null) {                                mFirstTouchTarget = next;                            } else {                                predecessor.next = next;                            }                            target.recycle();                            target = next;                            continue;                        }                    }                    predecessor = target;                    target = next;                }            }}//...省略

以上是ViewGroup事件分发方法的部分源码,我们在Android事件分发二之ViewGroup如何处理事件,总结了手指从第一次触摸DOWN到滑动MOVE时如何命中目标。此刻的场景是命中目标后手指在屏幕上面MOVE,mFirstTouchTarget此时不为空。会调用这个 if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {}判断,并且其中的cancelChild此时为true(因为我们的ListView在MOVE时,根据滑动手势左右滑动时要求父类拦截我->intercepted为true)。那么我们来看看dispatchTransformedTouchEvent方法cancelChild传true时候的源码:

 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) {            //调用ACTION_CANCEL方法            event.setAction(MotionEvent.ACTION_CANCEL);            if (child == null) {                handled = super.dispatchTouchEvent(event);            } else {                handled = child.dispatchTouchEvent(event);            }            event.setAction(oldAction);            return handled;        }//。。省略}

很容易看出,在cancel传入为true的时候,进入判断调用ACTION_CANCEL方法。这也就是为何在ACTION_CANCEL方法在被上级拦截时调用的原因了。

  • ListView被上层拦截了怎么将事件交还给ViewPager?

我们接着刚刚的分析步骤dispatchTransformedTouchEvent方法会进入handled = child.dispatchTouchEvent(event),其child为ListView将事件是否消费交给了ListView处理。(记住此时条件在用户down并且move后触发了ViewPager的拦截方法导致intercepted为true,此时命中了消费事件的目标ListView,即mFirstTouchTarget不为空)我们接着ViewGroup的事件方法走,此时走到了 if (cancelChild) {}我们注意进入该判断后会调用 mFirstTouchTarget = next方法,会将mFirstTouchTarget置为空。

此时ViewGroup的dispatchTouchEvent事件分发方法走完了一次,随着手指的滑动再次进入dispatchTouchEvent(一定要记住MOVE事件会多次调用),很容易看出最终会进入Android滑动冲突二内部拦截法详情_第1张图片

调用dispatchTransformedTouchEvent方法,并且child传null。我们通过查看dispatchTransformedTouchEvent的源码,得知当child为null时,会调用其父级的dispatchTouchEvent方法。这样就将事件交回了ViewPager

 

 

更多相关文章

  1. Android的消息机制源码分析
  2. Android 控件(button)对齐方法实现详解
  3. Android 源码分析 - 消息处理机制
  4. Android Studio更新升级方法
  5. android 实现 APP 保活且正常升级的方法
  6. 【源码分享下载】Android 智能问答机器人的实现
  7. Android保存数据几种常用方法解析
  8. Android查看外部依赖jar的源码'Android Private Libraries' whic
  9. Android 中插件的编写方法

随机推荐

  1. Android的Launcher成为系统中第一个启动
  2. 基于Android的计算器(代码行数足够用来交
  3. android中listView下拉刷新
  4. 我是如何自学Android,资料分享
  5. 【Android】手写优化-更为平滑的签名效果
  6. ANDROID 开机启动VNC SERVER
  7. Android(安卓)button去掉背影阴影效果
  8. Android的隐私保护利器
  9. 读取联系人 2.0
  10. Android手机搭建Linux服务器