💪
AndroidCollect
  • 写在前面
  • 计算机基础
    • 计算机组成原理
    • 算法
      • 查找
        • 二分查找
      • 排序
        • 简单排序
        • 高级排序
        • 特殊排序
      • 海量数据
      • 思想
        • 贪心
        • 分治
        • 动态规划
        • 回溯
      • 哈希算法
    • 数据结构
      • 队列
        • 知识点
        • 相关题目
          • 用两个栈实现队列
          • 实现循环队列
          • 用链表实现队列
          • 用数组实现队列
      • 栈
        • 相关算法题目
          • 用链表实现栈
          • 用数组实现栈
      • 链表
        • 知识点梳理
        • 相关算法题目
          • 删除倒数第n个结点
          • 合并两个有序链表
          • 检测单链表是否有环
          • 获取中间结点
          • 反转链表
      • 跳表
      • 哈希表
      • 树
        • 二叉树
        • 二叉查找树
        • AVL 树
        • Trie 树
        • 红黑树
      • 堆
        • 存储
        • 堆的应用
      • 图
    • 网络
      • 应用层协议
        • DNS
        • HTTP
        • HTTPS
      • 传输层协议
        • TCP
        • UDP
      • 输入网址后发生了什么
    • 操作系统
      • 内存
    • 数据库
  • 软件工程
    • 编程思想
    • 设计模式
      • 状态模式
      • 装饰器模式
      • 代理模式
      • 责任链模式
      • 建造者模式
      • 单例模式
      • 观察者模式
  • Java
    • 基础
    • 异常
    • 并发编程
      • ThreadLocal
      • 线程池
      • 理解 volatile
      • AbstractQueuedSynchronizer
    • 集合
      • LinkedHashMap 源码
      • HashMap 源码
    • 注解
    • 反射
      • JDK 动态代理
    • JVM
      • 自动内存管理机制
      • Class 文件格式
      • 类加载机制
      • Java 内存模型(JMM)
      • 字节码指令
      • HotSpot 虚拟机实现细节
    • 源码与原理
    • 各版本主要特性
  • Android
    • 基础组件
      • Context
      • Activity
        • 生命周期
        • 启动模式与任务栈
        • 启动流程
      • Service
      • ContentProvider
      • BroadcastReceiver
      • Fragment
      • View
        • 常用控件问题总结
          • RecyclerView
          • ViewPager2
        • CoordinatorLayout
        • SurfaceView
        • 事件分发
        • 绘制流程
        • 自定义 View
        • Window
    • 数据存储
      • 存储结构
      • Sqlite
      • 序列化
      • SharedPreferences
    • 资源
      • 图片加载
    • 动画
      • 属性动画
    • 线程和进程
      • Binder 机制
      • 跨进程通信
        • AIDL
    • 内部原理
      • 消息循环机制
      • Binder
      • Window
      • SparseArray
      • ArrayMap
      • RecyclerView
      • App 启动流程
    • 性能优化
      • 内存
        • 内存使用优化
        • 内存泄漏
      • 启动优化
      • 缩减包大小
      • 布局优化
      • ANR
    • 打包构建
      • dex 文件
      • APK 打包流程
      • APK 签名流程
    • 架构
      • 运行时
      • Android 系统架构
      • 应用项目架构
    • 开源框架源码或原理
      • RxJava
        • 使用笔记
        • 源码解析
      • Retrofit
      • ButterKnife
      • BlockCanary
      • LeakCanary
      • OkHttp
      • 图片加载
        • Glide
        • Picasso
    • 碎片化处理
      • 屏幕适配
    • 黑科技
      • 热修复
    • Jetpack
      • Lifecycle
      • Room
      • WorkManager
    • 新动态
      • AndroidX
      • 各系统版本特性
  • 开发工具
    • 正则表达式
    • ADB
    • Git
  • Kotlin
  • Flutter
  • 关于作者
  • 致谢
由 GitBook 提供支持
在本页
  • ViewRootImpl/DecorView/Activity/Window/DecorView
  • Activity
  • Window
  • DecorView
  • ViewGroup
  • View
  • 多点触控
  • 滑动冲突
  • 相关问题
  • 如何解决View的滑动冲突?
  • onTouch()、onTouchEvent()和onClick()关系?
  • 防止短时间内重复点击
  • 相关链接

这有帮助吗?

  1. Android
  2. 基础组件
  3. View

事件分发

ViewRootImpl/DecorView/Activity/Window/DecorView

事件由 ViewRootImpl 分发,会先分发到 DecorView。

//ViewRootImpl.java
private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;
            ...
            //关键点:mView分发Touch事件,mView就是DecorView
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            ...
       }
 // View.java
 public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            //分发Touch事件
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }
//FrameLayout
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

Window.Callback都被Activity和Dialog实现,所以变量cb可能就是Activity和Dialog。

Activity

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

Window

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

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

DecorView

//extends ViewGroup
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks{

    public boolean superDispatchTouchEvent(MotionEvent event) {
        //调用 ViewGroup 的 dispatchTouchEvent
        return super.dispatchTouchEvent(event);
    }

}

