我们对屏幕的点击,滑动,抬起等一系的动作都会被封装成若干个MotionEvent对象。MotionEvent可以分为以下三种事件类型:
a. ACTION_DOWN:手指刚接触屏幕,按下去的那一瞬间产生该事件
b. ACTION_MOVE:手指在屏幕上移动时候产生该事件
c. ACTION_UP:手指从屏幕上松开的瞬间产生该事件
Android系统时如何将用户的输入事件MotionEvent分发给具体的View进行处理呢?
1. Activity分发输入事件
Activity.java (frameworks\base\core\java\android\app) 321500 2017/8/25
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
}
getWindow()获得的是PhoneWindow,首先将输入事件分发到PhoneWindow处理,如果PhoneWindow调用superDispatchTouchEvent返回true则表示事件已经被处理了则直接返回true;如果返回false则调用Activity的onTouchEvent处理事件
即事件先分发给PhoneWindow处理,如果PhoneWindow没有处理事件,则会由Activity处理事件
PhoneWindow.java (frameworks\base\core\java\com\android\internal\policy) 143971 2017/8/25
public class PhoneWindow extends Window implements MenuBuilder.Callback {
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
}
PhoneWindow会将输入事件分发给DecoreView处理
DecorView.java (frameworks\base\core\java\com\android\internal\policy) 107205 2017/8/25
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
ViewGroup.java (frameworks\base\core\java\android\view) 327264 2017/8/25
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
public boolean dispatchTouchEvent(MotionEvent ev) {
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) { //清除之前的View事件
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev); //如果输入事件的事件类型是ACTION_DOWN,则会重置FLAG_DISALLOW_INTERCEPT标志位并且将mFirstTouchTarget置为null
resetTouchState();
}
// Check for interception.
final boolean intercepted; //是否拦截事件的标志位
if (actionMasked == MotionEvent.ACTION_DOWN //如果事件由子View处理(mFirstTouchTarget指向处理View事件的子View,如果不为空表示此事件由子View处理)
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //判断是否允许当前的ViewGroup拦截事件
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev); //如果没有设置标志位FLAG_DISALLOW_INTERCEPT,表示ViewGroup允许拦截事件,则调用onInterceptTouchEvent判断是否需要拦截
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false; //如果设置了标志位FLAG_DISALLOW_INTERCEPT,表示ViewGroup不允许拦截事件,则将intercepted置为false,表示当前的ViewGroup没有拦截这个View事件
}
} else { //如果事件不由子View处理,则表示事件由当前的ViewGroup处理
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
...
}
}
}
结论:
a. 子View可以通过requestDisallowInterceptTouchEvent方法设置FLAG_DISALLOW_INTERCEPT标志位不让父View拦截除ACTION_DOWN以外的事件
b. intercepted为true时输入事件由父View处理,intercepted为false时输入事件由子View处理
如果输入事件为ACTION_DOWN,则在执行cancelAndClearTouchTargets的时候FLAG_DISALLOW_INTERCEPT标志位会被重置,故FLAG_DISALLOW_INTERCEPT标志位不可干预ACTION_DOWN事件
如果设置FLAG_DISALLOW_INTERCEPT为true表示父View不会拦截事件(输入事件由子View处理),否则FLAG_DISALLOW_INTERCEPT为false时父View会调用onInterceptTouchEvent判断是否需要拦截事件(如果父View会拦截输入事件则输入事件由父View处理,否则由子View处理)
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
//从前到后对子View进行遍历,查找子View处理事件
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView( //child即为当前需要判断是否可以接收此事件的View
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;
}
//判断1. View可见并且没有播放动画。2. 点击事件的坐标落在View的范围内
//如果上述两个条件有一项不满足则continue继续循环下一个View
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//如果有子View处理即newTouchTarget不为null则跳出循环。
newTouchTarget = getTouchTarget(child); //当事件还未分发到子View时,getTouchTarget返回null
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);
//dispatchTransformedTouchEvent第三个参数child这里不为null
//child不为null,因此调用的是child的dispatchTouchEvent方法,child.dispatchTouchEvent返回true表示子View处理了事件;返回false表示子View没有处理事件,进入下一次循环判断下一个子View
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();
//当child处理点击事件,那么在addTouchTarget设置newTouchTarget = mFirstTouchTarget = child = 处理此事件的View
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//子View处理了事件,然后就跳出了for循环
break;
}
}
}
}
}
}
return handled;
}
}
如果intercepted为false表示输入事件由子View处理,继续分发输入事件
分析getTouchTarget
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private TouchTarget getTouchTarget(@NonNull View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
}
当mFirstTouchTarget不为null的时候并且target.child就为我们当前遍历的child的时候,那么返回的newTouchTarget就不为null,则跳出循环。由于此时事件还未确定分发到哪个子View进行处理,所以此时的getTouchTarget返回null
分析dispatchTransformedTouchEvent
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent); //传入的child为需要判断是否可以处理事件的子View,不为null。如果child可以处理事件则dispatchTouchEvent返回true,进而dispatchTransformedTouchEvent返回true
}
// Done.
transformedEvent.recycle();
return handled;
}
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
}
dispatchTransformedTouchEvent传入的child为需要判断是否可以处理事件的子View,不为null。如果child可以处理事件则child.dispatchTouchEvent返回true,进而dispatchTransformedTouchEvent返回true
dispatchTransformedTouchEvent如果返回false表示事件没有分发给当前子View,会继续遍历下一个子View;如果返回true表示事件分发给了当前子View,然后调用addTouchTarget将当前子View赋值给newTouchTarget,最后break循环
newTouchTarget = mFirstTouchTarget = 处理此事件的子View,此时找到了处理此事件的View
结论:
ViewGroup会遍历所有子View去寻找能够处理点击事件的子View,判断子View能否处理此事件的前提:没有播放动画,点击事件坐标落在子View内部
满足以上两个前提之后调用子View的dispatchTouchEvent方法处理事件,如果返回false表示子View没有处理掉此事件,进入下一次循环判断下一个子View是否能够处理此事件;如果返回true表示子View处理了此事件
如果ViewGroup并没有子View或者子View的dispatchTouchEvent返回了false,那么ViewGroup会去处理这个事件,即会调用ViewGroup的dispatchTouchEvent处理事件
View事件传递过程总结:
a. Activity首先会将输入事件交给PhoneWindow处理,如果PhoneWindow处理不了Activity则会调用onTouchEvent处理
b. PhoneWindow会将输入事件分发给DecoreView处理,DecoreView会调用dispatchTouchEvent将事件分配给对应的View
c. 父View调用dispatchTouchEvent时,子View中可以设置标志位FLAG_DISALLOW_INTERCEPT处理滑动冲突。当子View设置了此标志位,则intercepted会被置为false;否则父View调用onInterceptTouchEvent判断当前是否需要拦截事件,拦截时intercepted置为true,否则置为false
d. 当标志位intercepted为true时,输入事件由父View处理,调用ViewGroup的dispatchTouchEvent处理事件
e. 当标志位intercepted为false时,ViewGroup会遍历所有子View去寻找能够处理点击事件的子View,判断子View能否处理此事件的前提:没有播放动画,点击事件坐标落在子View内部
以上两个前提之后调用子View的dispatchTouchEvent方法处理事件,如果返回false表示子View没有处理掉此事件,进入下一次循环判断下一个子View是否能够处理此事件;如果返回true表示子View处理了此事件
2. View处理输入事件
View.java (frameworks\base\core\java\android\view) 1045455 2017/8/25
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean dispatchTouchEvent(MotionEvent event) {
//按下屏幕时actioin = ACTION_DOWN,此时会调用stopNestedScroll清除滚动效果
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) { //如果窗口没有被遮盖
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//需要特别注意这个判断当中的li.mOnTouchListener.onTouch(this, event)条件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
}
View在调用dispatchTouchEvent处理事件时首先会判断View是否设置了OnTouchListener,如果View设置了OnTouchListener则会调用OnTouchListener中的onTouch处理事件;如果没有设置OnTouchListener则会调用View的onTouchEvent处理事件
首先来看如果没有设置OnTouchListener的情况
View.java (frameworks\base\core\java\android\view) 1045455 2017/8/25
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//如果View是设置成不可用的(DISABLED)仍然会消费点击事件,只是不会做出任何响应
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;
}
//只要进入此if则会返回true表示消费掉了这个事件,因此CLICKABLE和LONG_CLICKABLE只要有一个为true就消费这个事件;否则返回false表示没有消费掉此事件
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
//让View显示被点击的状态
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// 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();
}
//调用performClick处理事件
if (!post(mPerformClick)) {
performClick();
}
}
}
}
break;
case MotionEvent.ACTION_DOWN:
//点击事件一般在ACTION_UP中完成,ACTION_DOWN中基本上不做响应
break;
case MotionEvent.ACTION_CANCEL:
...
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
//如果手指移动出了View则直接break
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
}
View的onTouchEvent方法默认都会消费掉事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)
View.java (frameworks\base\core\java\android\view) 1045455 2017/8/25
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean performClick() {
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;
}
return result;
}
}
View在onTouchEvent处理ACTION_UP事件时会调用performClick,在performClick中会判断View有没有设置OnClickListener。
如果View设置了OnClickListener则会调用OnClickListener中的onClick方法,并返回true;如果没有设置OnClickListener则会返回false。无论返回true或者false都处理了此事件,onTouchEvent都返回true。
示例:
public class MotionEventActivity extends BaseActivity {
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_motion_event);
mButton = (Button) findViewById(R.id.button);
mButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
e("MotionEvent: ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
e("MotionEvent: ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
e("MotionEvent: ACTION_UP");
break;
}
return false;
}
});
}
}
这里给View设置了OnTouchListener,因此点击Button的时候会调用onTouch方法
View事件处理过程总结:
a. View在调用dispatchTouchEvent处理事件时首先会判断View是否设置了OnTouchListener
如果View设置了OnTouchListener则会调用OnTouchListener中的onTouch处理事件;如果没有设置OnTouchListener则会调用View的onTouchEvent处理事件
b. View在onTouchEvent处理ACTION_UP事件时会调用performClick,在performClick中会判断View有没有设置OnClickListener
如果View设置了OnClickListener则会调用OnClickListener中的onClick方法,并返回true;如果没有设置OnClickListener则会返回false。无论返回true或者false都处理了此事件,onTouchEvent都返回true
优先级:onTouch > onTouchEvent > onClick
c. 如果View是unclickable则View不会处理事件,否则View是clickable或者longClickable都会处理事件
setClickable失效的情况:将View的clickable设置成false,然后又给View设置了OnClickListener(原因:setOnClickListener会默认将View的clickable设置成true)
好文:
https://www.jianshu.com/p/238d1b753e64