Eche un vistazo a la distribución de eventos táctiles de Android

Cuando nuestros dedos tocan varias vistas en la pantalla, ¿qué sucede exactamente hasta el final de este evento de clic? Analicemos brevemente. La razón de este análisis simple es que la capa hal no está involucrada en absoluto.

tipo de evento

Hay tres tipos de eventos táctiles:

int action = MotionEventCompat.getActionMasked(event);
    switch(action) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

Además de estos tres tipos, también existe ACTION_CANCEL, que indica que el gesto actual está cancelado. Se puede entender que una Vista secundaria recibe el evento ACTION_DOWN que le envía la Vista principal. Cuando la Vista principal intercepta el evento y ya no lo reenvía a la Vista secundaria, le dará a la Vista secundaria un evento ACTION_CANCEL.

Mecanismo de distribución de eventos de la actividad.

Cuando tocamos la pantalla, el evento de clic se pasará primero a la Actividad, que PhoneWindow completa en la Actividad, y luego PhoneWindow pasará el evento a DecorView, la raíz de todo el árbol de control, y luego DecorView entregará el procesamiento de eventos a ViewGroup.

### Activity.dispatchTouchEvent
  public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
  // 由Window进行分发,当getWindow().superDispatchTouchEvent(ev)返回true
  //则 dispatchTouchEvent返回true,
  //方法结束,不会执行onTouchEvent方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev); 1
    }
### PhoneWindow
//将事件传递到ViewGroup进行处理
   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

Cuando el evento no se maneja, volverá al método onTouchEvent de la actividad:

### Activity.onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
  //只有在点击事件在Window边界外才返回true,一般都返回false
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
			
        return false;
    }

### Window.shouldCloseOnTouch
  public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
  //是不是Down事件,event坐标是否在边界内等待
        final boolean isOutside =
                event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
          //返回true:说明事件在边界外,即 消费事件
            return true;
        }
        return false;
    }

Mecanismo de distribución de eventos ViewGroup

Como puede ver en el mecanismo de distribución anterior, el mecanismo de distribución de eventos de ViewGroup comienza desde despachoTouchEvent ().

	### ViewGroup.dispatchTouchEvent
    public boolean dispatchTouchEvent(MotionEvent ev) {
      if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
            }
       ......

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {             
                cancelAndClearTouchTargets(ev);
              //重置所有的触摸状态以这准备新的循环
                resetTouchState();
            }

            // 事件被处理, mFirstTouchTarget为第一个触摸目标
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
              //子View通过requestDisllowInterceptTouchEvet设置FLAG_DISALLOW_INTERCEPT标志,表示父View不再拦截事件
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                  //返回false默认
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // 恢复
                } else {
                    intercepted = false;
                }
            } else {            
                intercepted = true;
            }

           ......
             
            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
          //不取消 不拦截事件
            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) {
                  
                   ......
                     
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        ......
                      
                      //遍历ViiewGroup的子元素,如果子元素能够接收到点击事件,则交给子元素处理
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                           ......
                             
                             //子 View的范围内或者子View是否在播放动画
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // 在触摸范围内
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                          
                          //条件判断的内部调用了该View的dispatchTouchEvent()
                          //实现了点击事件从ViewGroup到子View的传递
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                               .....
                    }
                    ......
                }
            }
            ......
        return handled;
    }
  
  
  //拦截
  //返回false:不拦截(默认)
  // 返回true:拦截,即事件停止往下传递(需手动复写onInterceptTouchEvent()其返回true)
     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;
    }

Al ver que se llamó al método DispatTransformedTouchEvent anteriormente, ¿cómo distribuye ViewGroup el evento a la subvista?

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

        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
          //有子View,则调用子View的dispatchTouchEvent(event)方法,如果没有子View,
        // 则调用super.dispatchTouchEvent(event)方法。
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
       ......
        // Done.
        transformedEvent.recycle();
        return handled;
    }  

Se puede saber que la distribución de eventos siempre se pasa primero al ViewGroup, luego atraviesa la subvista para encontrar la subvista en la que se hizo clic y luego la pasa a la subvista.

TouchTarget: es una lista vinculada unidireccional que registra qué puntero (dedo) presionó cada subvista.

Las tres relaciones de métodos más importantes se pueden expresar en pseudocódigo:

public boolean dispatchTouchEvent(MotionEvent ev){
  
  boolean consume = false;
  
  if(onInterceptTouchEvent(ev)){
    	consume = onTouchEvent(ev);
  } else {
    	consume = childDispatchTouchEvent(ev);
  }
  return consume;
}

Mecanismo de distribución de eventos de la vista.

