View事件分发机制(源码 API27)

1、什么是事件分发机制

当用户触摸屏幕时,会产生一个touch事件,这个touch事件(motionEvent)传递到某个具体的view处理的整个过程

用户触摸屏幕会产生一个事件流(ACTION_DOWN -> ACTION_MOVE -> ACTION_UP)

一般来说,view负责处理action_down事件后,会由这个view来处理接下来的事件(注意一般来说)

2、核心方法

  • activity: dispatchTouchEvent / onTouchEvent

  • viewGroup: dispatchTouchEvent / onInterceptTouchEvent / onTouchEvent

  • view: dispatchTouchEvent / onTouchEvent

3、Activity的处理

onUserInteraction()是个空方法,只要接收到down事件,就会执行(用户可重写)。

  • 如果getWindow().superDispatchTouchEvent()返回true,则意味着事件被viewGroup或者子view消费掉,则activity.dispatchTouchEvent()也返回true,事件分发结束;

  • 若子view或者viewGroup没有对事件进行处理,则getWindow().superDispatchTouchEvent()返回false , 则执行activity.onTouchEvent()

  • 无论activity.onTouchEvent() 返回true 还是false, 事件分发都结束。

Activity#dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction(); //空方法,用户可自行实现
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true; //如果被viewGroup/view消费,则事件分发结束
    }
    return onTouchEvent(ev); //没有任何view消费,事件也分发结束
}

//如果没有任何view消费这个事件,则会执行activity#onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) { //点击区域在边界外,则返回true
        finish();
        return true;
    }
    return false;
}

//点击区域在边界外,则返回true,否则返回false
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        final boolean isOutside =
                event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
            return true;
        }
        return false;
    }

getWindow()#superDispatchTouchEvent()

以前我们说过getWindow返回的是一个PhoneWindow对象,PhoneWindow对象是在activity.attach()里实例化的。

mDecor是通过installDecor()实例化的,是window的根布局。mDecor继承自FrameLayout,FrameLayout继承自ViewGroup。即motionEvent对象由Activity传递到了ViewGroup。(activity - phoneWindow - mDecor - ViewGroup )

PhoneWindow#superDispatchTouchEvent()

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

DecorView#superDispatchTouchEvent(event)

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

4、ViewGroup的处理

ViewGroup每次事件分发时,会先判断disallowIntercept(默认为false) 和 onInterceptTouchEvent (返回false, 不拦截 ) , 决定ViewGroup是否拦截事件,如果不拦截事件,则遍历ViewGroup的子View,调用 view.dispatchTouchEvent( ), 即完成了由ViewGroup传递到View事件。

若点击的是空白处(没有任何View处理此事件),或者手动重写onIntercepTouchEvent()返回为true (拦截),则调用super.DispatchTouchEvent( ),即View.DispatchTouchEvent(ViewGroup继承自View)。由ViewGroup自己处理onTouch事件,则由 ViewGroup 的 onTouch() -> onTouchEvent() -> performClick() -> onClick()

ViewGroup本身是没有onTouchEvent()方法的,所以实际上是调用了父类View.onTouchEvent()

ViewGroup#dispatchTouchEvent()

disallowIntercept 是否禁用事件拦截的功能

  • 默认是false,不禁用拦截功能,由onInterceptTouchEvent()来决定是否拦截
  • 为true,禁用拦截功能,即不拦截事件

onInterceptTouchEvent()返回true则拦截,false则不拦截

如果设置这两个字段

  • disallowIntercept可在子View中通过getParent().requestDisallowInterceptTouchEvent()设置
  • onInterceptTouchEvent()用户可重写ViewGroup的onInterceptTouchEvent()
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
 
        boolean handled = false; //是否消费事件
        if (onFilterTouchEventForSecurity(ev)) {

         // 如果是个down事件,则把之前的事件都抛掉不管。
            if (actionMasked == MotionEvent.ACTION_DOWN)

            //判断是否需要拦截
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
                if (!disallowIntercept) { 
  //只有diallowIntercept为false,且onInterceptTouchEvent为true时才拦截
                    intercepted = onInterceptTouchEvent(ev); 
                } else {
   //如果diallowIntercept为true,则不拦截
                    intercepted = false; 
                }
            } else {
                // 如果这不是个down事件,也不是上一个的down事件,则意味着拦截(异常情况)
                intercepted = true;
            }

            //判断事件是否已经取消
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            if (!canceled && !intercepted) { // 如果不取消也不拦截,则由子View进行处理

            //遍历并分发给子View进行处理
             if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                   // Child wants to receive touch within its bounds.
                   break;
              }
                      
            // handled
            }
        return handled;
    }

ViewGroup#onInterceptTouchEvent()

在 dispatchTouchEvent()内部,判断是否拦截事件MotionEvent()

public boolean onInterceptTouchEvent(MotionEvent ev) {
    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,则执行child.disptachTouchEvent(event)

没有child,则执行super.dispatchTouchEvent (ViewGroup继承自View,super也就是View)

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
        return handled;
    }

5、View的事件处理

onTouch优先于onTouchEvent,更优先于onClick()

View#dispatchTouchEvent()

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;

        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true; //返回true 则事件已被View消费掉,不再往下传递
        }

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

//mTouchListener.onTouch(this, event) 返回true 则事件已被View消费掉,不再往下传递

button.setOnTouchListener(object : View.OnTouchListener {
    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
        return true
    }
})

View#onTouchEvent()

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
   
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                break;
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_CANCEL: //事件取消(非人为)
                break;
            case MotionEvent.ACTION_MOVE:
                break;
        }
        return true; //若该控件可点击,则一定返回true
    }
    return false; //若该控件不可点击,则一定返回false
}

View#performClick()

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        li.mOnClickListener.onClick(this); //onTouch() 回调优先于 onClick() 回调
        result = true;
    } else {
        result = false;
    }
    return result;
}

总结

责任链模式,由Activiy -> ViewGroup -> View

参考

Android事件分发机制 详解攻略,您值得拥有

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值