View的事件传递及分发机制

前言

当我们点击一个View,点击的动作是怎么传递到当前View的,系统会做哪些处理呢?我们在处理滑动冲突的时候,从哪里下手,毫无头绪;我们先来看下事件的传递及分发机制,系统是如何传递事件,分发事件的,给我们处理相关问题提供基础知识

先了解下Activity的层级结构,便于更好的理解事件的传递顺序;
Activity的层级结构

要点总结:

  • 触摸事件有一个down,多个move,一个up组成;

  • 事件的传递是从Activity开始的,Activity -->PhoneWindow–>DectorView–>ViewGroup–>View;主要操作在ViewGroup和View中;

  • ViewGroup类主要调用:dispatchTouchEvent()–>onInterceptTouchEnent()–>dispatchTransformedTouchEvent();ViewGroup不直接调用onTouchEvent()方法;

dispatchTouchEvent()方法,是这个事件分发的核心,解决滑动冲突主要在这个方法中操作;

onInterceptTouchEnent()方法默认情况下,直接返回false,父View需要拦截事件,重写该方法,返回true;如果被拦截了,down不会传递到子View只要某个Action被拦截了,从这个action开始及其后的action都不会再传递到子View中了

ViewGroup可以调用requestDisallowInterceptTouchEvent(boolean disallowIntercept) 默认情况下是允许父View拦截的;如果disallowIntercept=true,父View将无法拦截事件的传递;

如果ViewGroup没有子View,或者子View都不消费事件,或者父View拦截了事件,会将ViewGroup当做普通View处理;看ViewGroup自身是否处理事件;

  • View类主要调用:dispatchTouchEvent()–>onTouchEvent()–>performClick();

如果View重写了OnTouchListener的onTouch()方法,并且返回了true,则onTouchEvent()方法不会再执行了;由此可见onTouch()方法的优先级高于onTouchEvent();

如果View重写了onTouchEvent()方法,不调用super.onTouchEvent(),则onClick()方法将不执行;

执行优先级:OnTouchListener的onTouch() > onTouchEvent() > onClick();

disable的View只要是可点击的,也可以消费touch 事件,但是无法响应;

View是clickable的,就可以消费该事件;onClickListener,在接受up之后,才执行;onClickListener在onTouchEvent方法中被执行;如果View重写了onTouchEvent方法,不调用super.onTouchEvent方法onClick()方法不会执行;

  • 事件的传递顺序:

1)down 从最外层的View往里层的子View传递;如果最里层的View不消费事件,在将事件往父View上抛;

2)把父View当做普通View看待(不在当做ViewGroup来看),如果当前的View也不消费该事件,继续往父View抛;

3)重复步骤2),整个View的传递和分发过程,呈现U型;
View的事件分发及传递

如果down已经找到target view,move和up都通过层层传递直接到target view;不再经过遍历获取target view了;

流程分析

Window类,PhoneWindow是Window的具体实现类;

public abstract boolean superDispatchTouchEvent(MotionEvent event);

Window类的Callback的接口回调;Activity实现了该接口;

/**
 * API from a Window back to its caller.  This allows the client to
 * intercept key dispatching, panels and menus, etc.
 */
