在从事android开发过程中,我们经常会碰到View之间的滑动冲突,如ScrollView与Listview、RecyclerView之间的嵌套使用。在很好的解决此类问题之前,我们应深入的了解Android事件响应机制。
预备知识
MotionEvent对象的四种状态
MotionEvent.ACTION_DOWN:手指按下屏幕的瞬间。
MotionEvent.ACTION_MOVE:手指在屏幕上移动
MotionEvent.ACTION_UP:手指离开屏幕瞬间
MotionEvent.ACTION_CANCEL:取消手势
通常一次Touch事件是由ACTION_DOWN 开始,经过多次ACTION_MOVE 到ACTION_UP 结束此次点击事件。
Android事件响应机制流程
由外到内”分发、“由内到外”处理的形式实现。Android 事件分发总是遵循 Activity => ViewGroup => View 的传递顺序,从上往下调用dispatchTouchEvent()分发,Activity A ->> ViewGroup B ->> View C,从下往上调用onTouchEvent()处理
View C ->> ViewGroup B ->> Activity A。
事件分发核心函数
1,dispatchTouchEvent()
2,onTouchEvent()
3,onInterceptTouchEvent()
下面我将结合demo以及源码来详细介绍事件分发及处理过程:
在activity中:

protected void initView() {
        super.initView();
        CustominzeLinearLayout linearLayout = findViewById(R.id.custom_linearlayout);
        CustomizeView button = findViewById(R.id.custom_button);
        linearLayout.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(tag1,"customizLinearLayout Onclick");

            }
        });

        linearLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d(tag1,"customizLinearLayout Ontouch");
                return false;
//                return true;
            }
        });

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(tag1,"customizeView onclick");
            }
        });

        button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.d(tag1,"customizeView onTouch");
                return false;
//                return true;
            }
        });

        button.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Log.d(tag1,"customizeView onLongclick");
                return false;
//                return true;
            }
        });

        button.setOnScrollChangeListener(new View.OnScrollChangeListener() {
            @Override
            public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                Log.d(tag1,"customizeView OnScroll");
            }
        });
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(tag1,"Activity dispatchTouchEvent");
        return super.dispatchTouchEvent(ev);
//        return false;
//        return true;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        Log.d(tag1,"Activity onTouchEvent");
        return super.onTouchEvent(ev);
        //return false;
//        return true;
    }

在自定的LinearLayout中(LinearLayout 是ViewGroup的一个子类):
public class CustominzeLinearLayout extends LinearLayout {
public CustominzeLinearLayout(Context context) {
super(context);
}

public CustominzeLinearLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustominzeLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

// public CustominzeLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// super(context, attrs, defStyleAttr, defStyleRes);
// }

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d(tag1,"ViewGroup dispatchTouchEvent");
    return super.dispatchTouchEvent(ev);
    //        return false;

// return true;
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    Log.d(tag1,"ViewGroup onInterceptTouchEvent");
    return super.onInterceptTouchEvent(ev);
    //        return false;

// return true;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    Log.d(tag1,"ViewGroup onTouchEvent");
    return super.onTouchEvent(ev);
    //        return false;

// return true;
}
}
在自定义的Button中(View的子类):
public class CustomizeView extends Button {

public CustomizeView(Context context) {
    super(context);
}

public CustomizeView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

public CustomizeView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
}

// public CustomizeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// super(context, attrs, defStyleAttr, defStyleRes);
// }

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    Log.d(tag1,"View dispatchTouchEvent");
    return super.dispatchTouchEvent(ev);
    //        return false;

// return true;
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    Log.d(tag1,"View onTouchEvent");
    return super.onTouchEvent(ev);
    //        return false;

// return true;

}

}

我们先默认正常情况下 点击button按Onclick响应事件,看下打印的log信息如下:

android界面事件响应 android事件响应过程_ide


正如前文所述从上往下调用dispatchTouchEvent()分发,Activity A ->> ViewGroup B ->> View C,由于子view直接处理了onclick点击,所以在onclick方法中处理完后没在向上往调用调用onTouchEvent()处理。

下面结合核心函数及源码介绍事件分发的过程:

public boolean dispatchTouchEvent(MotionEvent ev)

