美文网首页
View事件体系

View事件体系

作者: 安卓小白之小楼又东风 | 来源:发表于2018-12-16 17:26 被阅读23次

    标签(空格分隔): android


    View的定义

    View是android中所有控件的基类,不管是简单的Button还是TextView或者复杂的RelativeLayout和ListView,它们的基类都是View。

    view的位置参数

    • view的位置主要由四个顶点来决定的,分别对应View的四个属性:top、left、right、bottom。
      top是指左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角的纵坐标。
    • 这些坐标都是相对于父容器来说的,是一种相对路径。
    view的位置坐标和父容器的关系view的位置坐标和父容器的关系

    height = bottom-top;
    width = right-left;

    如何获取它们:
    left = getLeft();
    right = getRight();
    top = getTop();
    bottom = getBottom();

    其他的位置参数:x,y,translationX,translationY。

    • x,y是View左上角的坐标,translationX,tranlationY是View左上角相对于父容器的偏移量。
    • 转换公式:
    • x = left + translationX;
    • y = top + translationY;

    MotionEvent 和 TouchSlop

    1、MotionEvent

    • ACTION_DOWN----手指刚接触屏幕。
    • ACTION_MOVE----手机在屏幕上移动。
    • ACTION_UP------手机从屏幕松开的一瞬间。

    通常情况一次手机触摸的触发事件:

    • 点击屏幕后离开,事件序列为DOWN--->UP.
    • 点击屏幕滑动一会再松开,事件序列为DOWN-->MOVE---> ....--->MOVE--->UP

    通过MotionEvent对象获取下,下,x,y坐标。

    getX/getY返回当前View左上角的下,x、y坐标,getRawX/getRawY返回手机屏幕的x、y坐标。

    2、TouchSlop
    TouchSlop是系统所能识别出的被认为是滑动的最小距离。

    不同的设备的这个值不同,获取这个常量需要用:ViewConfiguration.get(getContext()).getScaledTouchSlop().

    VelocityTracker GestureDetector Scroller

    1、VelocityTracker
    速度追踪,用于追踪手指在滑动过程中的速度。(水平速度和竖直速度)
    获取滑动速度代码:

    VelocityTracker velocityTracker = VelocityTracker.obtain();//创建对象
    velocityTracker.addMovement(event);//添加事件 velocityTracker.computeCurrentVelocity(1000);//设置事件间隔
    //获取速度(像素)
    int xVelocity = (int)velocityTracker.getXVelocity();
    int yVelocity = (int)velocityTracker.getYVelocity();
    

    重置、回收内存:

     velocityTracker.clear();
     velocityTracker.recycle();
    

    注意:手指逆着坐标系的正方向滑动,产生速度是负值,正负号表示方向。
    2、GestureDetector
    手势检测,用于辅助用户单击、滑动、长按、双击等行为.
    使用过程:
    1、创建GestureDetetor对象,实现OnGestureListener、OnDoubleTapListener接口。
    2、接管目标View的onTouchEvent方法.

    一些回调接口:(参考于这篇博文)
    1.OnGestureListener,这个Listener监听一些手势,如单击、滑动、长按等操作:

    • onDown(MotionEvent e):用户按下屏幕的时候的回调。
    • onShowPress(MotionEvent e):用户按下按键后100ms(根据Android7.0源码)还没有松开或者移动就会回调。
    • onLongPress(MotionEvent e):用户长按后(好像不同手机的时间不同,源码里默认是100ms+500ms)触发,触发之后不会触发其他回调,直至松开(UP事件)。
    • onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):手指滑动的时候执行的回调(接收到MOVE事件,且位移大于一定距离),e1,e2分别是之前DOWN事件和当前的MOVE事件,distanceX和distanceY就是当前MOVE事件和上一个MOVE事件的位移量。
    • onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY):用户执行抛操作之后的回调,MOVE事件之后手松开(UP事件)那一瞬间的x或者y方向速度,如果达到一定数值(源码默认是每秒50px),就是抛操作(也就是快速滑动的时候松手会有这个回调,因此基本上有onFling必然有onScroll)。
    • onSingleTapUp(MotionEvent e):用户手指松开(UP事件)的时候如果没有执行onScroll()和onLongPress()这两个回调的话,就会回调这个,说明这是一个点击抬起事件,但是不能区分是否双击事件的抬起。

    2.OnDoubleTapListener,这个Listener监听双击和单击事件。

    • onSingleTapConfirmed(MotionEvent e):可以确认(通过单击DOWN后300ms没有下一个DOWN事件确认)这不是一个双击事件,而是一个单击事件的时候会回调。
    • onDoubleTap(MotionEvent e):可以确认这是一个双击事件的时候回调。
    • onDoubleTapEvent(MotionEvent e):onDoubleTap()回调之后的输入事件(DOWN、MOVE、UP)都会回调这个方法(这个方法可以实现一些双击后的控制,如让View双击后变得可拖动等)。

    3.OnContextClickListener,很多人都不知道ContextClick是什么,我以前也不知道,直到我把平板接上了外接键盘——原来这就是鼠标右键。。。

    • onContextClick(MotionEvent e):当鼠标/触摸板,右键点击时候的回调。

    4.SimpleOnGestureListener,实现了上面三个接口的类,拥有上面三个的所有回调方法。

    • 由于SimpleOnGestureListener不是抽象类,所以继承它的时候只需要选取我们所需要的回调方法来重写就可以了,非常方便,也减少了代码量,符合接口隔离原则,也是模板方法模式的实现。而实现上面的三个接口中的一个都要全部重写里面的方法,所以我们一般都是选择SimpleOnGestureListener。

    3、Scroller
    弹性滑动对象,用于实现View的弹性滑动。
    用Scroller来实现有过渡效果的滑动,其过程不是瞬间完成的。

    View的滑动

    常见的滑动:

    • 通过View本身提供的scrollTO/scrollBy方法实现。
    • 通过动画给View施加平移效果。
    • 通过改变LayoutParams使View重新布局。

    1、使用ScrollTo、ScrollBy

    scrollBy实际上是调用了scrollTo方法,实现了基于当前位置的相对滑动,srcollTo是实现了基于参数的绝对滑动。
    scrollTo和scrollBy只能改变View内容的位置而不能改变View在布局中的位置。
    mScrollX,mScrollY的单位是像素,可以是负值,符号表示方向。
    重要的源码:

    public void scrollTo(int x,int y){
        if(mScrollX != x || mScrollY != y){
           int oldX = mScrollX;
           int oldY = mScrollY;
           mScrollX = x;
           mScrollY = y;
           invalidateParentCaches();
           onScrollChanged(mScrollX,mScrollY,oldX,oldY);
           if(!awakenScrollBars()){
              postInvalidateOnAnimation();
           }
        }
    }
    
    public void scrollBy(int x,int y){
      scrollTo(mSrollX+x,mScrollY+y);
    }
    

    2、使用动画

    使用动画移动View,主要是操作View的translationX和translationY属性,可采用传统动画和属性动画。
    View动画已经逐渐被废弃,不是改变view的位置,动画完成瞬间,回到原位置。
    属性动画:

    ObjectAnimator.dfFloat(targetView,"translationX",0,100).setDuration(100).start();
    

    3、使用延时策略

    核心思想:通过发送一系列的延时消息从而达到一种渐进式的效果,可以使用Handler、view的postDelayed方法,也可以使用线程中的sleep方法。

    • 对于View滑动就是不断地发送延时消息,达到弹性滑动的效果。
    • 对于sleep方法,就是通过while循环中不断的滑动和sleep。

    View的事件分发机制

    定义:当一个MotionEvent产生后,系统需要把这个事件传递给一个具体的View,而传递的过程就是分发过程。

    常见的一些方法:
    dispatchTouchEvent:用于进行事件的分发,返回值为boolean,表示是否消耗当前事件。
    onInterceptTouchEvent:用来判断是否拦截某个事件,返回值为boolean,表示是否拦截当前事件。
    onTouchEvent:用于处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则同一事件序列中,当前View无法再次接收到事件。

    点击事件产生后遵循顺序:Activity--》window--》View

    同一事件序列是指从手指接触屏幕开始到离开屏幕这个过程中所产生一系列事件,这个事件序列从down开始,中间有一系列的move,最终以up事件结束。

    1、Activity对点击事件的分发过程

    Activity # dispatchTouchEvent

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

    首先事件开始交给Activity所附属的Window进行分发,返回true,返回false就意味着事件没人处理,所有的View的onTouchEvent都返回了false,那么Activity的onTouchEvent就会被调用。

    window的唯一实现类是PhoneWindow,PhoneWindow将事件传递给了DecorView。DecorView继承于FrameLayout,是父View。

    2、顶级View对点击事件的分发过程

    主要逻辑:如果顶级ViewGroup拦截事件,就是onInterceptTouchEvent返回true,事件由ViewGroup处理,这时如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用。否则onTouchEvent会被调用。在onTouchEvent中,设置了mOnclickListener,则onClick方法会被调用。如果ViewGroup不拦截事件,事件会传递给它所在的点击事件链的上的子View,这时子View中的dispatchTouchEvent方法会被调用。

    ViewGroup在事件分发时,如果时ACTION_DOWN会被重置FLAG_DISALLOW_INTERCEPT这个标记位,将导致子View中设置标记位无效。

    当ViewGroup决定拦截事件后,后续点击事件将会默认交给它处理并且不会调用它的onInterceptTouchEvent方法。onInterceptTouchEvent不是每次事件都被调用的,如果要提前处理所有点击事件,要选择dispatchYouchEvent方法,只有这个方法能够确保每次都会调用。

    是否接受点击事件主要由两点衡量:子元素是否播放动画和点击事件的坐标是否落在子元素的区域内。

    通过for循环遍历ViewGroup中子元素,如果子View满足接受点击事件的条件,子元素直接调用dispatchTouchEvent方法,如果返回值为true,事件就被分发到子元素,mFirstTouchTarget被赋值跳出for循环。

    mFirstTouchTarget是一种单链表结构,如果它为null,ViewGroup就默认拦截接下来同一事件序列的所有点击事件。遍历所有的子元素后,没有被合适的处理就说明ViewGroup中没有子元素或者子元素的onTouchEvent返回为false。

    3、View对点击事件的处理过程

    优先级比较:
    OnTouchListener > onTouchEvent > onClick

    只要View的CLICKBLE和LONGCLICKBLE有一个是true,View就会消耗这个事件,即onTouchEvent返回true。当ACTION_UP发生时,会触发performClick方法,如果View被设置了onClickListener,那么performClick内部会调用它的onClick方法。

    滑动冲突

    1、外部拦截法:通过父容器需要对此事件拦截,如果不需要就不拦截。

    在ACTION_DOWN必须返回false,否则一旦拦截,后续的事件序列都会被拦截,没法传递给子View了,ACTION_MOVE根据需求去返回false或true,ACTION_UP必须返回false,其本身没有意义,否则子元素无法接受up事件,onClick没有被触发。

    2、内部拦截法:父容器不拦截任何事件,所有的事件都传递给子元素,直接被消耗掉,否则返回给父元素,需要配合requestDisallowInterceptTouchEvent。

    相关文章

      网友评论

          本文标题:View事件体系

          本文链接:https://www.haomeiwen.com/subject/ivsxkqtx.html