目录
前言
一、MotionEvent类
二、事件传递对象的顺序
三、事件传递过程的方法
四、源码分析
记得有人这样过说:View的事件传递和分发是个看起来简单、学起来也不难、讲起来却憋死个人、用起来又需要充实的知识和编程经验。没错,事件分发机制确实就只有几个函数而已,看起来好像也不难理解,实际学习也不难。
那接下来我们一起梳理一下Android的事件分发机制的相关知识吧!
首先,我们来认识触摸事件封装的一个对象MotionEvent:
这个类中包装了很多触摸事件,如按下、抬起、滑动等。而且通过这个类可以获取到触摸事件的信息,如x\y坐标值、事件类型、时间等。
1、public final float getRawX() / getRawX() :屏幕坐标
触摸点在屏幕上的绝对坐标;坐标值相对于屏幕而言。
2、public final float getY() / getY(int index) / getX() / getX(int index) :视图坐标
触摸点基于该View的坐标值;有参数的方法则会返回某个点的坐标值,无参数的方法返回index为0的点的坐标值;index值范围从0到getPointerCount() - 1。
3、public final float getAction() / getActionMasked() :事件类型
getAction,4种常用类型:ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL。
getActionMasked,多2种:ACTION_POINTER_DOWN、ACTION_POINTER_UP。它们代表是多点触控时有其他手指落下或抬起。某些时候,比如滚动,为了防止抬起落下多根手指时出现跳动,我们是需要检测并计算多点触控的,因此推荐直接用getActionMasked。
事件序列,如下:
4、 public final void offsetLocation(float deltaX, float deltaY)
将事件中的坐标值进行位移变换,如滚动。
由于滚动有两种方式,一种是改变子控件的位置,另一种就是利用方法setScrollY(int value) / setScrollX(int value),这两个方法都会影响View类中的mScrollX / mScrollY两个属性,而这两个属性又会影响View在分发事件以及绘制时的行为。
Android的UI界面由Activity、ViewGroup、View 及其派生类组成,假如用户触发了一个点击事件后,事件会先传到Activity
、再传到ViewGroup
、最终再传到 View。
事件在UI界面中的传递顺序如下:
Activity -> ViewGroup -> View
1、public boolean dispatchTouchEvent(MotionEvent event)
事件分发,参数是要分发的事件。
这个方法会将事件分发下去,如果返回true表示它或者它的子view消化了这个事件;返回false表示它和它的子view都不消化。
2、public boolean onInterceptTouchEvent(MotionEvent ev)
事件拦截,参数是要拦截的事件。
这个方法会在dispatchTouchEvent内部调用,判断当前 viewGroup 是否拦截了某个事件。如果拦截了某个事件,那么此方法在同一个事件序列中不会再被调用了。ViewGroup默认不拦截事件。
3、public boolean onTouchEvent(MotionEvent event)
事件处理,参数是要处理的事件。
这个方法也会在 dispatchTouchEvent 内部调用,。判断当前View是否处理事件,如果不处理返回false,则在同一个事件序列中不再接收该事件;View默认都会处理,即返回ture;除非它是不可点击的,即clickable为false。另外,Button的clickable默认为ture,而TextView的clickable默认为false。
三者关系
//伪代码
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume =false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume =child.dispatchTouchEvent(ev);
}
return consume;
}
关系解析:
对于ViewGroup来说,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent会被调用,如果它的onInterceptTouchEvent方法返回true,表示它拦截了当前事件,接着它的onTouchEvent方法就会被调用。否则表示它不拦截当前事件,这时事件就会继续传递给它的子元素,如此反复。
如果View的onTouchEvent方法返回false,表示不消化当前事件,那么它的父容器的onTouchEvent将会被调用,依次类推。如果所有的元素都不处理这个事件,那么这个事件将会被最终传递给Activity处理,即Activity的onTouchEvent方法将会被调用。
补充一点:
平时我们会将View设置的OnClickListener的事件监听,其内部onClick方法会被调用。如果这个onTouch的返回值是false,则当前View的onTouchEvent方法会被调用,即返回ture;否则为ture时,那么onTouchEvent将不会被调用,即返回false。即,给View设置的OnClickListener优先级要比onTouchEvent高。
事件传递的对象顺序是从Activity -> ViewGroup -> View。接下,我们一起探究一下事件分发过程的源码。
1、Activity.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
// DOWN事件,默认true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//等于是调用ViewGroup的superDispatchTouchEvent方法,事件从Activity传递到viewGroup
return mDecor.superDispatchTouchEvent(event);//mDecor:顶层DecorView的实例对象
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
//事件没有子元素处理时,交由Activity处理
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;//至此,事件分发终止
}
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;// 返回true:说明事件在边界外,即消费事件
}
return false;// 返回false:未消费
}
2、ViewGroup.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
...
//disallowIntercept默认是false,是否禁用事件拦截的功能,可通过调用requestDisallowInterceptTouchEvent方法修改
// 调用onInterceptTouchEvent()是否进行拦截,默认false不拦截
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
// 遍历ViewGroup下的子View
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
// 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//调用了view的dispatchTouchEvent分发方法
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
// 如果子view可点击,ViewGroup的dispatchTouchEvent()就要返回true,即直接跳出, 等于子view把ViewGroup的点击事件拦截掉了
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
// 若无子View 拦截事件,或主动调用onInterceptTouchEvent方法,返回true进行拦截
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
...
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;//不拦截(viewGroup默认)
}
3、View.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && //是否设置监听
(mViewFlags & ENABLED_MASK) == ENABLED && //默认enable 为ture
mOnTouchListener.onTouch(this, event)) { //onTouch 为ture
return true; //分发结束
}
return onTouchEvent(event); //自己消化,源码分析
}
// 注册了Touch事件,mOnTouchListener就被赋值,不为null
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
View.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false; // 使上面if判断不成立,执行onTouchEvent()
}
});
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// clickable为ture
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
// 手势抬起事件
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
performClick(); //源码分析
break;
// 手势按下事件
case MotionEvent.ACTION_DOWN:
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPrivateFlags |= PREPRESSED;
mHasPerformedLongPress = false;
postDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());
break;
// 结束事件
case MotionEvent.ACTION_CANCEL:
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
removeTapCallback();
break;
// 滑动事件
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
int slop = mTouchSlop;
if ((x < 0 - slop) || (x >= getWidth() + slop) ||
(y < 0 - slop) || (y >= getHeight() + slop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
// Need to switch from pressed to not pressed
mPrivateFlags &= ~PRESSED;
refreshDrawableState();
}
}
break;
}
return true; // 控件可点击返回true
}
return false; // 控件不可点击返回false
}
public boolean performClick() {
if (mOnClickListener != null) { //设置了点击事件的监听
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this); //绑定当前view的事件
return true; //调用onClick()
}
return false;
}
完。