1. MotionEvent类
MotionEvent类用于记录View的事件。
包括MotionEvent.DOWN手指点击屏幕
MotionEvent.UP手指离开屏幕
MotionEvent.Move 手指在屏幕上滑动
MotionEvent.CANCEL 事件取消(非人可控制)。
2.View事件的传递规则
View事件的传递都是通过三个方法来完成的:
(1)dispatchTouchEvent(MotionEvent e)
用于事件分发。如果事件能传递到这个View时,这个方法一定会被调用,返回的结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响。
(2)onInterceptTouchEvent(MotionEvent event)
用于事件拦截。如果事件被拦截了,这个方法将不会再被调用。
(3)onTouchEvent(MotionEvent event)
用于事件消耗。返回的结果表示事件是否被消耗。
事件传递的过程:
Activity -> Window -> ViewGoup ->view
3.OnTouchListener ,OnTouchEvent 和ClickListener的优先级?
自定义一个简单的View,实现onTouchListener接口。
public class CusView extends View implements View.OnTouchListener {
private static final String TAG = "CusView";
public CusView(Context context) {
this(context, null);
}
public CusView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CusView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnTouchListener(this);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG,"onTouchEvent");
return super.onTouchEvent(event);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG,"onTouch");
return false;
}
}
当OnTouch返回值为false时输出:
E/CusView: onTouch
E/CusView: onTouchEvent
当OnTouch返回值为true时输出:
E/CusView: onTouch
onTouchEvent方法没有被调用。说明当OnTouchListener消耗(返回true)了事件后,OnTouchEvent方法不调用。当OnTouchListener没有消耗(返回false)事件后,OnTouchEvent方法被调用了。
OnTouchListener优先级大于OnTouchEvent。
ClickListener是在OnTouchListener方法中执行的。注意performClick方法
public boolean onTouchEvent(MotionEvent event) {
...........................
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
.............................
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
.............................
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
............................
public boolean performClick() {
..................
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
......................
return result;
}
OnTouchEvent优先级大于ClickListener
所以它们的优先级是 OnTouchListener > OnTouchEvent > ClickListener
4.一些结论
(1)同一个事件序列是指 从手指接触屏幕 到 手指离开屏幕所产生的一系列事件。
其中down事件 和 up事件 各产生一次,多个move事件。
E/CusView: ACTION_DOWN
E/CusView: ACTION_MOVE
E/CusView: ACTION_MOVE
E/CusView: ACTION_MOVE
E/CusView: ACTION_MOVE
E/CusView: ACTION_MOVE
E/CusView: ACTION_UP
(2)某个View一旦决定拦截,那个这一系列的事件都会交给它来处理。并且它的onInterceptTouchEvent不会再被调用。
自定义ViewGroup的代码:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"onInterceptTouchEvent_ACTION_DOWN");
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"onInterceptTouchEvent_ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"onInterceptTouchEvent_ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"onTouchEvent_ACTION_DOWN");
return true;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"onTouchEvent_ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"onTouchEvent_ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
自定义view的代码:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"ACTION_UP");
break;
}
return true;
}
ViewGroup的OnIntercepTouchListener()没有拦截时:
E/CusLinearLayout: onInterceptTouchEvent_ACTION_DOWN
E/CusLinearLayout: onInterceptTouchEvent_ACTION_MOVE
E/CusView: ACTION_DOWN
E/CusLinearLayout: onInterceptTouchEvent_ACTION_MOVE
E/CusView: ACTION_MOVE
E/CusLinearLayout: onInterceptTouchEvent_ACTION_UP
E/CusView: ACTION_UP
ViewGroup 的其他事件还是会被传递到OnIntercepTouchListener()方法中,之后事件会被传递到子View。
ViewGroup.onInterceptTouchEvent()
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"onInterceptTouchEvent_ACTION_DOWN");
return true;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"onInterceptTouchEvent_ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"onInterceptTouchEvent_ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
ViewGroup的OnIntercepTouchListener()拦截时,会输出:
E/CusLinearLayout: onInterceptTouchEvent_ACTION_DOWN
E/CusLinearLayout: onTouchEvent_ACTION_DOWN
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_UP
ViewGroup的OnIntercepTouchListener()拦截事件后,不会再调用OnIntercepTouchListener()了,
其他的事件都会调给ViewGroup的onTouchEvent方法处理了。
(3)某个View开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素处理。
view的onTouchEvent()
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"ACTION_DOWN");
return false;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG,"ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
在ACTION_DOWN事件中不消耗
E/CusLinearLayout: onInterceptTouchEvent_ACTION_DOWN
E/CusView: ACTION_DOWN
E/CusLinearLayout: onTouchEvent_ACTION_DOWN
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onTouchEvent_ACTION_UP
因为View不消耗ACTION_DOWN事件,所以余下的事件都不会找View了,它的父View的onTouchEvent方法会被调用。
(4)如果View不消耗除ACTION_DOWN以外的其他事情,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。
View的onTouchEvent():
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
Log.e(TAG,"onTouchEvent_ACTION_DOWN");
return true;
case MotionEvent.ACTION_MOVE:
Log.e(TAG,"onTouchEvent_ACTION_MOVE");
return false;
case MotionEvent.ACTION_UP:
Log.e(TAG,"onTouchEvent_ACTION_UP");
return false;
}
return super.onTouchEvent(event);
}
消耗ACTION_DOWN事件,不消耗ACTION_MOVE事件,不消耗ACTION_UP事件
Activity的onTouchEvent():
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event!!.action) {
MotionEvent.ACTION_DOWN -> Log.e(TAG, "onTouchEvent_ACTION_DOWN")
MotionEvent.ACTION_MOVE -> Log.e(TAG, "onTouchEvent_ACTION_MOVE")
MotionEvent.ACTION_UP -> Log.e(TAG, "onTouchEvent_ACTION_UP")
}
return super.onTouchEvent(event)
}
输出:
E/CusLinearLayout: onInterceptTouchEvent_ACTION_DOWN
E/CusView: dispatchTouchEvent_ACTION_DOWN
E/CusView: onTouchEvent_ACTION_DOWN
E/CusLinearLayout: onInterceptTouchEvent_ACTION_MOVE
E/CusView: dispatchTouchEvent_ACTION_MOVE
E/CusView: onTouchEvent_ACTION_MOVE
E/EventActivity: onTouchEvent_ACTION_MOVE
E/CusLinearLayout: onInterceptTouchEvent_ACTION_UP
E/CusView: dispatchTouchEvent_ACTION_UP
E/CusView: onTouchEvent_ACTION_UP
E/EventActivity: onTouchEvent_ACTION_UP
在ACTION_DOWN事件,View消耗了。
在ACTION_MOVE和ACTION_UP事件的时候,Activity的onTouchEvent()被调用了。
(5)ViewGroup默认不拦截任何事情
(6)View没有onIntercepTouchEvent方法,一旦有点击事件传递给它。那么它的onTouchEvent方法就会被调用。
(7)View的OnTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)
5(源码解析)
(1)Activity对事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Activity收到事件后,会向window传递,事件回一级一级的传递下去,当返回false时(没有View消耗这个事件),那么就由Activity来消耗(调用onTouchEvent方法)
getWindow().superDispatchTouchEvent(ev)
由于Window是一个抽象类,而它的事件类是PhoneWindow类。
在PhoneWindow中
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
mDecor是什么?
DecorView是一个根View。DecorView继承自FrameLayout,是一个ViewGroup。
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
在DecorView中
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView还是会把事件传递到下一级。
总结:Activity接收到事件后,将事件传递到PhoneWindow中,PhoneWindow会把事件传递给跟View。
(2)顶级View对点击事件的分发过程
ViewGroup判断是否进行拦截 ViewGroup.diapatchTouchEvent()
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
intercepted 变量用于记录是否拦截。
actionMasked == MotionEvent.ACTION_DOWN 判断是否为ACTION_DOWN。
mFirstTouchTarget != null 当ViewGroup 不拦截事件并将事件交由子元素处理时 这个条件成立。
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)
如果第一次MOTION.ACTION_DOWN事件最终不是由子View消耗掉,那么mFirstTouchTarget 将为null,则intercepted = true,此后的事件都是由这个ViewGroup处理。否则intercepted = false,将事件传递给子View。
ViewGroup将事件传递给子View
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
首先遍历ViewGroup所有的子View,然后根据 子元素是否在播动画 和 点击事件的坐标是否在子元素的区域内,来判断子元素是否能够接收点击事件。如果某个子元素满足这两个条件,那个事件就会传递给它来处理。
总结:ViewGroup会根据首次的事件Motion.action_down 是否子View消耗掉了,来判断是否拦截事件,如果拦截了就调用onTouchEvent进行事件处理,否则会根据点击的坐标确定将事件传递到哪个子View。
(3)View对点击事件的处理过程
View.dispatchTouchEvent()
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
onFilterTouchEventForSecurity方法用于判断View是否被遮盖
ListenerInfo 是View的一个静态内部类,用于记录各种操作事件,比如:
OnClickListener
OnLongClickListener
OnTouchListener
判断View是否实现了OnTouchListener接口,如果实现就执行onTouchListener.onTouch()方法。
result 为true,就不执行onTouchEvent方法。
说明OnTouchListener 的优先级比onTouchEvent高
View.onTouchEvent()
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
判断View是否可用,在View处于不可用状态下还是会消耗掉点击事件。
在收到Motion.Action_up时
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
li.mOnClickListener.onClick(this); 触发了点击事件接口。
总结:判断是否已经实现了OnTouchListener接口,是则onTouch方法处理这个事件。否则onTouchEvent方法来处理这个事件。
学习资料:Android开发艺术探索