美文网首页
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