1.View的基础知识
View的位置参数
由该图可以得到,View的宽高为:
width = right - left
height = bottom - top
坐标:getX/getY返回的是相对于当前View左上角的x和y的坐标,而getRawX和getRawY返回的是相对于屏幕左上角的x和y坐标。
TouchSlop:系统所能识别的滑动的最小距离,滑动距离太短,系统不认为在滑动。获取常量:ViewConfiguration.get(getContext.getScaledTouchSlop())。
VelocityTracker:追踪手指在滑动过程中的速度,包括水平速度和竖直方向的速度。
GestureDetector:手势检测,用于辅助检测用户的单击,滑动,长按,双击等行为。
Scoller:弹性滑动对象,实现View的弹性滑动,有过渡效果的滑动。
View的滑动:scrollerTo/scrollerBy:scrollerBy实际调用了scrollerTo方法,实现基于当前位置的相对滑动,而scrollerTo实现了基于所传递参数的绝对滑动。
三种View滑动方式的区别:
- scrollerTo/scrollerBy:操作简单,适用于对View内容的滑动。
- 动画:操作简单,适用于没有交互的View和实现复杂的动画效果。
- 改变布局参数:适用于有交互的View.
2.点击事件传递规则
2.1.点击事件的研究对象
点击事件要研究的对象就是MotionEvent即点击事件,点击事件就是手指触摸到屏幕出现的一系列事件。
- ACTION_DOWN:手指刚接触屏幕。
- ACTION_MOVE:手指在屏幕上移动。
- ACTION_UP:手指从屏幕上松开的一瞬间。
一次手指触摸屏幕的行为会触发一系列的点击事件。
- 点击屏幕后松开:事件序列为DOWN->UP.
- 点击屏幕滑动一会松开:事件序列为DOWN->MOVE->…->MOVE->UP.
同一事件序列指从手指接触屏幕开始到手指离开屏幕的那一刻结束所产生的一系列事件,这个事件序列从down事件开始,中间含有数量不定的move事件,最终以up事件结束。
2.2.点击事件的传递规则
点击事件的事件分发就是对MotionEvent事件的分发过程,点击事件的分发过程是由三个方法共同完成的:diapatchTouchEvent,onInterceptTouchevent和onTouchEvent。
- public boolean dispatchTouchEvent(MotionEvent e):用来进行事件的分发,如果事件能够传递给当前View,此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
- public boolean onInterceptTouchEvent(MotionEvent e),在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前View拦截的某个事件,那么在同一事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件。
- public boolean onTouchEvent(MotionEvent e),在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再次接收到事件。
点击事件的传递规则: 当一个点击事件产生后,它的传递规则遵循以下顺序:Activity->Window->View(Activity->ViewGroup->View),即事件总是先传递给Activity,Activity再传递给Window,最后Window再传递给顶级View,顶级View接收到事件后,就会按照事件的分发机制去分发事件。
某个View一旦决定拦截,那么一个事件序列都只能由它来处理并且它的onInterceptTouchEvent不会在被调用,一个事件序列只能被一个View拦截并消耗。
某个View一旦开始处理事件,如果不消耗ACTION_DOWN事件(onTouchEvent返回false)那么同一事件序列中的其它事件都不会再交给他处理,并且事件将重新交给它的父元素处理。
ViewGroup默认不拦截任何事件,onInterceptTouchEvent默认返回false。
View没有onInterceptTouchEvent方法,一旦由点击事件传递给它,那么它的onTouchEvent就会被调用。
事件传递都是由外向内的,即事件总是先传递给父元素,再由父元素分发给子View。
ViewGroup的事件传递流程: 点击事件产生后,首先会传递给它,此时它的dipatchTouchEvent()就会被调用,如果ViewGroup的onInterceptTouchEvent返回true,表示它要拦截此事件,接着事件就会交给这个ViewGroup来处理即它的onTouchEvent方法就会被调用,如果ViewGroup的onInterceptTouchEvent返回false表示它不拦截当前事件,这时当前事件就会传递给它的子元素处理,子元素的dispatchTouchEvent就会被调用,如此反复直到事件被最终处理。
View的事件传递流程 :当事件到达View时,View的dispatchTouchEvent就会被调用,如果它设置了onTouchListener,那么onTouchLiistener的onTouch方法就会被回调,此时事件的处理看onTouch的返回值,如果返回true那么View的onTouchEvent方法将不会被调用,事件被消耗不再向下传递。如果返回false那么View的onTouchEvent方法将会被调用,然后事件向下传递就会调用performClick,然后如果设置了onClickListener方法,该方法就会被调用,事件传递结束。所以给View设置onTouchListener,其优先级比onTouchEvent要高,平常使用onClickListener优先级最低,处于事件传递尾端。
3.View的滑动冲突
界面中要内外两层可以同时滑动,就会产生滑动冲突。
3.1.常见的滑动冲突场景
- 外部滑动方向和内部滑动方向不一致
- 外部滑动方向和内部滑动方向一致
- 上述两种情况的嵌套
3.2.滑动冲突的处理规则
对于外部和内部滑动方向不一致的场景,处理规则是:当用户左右滑动时,需要让外部View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事件。处理规则一般是根据它的特征来解决滑动冲突。
3.3.滑动冲突的处理方式
外部拦截法: 点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做响应拦截即可。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if ("父容器需要当前点击事件") {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
mLastXIntercept = x;
mLatYIntercept = y;
return intercepted;
}
对于ACTION_DOWN事件,父容器必须返回false,一旦拦截了此事件后续的move和up事件都会交给父容器处理,此时事件就无法传递给子元素了,对于ACTION_MOVE事件,根据需要决定是否拦截,如果父容器需要拦截就返回true,否则返回false,对于ACTION_UP事件,必须要返回false,假如拦截了此事件,子元素无法收到此事件,onClick事件就无法触发。
内部拦截法: 父容器不拦截任何事件,所有的事件都传递给子元素,子元素需要就直接消耗,否则就交给父元素处理,需要配合requestDisallowInterceptTouchEvent方法,重写子元素的dispatchTouchEvent方法。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if ("父容器需要此类点击事件") {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(ev);
}