View的事件体系
View基础知识
- x和y是View左上角的坐标
- translationX和translationY是VIew左上角相对于父容器的偏移量
MotionEvent
- 系统提供了两组方法:getX/getY和getRawx/getRawY,getX/getY返回的值相对于当前VIew左上角的x和y坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标。
TouchSlop
- 是系统所能识别出的被认为是滑动的最小距离。(用来判定是否滑动)
VelocityTracker
- 速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直速度
VelocityTracker velocityTracker = VelocityTracker.obtain();
int xVelocity = velocityTracker.getXVelocity();
int yVelocity = velocityTracker.getYVelocity();
- 速度是指一段时间内手指所滑动的像素数,比如将时间设置为1000ms,在1s内,手指在水平方向从左向右活动100像素,那么水平速度为100.
- 速度公式: 速度 = (终点速度 - 起点位置) /时间段
GestureDector
- 手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。
- 如果只是监听滑动相关的,建议自己在onTouchEvent中实现,如果要监听双击这种行为i的话,那么就是用GestureDectector.
Scroller
- Scroller本身无法让View弹性滑动,它须要和View的computeScroll方法配合使用 才能共同完成这个功能。
View的滑动
- 使用scrollTo/scrollBy实现View的滑动
public void scrollBy(int x, int y){
scrollTo(mScrollX + x, mScrollY + y);
}
- scrollBy相当于的当前的
相对滑动
,而scrollTo实现了传递参数的绝对滑动
- 在滑动过程中,
mScarollX的值总是等于View左边缘和View内容左边缘在水平方向的距离
,而mscrollY的值总等于View上边缘和View内容上边缘在竖直方向上的距离。 - scrollTo和scrollBy只能改变View内容的位置,而不能改变view在布局的位置
2.使用动画
- View动画是对View的影象做操作,
并不能真正改变View的位置参数
,包括宽/高。 - 从Android3.0开始,使用属性动画可以解决上面的问题。
3.改变布局参数
当使用当前是全屏滑动的时候,要获取当前点击事件在屏幕中的左标
(getRawX()和getRawY())
。而不是相对于View本身的坐标。
弹性滑动
有个共同思想:将一次大的滑动分成若干个滑动并在一个时间段内完成。
Scroller滑动机制
在startScroll方法下面的invalidate方法,会导致View重绘,在View的draw方法中又会调用computeScroll方法,反复调用直到整个活动过程结束。
View的时间分发机制
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
//返回为true,onTouchEvent被调用
consume = onTouchEvent(ev);
} else {
//不拦截事件会被分发,子元素的dispatchTouchEvent()将被调用
consume = child.dispatchTouchEvent(ev);
}
}
事件分发由三个方法组成:
dispatchTouchEvent
/- 用来进行事件的分发。如果事件能够传递给当前View,此方法会被调用。返回结构受当前View的onTouchEvent和下级View的disptchEvent方法影响,表示是否能够消耗此事件。
onInterceptTouchEvent
/- 表示是否拦截了当前事件
onTouchEvent
/在dispatchTOuchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件
在VIew处理事件时,设置OnTouchListener,此时OnTouchListener中过得OnTouch方法将会被调用,当它返回false的时候,View中的OnTOuchEvent方法会被回调。
onTouchListener优先级要高于onTouchEvent方法
- 而onClickListener的优先级最低
事件传递顺序:Activity->WIndow->View
子View中都不处理的话,最总会传给Activity处理
事件传递机制的一些总结
- 事件以down事件开始,最后以up结束
- 一个事件序列只能被一个VIew拦截且消耗
- 在
子View中onInterceptOnTouchEvent不会被调用
,如果决定拦截一个事件,系统会直接交给它处理 - 底下的View开始处理事件,但不消耗ACTION_VIEW,同一序列的其他事件都不会再交给他处理,最后返回到父元素的onTouchEvent会被调用
- 如果View不消耗除了ACTION_DOWN以外的事件,父元素的onTouch不会被调用,点击事件也会消失,最终被Activity处理
- ViewGroup默认不拦截任何事件
- View没有onInterceptTouchEvent事件
- View的OnTouchEvent默认都会消耗事件(不管clickable或longClickable同时为false)
- 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子元素
事件分发源码解析
这一小节读了两遍,每一遍都有不同的感悟,作者真的是写的好。
1.在Activity对点击事件的分发过程中,Window的实现类是phoneWindow
,其中她有个superDispatchTouchListener()方法,将事件传递给了mDecor,而mDecor代表的就是事件的顶级VIew.
2.顶级View对点击事件的分发过程
分发逻辑:
这是重点,要常看。
传递给VIewGroup之后会调用dispatchTouchEvent方法,然后逻辑是:如果顶级VIewGroup拦截事件即mOnTouchListenEvent返回true,则事件由ViewGroup处理,这时如果ViewGroup的mOntouchListener被设置,则onTouch被调用,否则onTouchEvent被调用。如果都提供的情况下,onTouch会屏蔽onTouchEvent,在onTouchEvent中,如果设置了mOnClickListener,则onClick被调用,如果事件不处理,将会分发给子View。
- 其中有个mFirstTouchTaget表示ViewGroup不拦截事件交由子元素处理时,mFirstTouchTarget!=null,这个会在子VIew中被赋值
FLAG_DISALLOW_INTERCEPT这个标志位的作用让ViewGroup不再拦截事件
3.View对点击事件的分发过程
- View没有子元素无法进行向下分发,所以只能自己处理事件。
- 只要View的CLICKABLE和LONG_CLICKABLE由一个为true,那么它就会消耗这个事件,不管它是不是DISABLE状态。
View的滑动冲突
滑动处理解决方式
场景一时,根据滑动过程中两个点之间的坐标就可以得出到底是水平滑动还是竖直滑动。场景二时,根据业务判断相应的滑动规则。
- 外部拦截法:
在onInterceptTouchEvent方法中,三种事件的判断:
ACTION_DOWN
,返回false表示不拦截。如果拦截的话,后续的ACTION_MOVE和ACTION_UP都会直接交给父容器处理。
ACTION_MOVE
:这个事件可以提供需要来决定是否拦截
ACTION_UP
必须false 内部拦截法
在子元素中需要处理此事件的话,要配合requestDisallowInterceptTouchEvent方法才能正常工作。puiblci boolean disaptchTouchEvent(MotionEvent event){ ··· switch (event.getAction()){ case MotionEvent.ACTION_DOWN:{ //表示拦截事件,可以阻止父层的View截获touch事件 parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE:{ int deltax = x - mLastX; int deltaY = y - mLastY; if(父容器需要此类点击事件{ parent.requsetDisallowInterceptTouchEvent(false); } } } ··· }
父元素不能拦截
ACTION_DOWN
,将会导致所有事件都无法传递给子元素。
代码总结