美文网首页
View的事件体系

View的事件体系

作者: carlwu_186 | 来源:发表于2022-06-11 16:36 被阅读0次
    • View的真实位置由它的四个顶点来决定,分别对应于View的四个属性:top、left、right、bottom。这些坐标都是相对于View的父容器来说的。
    • Android3.0开始View增加了额外的几个参数:x、y、translationX、translationY,它们也是相对于父容器的坐标,x和y是View内容左上角的坐标,translationX和translationY默认值都是0。
    • View的平移(修改translationXtranslationY),top、left表示的是原始左上角的位置信息,其值不会发生变化,发生变化的是x、y、translationX、translationY。
    • MotionEvent提供了getX/getY 和 getRawX/getRawY,getX/getY返回相对于当前View左上角的x和y坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标。
    • TouchSlop是系统所能识别出的被认为是滑动的最小距离,这是一个常量,和设备有关。在处理滑动时,可以利用这个常量来做一些过滤。
    • VelocityTracker,速度追踪器,用于追踪手指在滑动过程中的速度。
    • GestureDetector,手势检测,用于辅助检测用户的单机、滑动、长按、双击等行为。
    • Scroller,弹性滑动对象,用于实现View的弹性滑动。View的scrollTo/scrollBy方法来进行滑动时,其过程是瞬间完成的。这时可以使用Scroller来实现有过渡效果的滑动,在一定的时间间隔内完成。典型代码是固定的:
    Scroller scroller = new Scroller(mContent);
    private void smoothScrollTo(int destX,int destY) {
        int scrollX = getScrollX();
        int delta = destX =scrollX;
        //1000ms内滑向destX,效果就是慢慢滑动
        mScroller.startScroll(scrollX,0,delta,0,1000);
        invalidate();
    }
    
    @Override
    public void computeScroll() {
        if(mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }
    
    • View内部两个属性mScrollXmScrollY的值表示View边缘和View内容边缘的距离。使用scrollTo和scrollBy来实现View的滑动,只能将View的内容在View控件的内部进行移动,并不能将View本身进行移动。x、y、translationX、translationY不会因为scrollTo和scrollBy而变化。
    • 通过View动画可以对View的影像做移动操作,View的真正位置参数不被改变。View影像移动后,点击事件依然在原本位置,新位置不会触发onClick事件。x、y、translationX、translationY不会因为View动画的执行而变化。
    • 通过属性动画修改translationXtranslationY,View的内容可以移动,新位置触发点击事件,View原本位置不变化,x、y变化。

    View的事件分发机制

    public boolean dispatchTouchEvent(MotionEvent ev)
    

    用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。

    public boolean onInterceptTouchEvent(MotionEvent ev)
    

    在上述方法内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个时间,那么在同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。

    public boolean onTouchEvent(MotionEvent ev)
    

    在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
    伪代码:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if(onInterceptTouchEvent(ev)){
            if(onTouchListener?.onTouch(ev)){//onTouchListener优先级比onTouchEvent高
                consume = true;
            }else{
                consume = onTouchEvent(ev);
            }
        }else{
            consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }
    
    public boolean onTouchEvent(MotionEvent ev){
        onClickListener?.onClick()//OnClickListener优先级最低,在onTouchEvent方法内部被调用
    }
    
    • 如果一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,以此类推。如果所有的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用。
    • 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理,并且它的onInterceptTouchEvent不会再被调用。
    • 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。
    • 如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的事件会传递给Activity处理。
    • ViewGroup默认不拦截任何事件。
    • View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
    • View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable默认都为false,clickable要分情况,比如Button的clickable默认为true,而TextView的clickable默认为false。
    • View的enable属性不影响onTouchEvent的默认返回。哪怕一个View是disable状态,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true。

    P143

    Activity的事件分发机制

    image.png

    ViewGroup的事件分发机制

    image.png

    View的事件分发机制

    image.png
    /**
      * 源码分析:View.dispatchTouchEvent()
      */
      public boolean dispatchTouchEvent(MotionEvent event) {  
    
           
            if ( (mViewFlags & ENABLED_MASK) == ENABLED && 
                  mOnTouchListener != null &&  
                  mOnTouchListener.onTouch(this, event)) {  
                return true;  
            } 
    
            return onTouchEvent(event);  
      }
      // 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()
      //   1. (mViewFlags & ENABLED_MASK) == ENABLED
      //   2. mOnTouchListener != null
      //   3. mOnTouchListener.onTouch(this, event)
    
    • 只有View是enable状态时,mOnTouchListener 才有可能被执行。
    • 只有View是clickable状态时,mOnClickListener 才有可能被执行。

    工作流程总结

    image.png
    • 虽然ViewGroup B的onInterceptTouchEvent()对DOWN事件返回了false,但后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent(),这一点与onTouchEvent()的行为是不一样的:不再传递 & 接收该事件列的其他事件。
    • 若 ViewGroup 拦截了一个半路的事件(如MOVE),该事件将会被系统变成一个CANCEL事件 & 传递给之前处理该事件的子View;该事件不会再传递给ViewGroup 的onTouchEvent(),后续MOVE事件将直接传递给ViewGroup B 的onTouchEvent()处理,而不会再传递给ViewGroup B 的onInterceptTouchEvent(),因该方法一旦返回一次true,就再也不会被调用了。之前的子View再也不会收到该事件列产生的后续事件。

    相关文章

      网友评论

          本文标题:View的事件体系

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