View的事件体系
2022-06-11 本文已影响0人
carlwu_186
- View的真实位置由它的四个顶点来决定,分别对应于View的四个属性:top、left、right、bottom。这些坐标都是相对于View的父容器来说的。
- Android3.0开始View增加了额外的几个参数:x、y、translationX、translationY,它们也是相对于父容器的坐标,x和y是View内容左上角的坐标,translationX和translationY默认值都是0。
- View的平移(修改
translationX
或translationY
),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内部两个属性
mScrollX
和mScrollY
的值表示View边缘和View内容边缘的距离。使用scrollTo和scrollBy来实现View的滑动,只能将View的内容在View控件的内部进行移动,并不能将View本身进行移动。x、y、translationX、translationY不会因为scrollTo和scrollBy而变化。 - 通过View动画可以对View的影像做移动操作,View的真正位置参数不被改变。View影像移动后,点击事件依然在原本位置,新位置不会触发onClick事件。x、y、translationX、translationY不会因为View动画的执行而变化。
- 通过属性动画修改
translationX
或translationY
,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.pngViewGroup的事件分发机制
image.pngView的事件分发机制
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再也不会收到该事件列产生的后续事件。