zoukankan      html  css  js  c++  java
  • Android 事件传递机制

    感谢大佬:https://www.jianshu.com/p/828550a1a0bf
    感谢大佬:https://www.cnblogs.com/shakinghead/p/10685148.html
    感谢大佬:https://blog.csdn.net/lbcwnu/article/details/82217907

    简略版

    1.控件的Listener事件触发的顺序是先onTouch,再onClick。
    2.控件的onTouch返回true,将会onClick事件没有了—阻止了事件的传递。
    返回false,才会传递onClick事件(才会传递up事件)


    补充:

    Android View的事件传递及滑动冲突

    ##事件传递顺序

    当用户点击屏幕产生一个事件,事件通过底层硬件捕获,然后交给ViewRootImpl处理,ViewRootImpl通过Window将事件交给Activity。
    事件要传递给Activity那么它就必须持有Activity的引用,Window在Activity的attach方法中通过mWindow.setCallback(this)调用持有了Activity的引用,Activity实现了Window.Callback的接口方法。所以最终事件是通过Window.Callback.dispatchTouchEvent把时间交给Acitivity的。

    /**
     * API from a Window back to its caller.  This allows the client to
     * intercept key dispatching, panels and menus, etc.
     */
    public interface Callback {
    
        public boolean dispatchKeyEvent(KeyEvent event);
    
        public boolean dispatchKeyShortcutEvent(KeyEvent event);
        
        public boolean dispatchTouchEvent(MotionEvent event);
        ...........
    }
    
    

    事件传递顺序
    硬件 -> ViewRootImpl -> Window -> Activity -> PhoneWindow -> DecorView -> VIewGroup -> View
    事件传递中主要的三个方法dispatchTouchEvent() onInterceptTouchEvent() 和 onTouchEvent()

    ##View的事件处理流程

    Activity的事件处理流程

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

    getWindow()即为PhoneWindow,通过跟踪源码可以看到PhoneWinodw调用了DectorView最后到ViewGroup,最终调用的是ViewGroup.superDispatchTouchEvent(ev)。

    Activity的事件传递流程 Activity -> PhoneWindow -> DectorView -> ViewGroup
    如果ViewGoup处理了事件则dispatchTouchEvent就结束了
    如果ViewGroup没有处理事件则由Activity.onTouchEvent方法自己处理

    在这里插入图片描述

    ViewGroup的事件处理流程图

    在这里插入图片描述

    View的事件处理流程图

    在这里插入图片描述

    ##View的滑动冲突

    当我们内外两层View都可以滑动时候,就会产生滑动冲突。滑动冲突有两种形式,内外两层滑动方向不一致和滑动一致。不管是哪种形式,我们只需要根据我们的逻辑考虑什么时候需要外层View处理滑动,什么时候需要内层View处理滑动即可。

    滑动冲突处理的方式有两种,外部拦截法和内部拦截法。

    ####外部拦截法
    外部拦截法是父View根据需要对事件进行拦截。逻辑处理放在父View的onInterceptTouchEvent方法中。我们只需要重写父View的onInterceptTouchEvent方法,并根据逻辑需要做相应的拦截即可。

    根据业务逻辑需要,在ACTION_MOVE方法中进行判断,如果需要父View处理则返回true,否则返回false,事件分发给子View去处理。
    ACTION_DOWN 一定返回false,不要拦截它,否则根据View事件分发机制,后续ACTION_MOVE 与
    ACTION_UP事件都将默认交给父View去处理
    ACTION_UP也需要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也无法触发
    外部拦截法子View不需要做任何处理

    ####内部拦截法
    内部拦截法父View拦截除ACTION_DOWN以外的其它事件。子View在ACTION_DOWN中调用getParent().requestDisallowInterceptTouchEvent(true)方法接管事件并在ACTION_MOVE中根据业务逻辑决定事件是否教给父View处理。如需交给父View处理则调用requestDisallowInterceptTouchEvent(false)方法。内部拦截法不符合事件分发流程,是通过子VIew反向控制父View拦截。伪代码如下:

    /**
     * 内部拦截法
     * 父View需拦截除DOWN以外的其他事件
     */
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            super.onInterceptTouchEvent(ev);
            return false;
        } else {
            return true;
        }
    }
    
    //子View.dispatchTouchEvent
    public boolean dispatchTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
    
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                parent.requestDisallowInterceptTouchEvent(true);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (父容器需要此类点击事件) {
                    parent.requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
            default:
                break;
        }
    
        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(event);
    }
    
    //父View.onInterceptTouchEvent
    public boolean onInterceptTouchEvent(MotionEvent event) {
    
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }
    
    

    上述代码是内部拦截的典型代码,当面对不同的滑动策略时只需要修改里面的条件即可,其他不需要做改动而且也不能有改动。

    父元素要默认拦截除了ACTION_DOWN以外的其他事件
    子元素调用parent.requestDisallowInterceptTouchEvent(false/true)来控制父元素是否拦截事件
    父元素不能拦截ACTION_DOWN因为它不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View


    补充:Android事件传递、多点触控及滑动冲突的处理 https://www.jianshu.com/p/7e21896a2c67

  • 相关阅读:
    通用权限管理设计 之 数据库结构设计
    jQuery LigerUI 插件介绍及使用之ligerDateEditor
    jQuery LigerUI 插件介绍及使用之ligerTree
    jQuery LigerUI V1.01(包括API和全部源码) 发布
    jQuery liger ui ligerGrid 打造通用的分页排序查询表格(提供下载)
    jQuery LigerUI V1.1.5 (包括API和全部源码) 发布
    jQuery LigerUI 使用教程表格篇(1)
    jQuery LigerUI V1.0(包括API和全部源码) 发布
    jQuery LigerUI V1.1.0 (包括API和全部源码) 发布
    nginx keepalived
  • 原文地址:https://www.cnblogs.com/tfxz/p/12621627.html
Copyright © 2011-2022 走看看