zoukankan      html  css  js  c++  java
  • Android触摸反馈

    事件分发

    • 当点击事件发生时,事件最先传递给Activity,Activity会首先将事件将被所属的Window进行处理,即调用 superDispatchTouchEvent() 方法。通过观察superDispatchTouchEvent()方法的调用链,我们可以发现事件的传递顺序:
    1. PhoneWinodw.superDispatchTouchEvent()
    2. DecorView.dispatchTouchEvent(event)
    3. ViewGroup.dispatchTouchEvent(event)
    • 事件一层层传递到了ViewGroup里。
    • 每到一个子view,看他的onInterceptTouchEvent 方法是否拦截,ontouch是否消费方法,如果没有继续向下dispatchTouchEvent分发事件,都不处理向上传,当回到顶级,若顶层(activity)也不对此事件进行处理,此事件相当于消失了(无效果)。
    • View没有onInterceptTouchEvent()方法,有dispatchTouchEvent,一但有点击事件传递给它,它的ouTouchEvent()方法就会被调用。
    • ouTouchEvent是否消费事件取决于 ACTION_DOWN 事件 或 POINT_DOWN 事件是否返回 为true

    递归
    ViewGroup(View).dispatchTouchEvent()

    • ViewGroup.onInterceptTouchEvent()
    • child.dispatchTouchEvent()
    • super.dispatchTouchEvent()
      • View.onTouchEvent()

    一个场景

    有一个 ViewGroup, 然后手指头接触 Button ,手指头滑开了,滑开又松手的过程,整个事件发生了什么?经历了什么?

    一开始ViewGroup 会接受到整个事件序列的第一个事件: ACTION_DOWN,ViewGroup#dispatchTouchEvent 收到ACTION_DOWN 后,
      开始询问 ViewGroup#onInterceptTouchEvent 是否需要拦截,
      默认情况下 ViewGroup#onInterceptTouchEvent 返回false 不拦截,开始向下传递ACTION_DOWN 事件,
      Buttton#dispatchTouchEvent 收到ACTION_DOWN 询问onTouchEvent 是否处理,
      Button 默认处理,此后的所有事件序列都直接跨过 ViewGroup#onInterceptTouchEvent 的判断直接传递给Button,
      但 ViewGroup#dispatchTouchEvent 会收到所有事件。随着手指的滑动Button 的坐标发生了改变,当手指抬起时触发 Button#onClick 事件。(移动出自己的范围,就消失了)

    事件冲突

    不同向嵌套

    1. 外部处理,重写父view的onInterceptTouchEvent ,MotionEvent的事件全部返回false,不拦截;
    2. 内部处理。重写子view的dispatchTouchEvent,通过requestDisallowInterceptTouchEvent方法(这个方法可以在子元素中干预父元素的事件分发过程),请求父控件不拦截自己的事件,true是不拦截,false是拦截。

    同向嵌套
    父 View 会彻底卡住子 View
    原因:抢夺条件一致,但 父 View 的 onInterceptTouchEvent() 早于子View 的 dispatchTouchEvent()
    本质上是策略问题:嵌套状态下用户手指滑动,他是想滑谁?
    解决⽅方案:
    实现策略—父 View、子 View 谁来消费事件可以实时协商

    1. 换成 NestedScrollView:可以滑动
    2. 实现 NestedScrollingChild3 接口来实现自定义的嵌套滑动逻辑

    自定义单 View 的触摸反馈

    View.onTouchEvent()

    • 当用户按下(ACTION_DOWN):
      • 如果不在滑动控件中,切换至按下状态,并注册长按计时器
      • 如果在滑动控件中,切换至预按下状态,并注册按下计时器
    • 当进入按下状态并移动(ACTION_MOVE):
      • 重绘 Ripple Effect
      • 如果移动出自己的范围,自我标记本次事件失效,忽略后续事件
    • 当用户抬起(ACTION_UP):
      • 如果是按下状态并且未触发长按,切换至抬起状态并触发点击事件,并清除⼀切状态
      • 如果已经触发长按,切换至抬起状态并清除一切状态
    • 当事件意外结束(ACTION_CANCEL):
      • 切换至抬起状态,并清除一切状态

    View.dispatchTouchEvent()

    View中 setOnTouchListener的onTouch,onTouchEvent,onClick的执行顺序
    View的dispatchTouchEvent源码

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

    当以下三个条件任意一个不成立时

    1. mOnTouchListener不为null
    2. view是enable的状态
    3. mOnTouchListener.onTouch(this, event)返回true
      函数会执行到onTouchEvent。在这里我们可以看到,首先执行的是mOnTouchListener.onTouch的方法,然后是onTouchEvent方法继续追溯源码,到onTouchEvent()观察,发现在处理ACTION_UP事件里有这么一段代码
    if (!post(mPerformClick)) {        performClick();    }
    

    此时可知,onClick方法也在最后得到了执行所以三者的顺序是:

    1. setOnTouchListener() 的onTouch
    2. onTouchEvent()
    3. onClick()

    view的事件分发:View为啥会有dispatchTouchEvent方法?
    View可以注册很多事件监听器,事件的调度顺序是onTouchListener> onTouchEvent>onLongClickListener> onClickListener

    自定义 ViewGroup 的触摸反馈

    • 除了重写 onTouchEvent() ,还需要重写 onInterceptTouchEvent()
    • onInterceptTouchEvent() 不用在第一时间返回 true,而是在任意事件,需要拦截的时候返回 true 就行
        //伪代码
        view.dispatchTouchEvent();
    
        public boolean dispatchTouchEvent(MotionEvent event) {
            return ontouchEvent();
        }
    
        ViewGroup.dispatchTouchEvent();
    
        public boolean dispatchTouchEvent(MotionEvent event) {
            boolean result;
            if (interceptTouchEvent()) {
                result = ontouchEvent();
            } else {
                result = 子 view的 dispatchTouchEvent ();
            }
            return result;
        }
    

    ViewGroup.dispatchTouchEvent()

    • 如果是用户初次按下(ACTION_DOWN),清空 TouchTargets 和DISALLOW_INTERCEPT 标记
    • 拦截处理
    • 如果不拦截并且不是 CANCEL 事件,并且是 DOWN 或者 POINTER_DOWN,尝试把 pointer(手指)通过 TouchTarget 分配给子 View;并且如果分配给了新的子 View,调用 child.dispatchTouchEvent() 把事件传给子 View
    • 看有没有 TouchTarget
      • 如果没有,调用⾃己的 super.dispatchTouchEvent()
      • 如果有,调用 child.dispatchTouchEvent() 把事件传给对应的子 View(如果有的话)
    • 如果是 POINTER_UP,从 TouchTargets 中清除 POINTER 信息;如果是 UP 或CANCEL,重置状态

    TouchTarget
    作用:记录每个子 View 是被哪些 pointer(手指)按下的
    结构:单向链表

    一般自定义onTouchEvent方法流程

    1. 在down的时候去记录坐标点
      getX/getY获取相对于当前View左上角的坐标,getRawX/getRawY获取相对于屏幕左上角的坐标。比如接触到按钮时,x,y是相对于该按钮左上点的相对位置。而rawx,rawy始终是相对于屏幕的位置。
    2. move的时候计算偏移量,并用scrollTo()或scrollBy()方法移动view。这俩个方法都是快速滑动,是瞬间移动的。注意:滚动的并不是viewgroup内容本身,而是它的矩形边框。
    3. 在up的时候,判断应显示的页面位置,并计算距离、滑动页面。
  • 相关阅读:
    [Codeforces-div.1 809C] Find a car
    [Codeforces-div.1 55D] Beautiful numbers
    [BZOJ3598] [Scoi2014]方伯伯的商场之旅
    [BZOJ3131] [Sdoi2013]淘金
    [BZOJ2757] [SCOI2012]Blinker的仰慕者
    [BZOJ3329] Xorequ
    [POJ3744] Scout YYF I
    angular-file-upload 回显已上传的文件
    angular-file-upload 限制文件上传个数 获取已上传文件队列
    angular-file-upload 一款好用的文件上传组件,基本应用
  • 原文地址:https://www.cnblogs.com/sixrain/p/11997411.html
Copyright © 2011-2022 走看看