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事件会多次调用),很容易看出最终会进入

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

 

 

更多相关文章

  1. Android(安卓)Activity全屏和设置背景色
  2. android Activity 组件
  3. Android的消息机制源码分析
  4. Kotlin 写 Android(安卓)单元测试(二),JUnit 4 测试框架和 kotlin.t
  5. Android---网络编程之Retrofit2整体结构了解以及+Okhttp3+rxjava
  6. Android(安卓)控件(button)对齐方法实现详解
  7. Android(安卓)View 绘制流程之三:draw绘制
  8. Epoxy——RecyclerView的绝佳助手
  9. android Instrumentation

随机推荐

  1. android 基本架构
  2. Android(安卓)无线接口层RIL(Radio Layer
  3. 前阿里技术总监手打:452页Android(安卓)Fr
  4. 【Android(安卓)UI】Android开发之View的
  5. android连接mysql数据库
  6. 前阿里技术总监手打:452页Android(安卓)Fr
  7. Android系统移植(三)-按键字符表
  8. Android实现圆角ListView效果
  9. Android中View图形绘制基础
  10. Linux/Android——Input系统之InputReade