ViewGroup

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    //....
    
    boolean handled = false;
    final int action = ev.getAction();
    final int actionMasked = action & MotionEvent.ACTION_MASK;

    // 如果是 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();
    } 
    // 检查是否需要拦截
    final boolean intercepted;
    
    //如果是down事件或者前面事件已经有子view接收,需要重新判断是否需要拦截
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {      
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            //拦截没有被禁用时,需要调用 onInterceptTouchEvent 判断
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            //禁用了拦截,直接返回false
            intercepted = false;
        }
    } else {
        //不是down事件,并且也没有其他子view处理过之前的事件
        //则ViewGroup自己拦截处理
        intercepted = true;
    }
    // 检查是否取消
    final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;
    
    TouchTarget newTouchTarget = null;
    //是否已由新目标处理事件
    boolean alreadyDispatchedToNewTouchTarget = false;
    //....
    
    if (!canceled && !intercepted) {
        //只针对这三种事件寻找View,其他情况一律由之前的目标处理
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    //按照z轴坐标以及自定义绘制顺序(如果有的话)排列子view
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        //获取view在列表中的索引
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);
                        //检查事件发生位置是否在View内以及View是否可以接受事件        
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        
                        //此时找到了事件发生在区域内且可以接收事件的view

                        //在 touchTarget 链表中寻找 View对应的TouchTarget
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            //view 已经在接受事件了
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        //没有找到对应target,先分发事件给 view
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // View 的 dispatchTouchEvent 返回了true
                            mLastTouchDownTime = ev.getDownTime();
                            if (preorderedList != null) {
                                // 找到在mChildren中的真实索引
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            //添加新的TouchTarget
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                         }
                     }//end for
                     if (preorderedList != null) preorderedList.clear();
                 }//end if
                 if (newTouchTarget == null && mFirstTouchTarget != null) {
                    // Did not find a child to receive the event.
                    // Assign the pointer to the least recently added target.
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }//end if
            
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //调用父类的 dispatchToucheEvent
                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) {
                        //Down 事件时走到这里
                        handled = true;
                    } else {
                        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;
                }//end while
            }//end else                              
        }//end if    
    return handled; 
}

View

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            //设置了onTouchListener/enable/onTouch返回true
            //直接返回
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    return result;
}

onTouchEvent(event) 中会调用onClick 和 onLongClick。

调用onClick 是在UP事件时,检查是否clickable以及是否设置监听;

调用onLongClick 是在Down 事件时,发出一个延时任务,如果任务执行时还是按下状态,就执行难onLongClick.

//todo 源码

多点触控

处理POINTER_DOWN 和POINTER_UP,进行指针处理。

区分 actionIndex(pointerIndex)/ pointerId.

滑动冲突

父View根据需要重写 onIntercept 或者子View根据需要调用父View的requestDisallowIntercept禁用父View对事件的拦截。

requestDisallowIntercept 会向上传递。

相关问题

事件传递大体过程:Activity--> Window-->DecorView --> View树从上往下,传递过程中谁想拦截就拦截自己处理。MotionEvent是Android中的点击事件。主要事件类型:

  • ACTION_DOWN 手机初次触摸到屏幕事件

  • ACTION_MOVE 手机在屏幕上滑动时触发,会回调多次

  • ACTION_UP 手指离开屏幕时触发

需要关注的几个方法。

  • dispatchTouchEvent(event);

  • onInterceptTouchEvent(event);

  • onTouchEvent(event);

上面3个方法可以用以下伪代码来表示其关系:

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;//事件是否被消费
    if (onInterceptTouchEvent(ev)) {//调用onInterceptTouchEvent判断是否拦截事件
        consume = onTouchEvent(ev);//如果拦截则调用自身的onTouchEvent方法
    } else {
        consume = child.dispatchTouchEvent(ev);//不拦截调用子View的dispatchTouchEvent方法
    }
    return consume;//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
}

如何解决View的滑动冲突?

父类根据需要重写 onIntercept 或者子View根据需要调用requestDisallowIntercept

onTouch()、onTouchEvent()和onClick()关系?

如果一个View需要处理事件,它设置了OnTouchListener,那么OnTouchListener的onTouch方法会被回调。如果onTouch返回false,则onTouchEvent会被调用,反之不会。在onTouchEvent方法中,事件为Action.UP的时候会回调OnClickListener的onClick方法,可见OnClickListener的优先级很低。

防止短时间内重复点击

通过在对比两次ACTION_DOWN事件之间的时间间隔是否小于最小间隔,如果小于直接忽略。

视应用范围,这段逻辑可以放在View/ViewGroup/Activity 中 。

private static final long CLICK_DURATION = 900;

//上次 ACTION_DOWN 事件发生时间
private long lastDownTime = 0;

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
        boolean ignore = event.getDownTime() - lastDownTime < CLICK_DURATION;
        lastDownTime = event.getDownTime();
        if (ignore) {
            return false;
        }
    }
    return super.dispatchTouchEvent(event);
}

相关链接

上一页SurfaceView下一页绘制流程

最后更新于4年前

这有帮助吗?

安卓自定义View进阶-MotionEvent详解
玩安卓|每日一问
玩安卓|每日一问:多指触控
安卓自定义View进阶-多点触控详解
事件分发和NestedScrolling(二)