事件分发原理
Android为触摸事件封装了一个类MotionEvent,其实OnTouchEvent的一个参数就是MotionEventMotionEvent中封装了触摸点坐标、点击事件类型等Android中的View可以放在一个ViewGroup中,这个ViewGroup又放在另一ViewGroup中等等,一层层嵌套起来,同一个触摸事件,View和父ViewGroup都想要处理,我们应该传给谁呢,此时就需要事件拦截机制
MotionEvent中处理得事件类型为:
?
注意:ACTION_MOVE方法会被调用多次,ACTION_CANCEL:当事件被上层拦截时触发,需要掌握拦截的条件
View的继承关系如下:
?
?
事件分发和事件处理流程
?
?
事件分发:ViewGroup.java--->dispatchTouchEvent
事件处理:View.java->dispatchTouchEvent
先分析处理流程
Onclick与OnTouch的事件冲突的分析:
? ? ? ?button = findViewById(R.id.click1);
?
? ? ? ?button.setOnClickListener(new View.OnClickListener() {
? ? ? ? ? ?@Override
? ? ? ? ? ?public void onClick(View v) {
? ? ? ? ? ? ? ?Log.i(TAG,"onclick");
? ? ? ? ? }
? ? ? });
?
? ? ? ?button.setOnTouchListener(new View.OnTouchListener() {
? ? ? ? ? ?@Override
? ? ? ? ? ?public boolean onTouch(View v, MotionEvent event) {
? ? ? ? ? ? ? ?Log.i(TAG,"onTouch action" + event.getAction());
? ? ? ? ? ? ? ?return false;
? ? ? ? ? }
? ? ? });
? }
返回的结果为:
2020-07-15 11:27:47.174 19712-19712/com.example.clickevent I/ClickTouch: onTouch action0
2020-07-15 11:27:47.265 19712-19712/com.example.clickevent I/ClickTouch: onTouch action1
2020-07-15 11:27:47.285 19712-19712/com.example.clickevent I/ClickTouch: onclick
表明在UP中执行onClick的方法
当reslut为true时,不执行onClick方法
原理分析:
view的事件处理:执行进入View.java->dispatchTouchEvent
view.dispatchTouchEvent
/**
? ? * Pass the touch screen motion event down to the target view, or this
? ? * view if it is the target.
? ? *
? ? * @param event The motion event to be dispatched.
? ? * @return True if the event was handled by the view, false otherwise.
? ? */
? ?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;
? ? ? ? ? ?//当控件重写了OnTouchListener,而onTouch的返回值会影响返回值,从运行的结果看到当OnTouch返回为true时,onClick不会被执行,因此返回结果会影响
? ? ? ? ? ?if (li != null && li.mOnTouchListener != null
? ? ? ? ? ? ? ? ? ?&& (mViewFlags & ENABLED_MASK) == ENABLED
? ? ? ? ? ? ? ? ? ?&& li.mOnTouchListener.onTouch(this, event)) {
? ? ? ? ? ? ? ?result = true;
? ? ? ? ? }
? ? ? ? ? ?//result的结果如果为flase的会执行到onTouchEvent
? ? ? ? ? ?if (!result && onTouchEvent(event)) {
? ? ? ? ? ? ? ?result = true;
? ? ? ? ? }
? ? ? }
?
? ? ? ?if (!result && mInputEventConsistencyVerifier != null) {
? ? ? ? ? ?mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
? ? ? }
?
? ? ? ?// Clean up after nested scrolls if this is the end of a gesture;
? ? ? ?// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
? ? ? ?// of the gesture.
? ? ? ?if (actionMasked == MotionEvent.ACTION_UP ||
? ? ? ? ? ? ? ?actionMasked == MotionEvent.ACTION_CANCEL ||
? ? ? ? ? ? ? (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
? ? ? ? ? ?stopNestedScroll();
? ? ? }
?
? ? ? ?return result;
? }
如果返回为false会执行到onTouchEvent的流程
public boolean onTouchEvent(MotionEvent event) {
? ? ? ?final float x = event.getX();//获得点击事件的X坐标
? ? ? ?final float y = event.getY();//获得点击事件的Y坐标
? ? ? ?final int viewFlags = mViewFlags;
? ? ? ?final int action = event.getAction();//获得Action的状态,UP,DOWN,CANCL等类型
? ? ? .......
? ? ? ?if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
? ? ? ? ? ?switch (action) {
? ? ? ? ? ? ? ?case MotionEvent.ACTION_UP:
? ? ? ? ? ? ? ? ? ?mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
? ? ? ? ? ? ? ? ? ?if ((viewFlags & TOOLTIP) == TOOLTIP) {
? ? ? ? ? ? ? ? ? ? ? ?handleTooltipUp();
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ........
? ? ? ? ? ? ? ? ? ?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();
? ? ? ? ? ? ? ? ? ? ? }
?
? ? ? ? ? ? ? ? ? ? ? ?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();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? }
?
? ? ? ? ? ? ? ? ? .........
? ? ? ? ? ? ? ? ? ?break;
?
? ? ? ? ? ? ? ?case MotionEvent.ACTION_DOWN:
? ? ? ? ? ? ? ? ? .......
? ? ? ? ? ? ? ? ? ?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);
? ? ? ? ? ?li.mOnClickListener.onClick(this);
? ? ? ? ? ?result = true;
? ? ? } else {
? ? ? ? ? ?result = false;
? ? ? }
?
? ? ? ?sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
?
? ? ? ?notifyEnterOrExitForAutoFillIfNeeded(true);
?
? ? ? ?return result;
? }
?
总结处理的流程为:
?