El evento se pasa al método DispatchTouchEvent de View.

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

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //onTouchListener方法的优先级要高于onTouchEvent方法
            ListenerInfo li = mListenerInfo;
          //有三个条件符合才会返回true
           //mOnTouchListener != null      mOnTouchListener变量在View.setOnTouchListener()里赋值
          //(mViewFlags & ENABLED_MASK) == ENABLED   判断当前点击的控件是否是enabled
          // mOnTouchListener.onTouch(this, event)   回调控件注册Touch事件时的onTouch()
            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;
    }

Si no se cumple alguna de las tres condiciones, el evento se pasará al onTouchEvent () de la Vista. El método onTouchEvent se analiza en detalle a continuación.

### 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) {
          //抬起,仍然设置setPressed(false),表示点击过了
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
 
            return clickable;
        }
   
   	//触摸代理  增加点击区域
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

   		//TOOLTIP   tooptipText是个解释工具
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                      //松手消失ToolTip
                        handleTooltipUp();
                    }
                    if (!clickable) {
                      //取消监听器
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                //按下 或者是预按下状态
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                       
                        boolean focusTaken = false;
                      //可以获取焦点
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                      //如果是预按下状态
                        if (prepressed) {
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 取消长按回调
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                              
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                  //触发点击
                                    performClickInternal();
                                }
                            }
                        }

                       ......

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

                    // 在滑动控件中
                    boolean isInScrollingContainer = isInScrollingContainer();

                //对于滚动容器内的视图,如果这是滚动,请将按下的反馈延迟一小段时间。
                //滑动控件中不知道你是操作父View还是子View
                //不是滑动的 可以重写shouleDelayChildPressedState为false,不用延迟子View按下时间
                    if (isInScrollingContainer) {
                      //设置状态设为 预按下
                        mPrivateFlags |= PFLAG_PREPRESSED;

                        if (mPendingCheckForTap == null) {
                          //设置按下等待器
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        //这是核心   如果不在滑动控件中  又是按下
                        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);
                    }

                    // 如果手出界了
                    if (!pointInView(x, y, mTouchSlop)) {
                        // 全部取消掉
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

Finalmente se llama al método performClick():

### View.performClick 
public boolean performClick() {
     
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
   // 如果View设置了点击事件,onClick方法就会执行。
   //onTouch()的执行 先于 onClick()
        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;
    }

Resumen de entrega del evento

  • La misma secuencia de eventos se refiere al tiempo desde que el dedo toca la pantalla hasta que el dedo sale. Toda la secuencia de eventos comienza con un evento hacia abajo, contiene un número variable de eventos de movimiento en el medio y finalmente termina con un evento hacia arriba.
  • En circunstancias normales, una secuencia de eventos solo puede ser interceptada y consumida por una Vista.
  • Una vez que se determina que una Vista debe interceptar, esta secuencia de eventos solo puede ser procesada por ella y no se volverá a llamar a su onInterceptToucheEvent.
  • Una vez que una Vista comienza a procesar eventos, si no consume el evento ACTION_DOWN, otros eventos en la misma secuencia de eventos no se le entregarán para su procesamiento y el evento se entregará nuevamente a su elemento principal para su procesamiento, es decir. es decir, se llamará al onTouchEvent del elemento padre.
  • Si la Vista no consume otros eventos además de ACTION_DOWN, entonces el evento de clic desaparecerá. En este momento, no se llamará al onTouchEvent del elemento principal y la Vista actual puede continuar recibiendo eventos posteriores. Eventualmente, estos clics que desaparecen los eventos se pasarán al procesamiento de actividades.
  • ViewGroup no intercepta ningún evento de forma predeterminada.
  • onTouchEvent de View consumirá eventos (devolverá verdadero) de forma predeterminada, a menos que no se pueda hacer clic (se puede hacer clic y se puede hacer clic durante mucho tiempo son falsos al mismo tiempo). El atributo longClickable de View tiene por defecto falso y el atributo en el que se puede hacer clic depende de la situación. Por ejemplo, el atributo en el que se puede hacer clic de Button tiene por defecto verdadero y el atributo en el que se puede hacer clic de TextView tiene por defecto false.
  • El atributo enable de View no afecta el valor de retorno predeterminado de onTouchEvent. Incluso si una Vista está en estado deshabilitado, siempre que uno de sus elementos en los que se puede hacer clic o en el que se puede hacer clic durante mucho tiempo sea verdadero, su onTouchEvent devolverá verdadero.
  • El proceso de entrega de eventos es de afuera hacia adentro, es decir, el evento siempre se entrega primero al elemento principal y luego el elemento principal lo distribuye a la Vista secundaria. El método requestDisallowInterceptTouchEvent se puede utilizar para intervenir en el proceso de distribución de eventos de el elemento padre en el elemento hijo, excepto el evento ACTION_DOWN.

Diagrama completo:

Supongo que te gusta

Origin blog.csdn.net/Jason_Lee155/article/details/132833955
Recomendado
Clasificación