当监听到有触发事件时,首先由Activity进行捕获,然后事件就进入事件分发的流程。Activity本身没有事件拦截,从而将事件传递给最外层的View的dispatchTouchEvent(MotionEvent ev)方法,该方法将对事件进行分发。

return true : View消费所有事件。停止向下分发,

Actiivity dispatchTouchEvent 返回true:

android界面事件响应 android事件响应过程_android界面事件响应_02


ViewGroup :

android界面事件响应 android事件响应过程_事件分发_03


View:

android界面事件响应 android事件响应过程_事件分发_04


return false :停止分发,交由上层控件的onTouchEvent方法进行消费,如果本层控件是Activity,那么事件将被系统消费、处理。

将自定义的View的dispatchTouchEvent 返回false,点击查看打印日志如下:

android界面事件响应 android事件响应过程_子类_05


可见交有View的上层空间CustomizLinearLayout 处理事件。

super.dispatchTouchEvent(ev): 默认情况:我们查看一下源码

public boolean dispatchTouchEvent(MotionEvent ev) {
       // 一般事件列开始都是DOWN事件 = 按下事件,故此处基本是true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //实现屏保功能,该方法为空方法,当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
            onUserInteraction();
        }
        /**
        a. getWindow() = 获取Window类的对象
  *     b. Window类是抽象类,其唯一实现类 = PhoneWindow类;即此处的Window类对象 = PhoneWindow类对象
  *     c. Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现 */
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        当一个点击事件未被Activity下任何一个View接收  处理时
        return onTouchEvent(ev);
    }
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
    // mDecor = 顶层View(DecorView)的实例对象,mDecor继承自FrameLayout,而FrameLayout 继承ViewGroup,也即mDecor 间接是ViewGroup 的一个子类
        return mDecor.superDispatchTouchEvent(event);
    }  
 public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
   实现从activity的dispatchTouchEvent 分发到 ViewGroup 的dispatchTouchEvent 
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }
        .........
   View的dispatchTouchEvent 源码部分
public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        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;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    //调用onTouch 方法,后续对比onclick方法
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        。。。。。。

事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
return true: 对事件拦截,交由本层的onTouchEvent进行处理。
return false: 不拦截,分发到子View,由子View的dispatchTouchEvent方法处理。
super.onInterceptTouchEvent(ev):在ViewGroup的dispatchTouchEvent中去获取是否拦截,默认不拦截
截取ViewGroup中的一段源码如下:

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;
            }

从源码可见,只有在ACTION_DOWN 操作是去判断onInterceptTouchEvent 是否拦截。

事件响应:public boolean onTouchEvent(MotionEvent ev)
return true: 表示onTouchEvent处理完事件后消费了此次事件。
return false: 不响应事件,不断的传递给上层的onTouchEvent方法处理,直到某个View的onTouchEvent返回true,则认为该事件被消费。如果到最顶层View还是返回false,那么该事件不消费,将交由Activity的onTouchEvent进行处理。
return: super.onTouchEvent,不响应事件,结果与return返回false一样。
看一下View的onTouchEvent的源码分析

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;

        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 (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    若该控件可点击,则进入switch判断中
                    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();
                        }

                        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();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }

                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }

                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // 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;
    }

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);
            //调用onclick方法
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

从View的dispatchTouchEvent 与onTouchEvent 源码中可以看出onTouch方法先比onclick方法调用,如果onTouch 方法返回true,那么onclick方法将不会得到调用,将demo中的onTouch 返回true,打印日志如下:只调用了onTouch方法。

android界面事件响应 android事件响应过程_事件分发_06


总结

1,Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。

2,onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

3,onTouch,onTouchEvent,onClick 执行顺序及关系

onTouch,onTouchEvent 都是在View的dispatchTouchEvent中调用。但onTouch()优先于onTouchEvent执行;若手动复写在onTouch()中返回true(即 将事件消费掉),将不会再执行onTouchEvent()

onTouch() 执行总优先于 onClick(),所以执行先后顺序是onTouch, onTouchEvent,onClick。

整个事件分发过程用流程图如下:

android界面事件响应 android事件响应过程_ide_07


在各个角色中3个核心方法的作用如下所示:

android界面事件响应 android事件响应过程_事件分发_08