public interface Callback {
    /**
     * Called to process touch screen events.  At the very least your
     * implementation must call
     * {@link android.view.Window#superDispatchTouchEvent} to do the
     * standard touch screen processing.
     *
     * @param event The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent event);
}

Activity的dispatchTouchEvent()方法

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();//空方法
    }
    //调用Window的superDispatchTouchEvent()方法,如果事件已经被具体的View处理了;返回true;否则调用Activity的onTouch();
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

PhoneWindow的superDispatchTouchEvent()方法

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;//DecorView是一个FrameLayout;

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

DecorView类的superDispatchTouchEvent();DecorView是FrameLayout的子类,FrameLayout继承ViewGroup;返回ViewGroup的dispatchTouchEvent()的返回值;

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

ViewGroup的dispatchTouchEvent():分发事件;主要看dispatchTransformedTouchEvent()方法

如果当前事件已经被自身,或者子View消费,则返回true;没有被消费,则返回false,向上抛;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
    //如果不进入if方法,dispatchTouchEvent()则返回false;
    if (onFilterTouchEventForSecurity(ev)) {
        //如果是down,重置相关的状态,mFirstTouchTarget=null;
        if (actionMasked == MotionEvent.ACTION_DOWN) {
                // 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);
                resetTouchState();
         }
		//1:检查是否拦截事件的分发
        // Check for interception.
        final boolean intercepted;
        //如果拦截或者有一个View已经开始处理该事件,正常执行事件的分发;
        //down:可以满足条件;move,up:mFirstTouchTarget!=null;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            //子类设置了requestDisallowInterceptTouchEvent(true);不允许父类拦截Touch事件;
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //允许父类拦截
            if (!disallowIntercept) {
                //onInterceptTouchEvent()可以认为默认情况下,返回false;需要拦截重写onInterceptTouchEvent(),返回true;
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        }
		//touch event没有被取消并且没有被拦截,通过不断的递归,找到target view;为mFirstTouchTarget赋值;
        if (!canceled && !intercepted) {
            //down会执行下面的代码,move,up不再往下执行;已经找到了对应的target view;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                 final int childrenCount = mChildrenCount;
                //遍历DecorView的子View;
                 if (newTouchTarget == null && childrenCount != 0) {
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        //child不为null;如果从子View中找到target view,消费了该事件,将为mFirstTouchTarget赋值;
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            //addTouchTarget()为mFirstTouchTarget赋值newTouchTarget;
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            break;
                        }
                    }
                }
            }
        }
        
		//mFirstTouchTarget不为null,找到了target view;否则没有找到target view;
        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            //没有找到target view;意味着1)没有子View;2)有子View,但是子View都不消费事件;3)
            //down就被拦截了,自己消费; 就将ViewGroup当做普通View来对待;
            // No touch targets so treat this as an ordinary view.
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            //找到了target view;
            // Dispatch to touch targets, excluding the new touch target if we already
            // dispatched to it.  Cancel touch targets if necessary.
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    //如果被拦截了(down没有被拦截,move或者up被拦截)
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                            target.child, target.pointerIdBits)) {
                        handled = true;
                    }  
                }
                predecessor = target;
                target = next;
            }
        }
    }
    return handled;
}

ViewGroup的onInterceptTouchEvent():可以认为默认情况下,返回false;需要拦截重写onInterceptTouchEvent(),返回true;

public boolean onInterceptTouchEvent(MotionEvent ev) {
	//满足 鼠标点击,Down等条件返回 true;
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}

ViewGroup的dispatchTransformedTouchEvent();不管是什么情况就做一件事;

  • child为null,意味着没有子View消费,就把当前ViewGroup当做普通View来处理;判断当前View是否消费该事件;
  • child不为null,否则就往子View继续分发事件;

由dispatchTouchEvent()决定返回值;true:子View或者自己消费了该事件;false:没有消费该事件,就往上抛;

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
        View child, int desiredPointerIdBits) {
    final boolean handled;

    // Perform any necessary transformations and dispatch.
    if (child == null) {
         //child== null;调用View的dispatchTouchEvent();当做普通的View;如果当前View的消费了该事件即(当前View的OnTouchListener的onTouch()返回了ture;或者onTouchEvent()返回了true),就返回true;
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        //1)child为ViewGroup,继续调用ViewGroup的dispatchTouchEvent(),继续往下分发;
        //2)child为普通的View;如果子View消费了该touch event即(子View的OnTouchListener的onTouch()返回了ture;或者onTouchEvent()返回了true),则dispatchTouchEvent()返回true;
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

View的dispatchTouchEvent():向target view 分发Touch事件或者当前View就是target view;

事件被target view消费了,即如果OnTouchListener的onTouch()返回了ture;或者onTouchEvent()返回了true,则返回true;否则touch事件没有被消费,则返回false;

可以看出先执行onTouch()方法和onTouchEvent()方法的执行优先级一旦执行了具体的实现类重写了onTouch()方法并且返回了true,则onTouchEvent()方法将不再执行;

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    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;
        }
        
        //onTouch()方法,一旦执行了具体的实现类重写了onTouch()方法并且返回了true,则onTouchEvent()方法将不再执行
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
		//onTouchEvent();
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    return result;
}

View的onTouchEvent()方法:有对应的target view消费了该触摸事件即target View是可点击的(clickable = true),就返回true;否则返回false;

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
	//View是可点击的条件;
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
	//一个disable的View依然可以消费touch事件,只是无法响应它而已;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable;
    }
    //虽然是当前View接受touch事件但是实际上是另一个View消费,setTouchDelegate()设置;
    //一般使用在ViewGroup的子类中,帮组其子View扩大点击事件范围;
    if (mTouchDelegate != null) {
        //如果事件在构造函数中指定的范围内,则将Touch事件转发给委托View。
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                      
                        // 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();
                            }
                            //发送到message到MessageQueue中,如果主线程还在运行中,就会返回true;
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                }
                mIgnoreNextUpEvent = false;
                break;

            case MotionEvent.ACTION_DOWN:
                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }
              	//检查是否长按;最后会调用performLongClick()方法
                break;

            case MotionEvent.ACTION_CANCEL:
                //取消相关Callback,修改状态值;
                break;

            case MotionEvent.ACTION_MOVE:
                //不在按钮内滑动,取消相关Callback
                // Be lenient about moving outside of buttons
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    // Remove any future long press/tap checks
                }
                break;
        }

        return true;
    }

    return false;
}

PerformClick类

 private final class PerformClick implements Runnable {
     @Override
     public void run() {
        performClick();
     }
 }

performClick()调用onClick()方法;

onClick()方法是在onTouchEvent()方法中的action==ACTION_UP的时候才执行的;

onTouch()、onTouchEvent()、onClick()三个方法的执行优先级依次递减;

public boolean performClick() {
    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;
    }
    return result;
}

以上就是事件分发及传递的主要内容,如有问题,请多指教,谢谢!

  • 12
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值