Pensé que era competente en el mecanismo de distribución de eventos de Android, pero el entrevistador me confundió.

El código fuente que aparece en el artículo se basa en 8.0

Prefacio

El mecanismo de distribución de eventos no es solo un punto de conocimiento central, sino también un punto difícil, y también es la base teórica del método deslizante de resolución de conflictos de un problema importante de View. Por lo tanto, es muy importante dominar el mecanismo de distribución de eventos de Vista.

1. Comprensión básica

1. El objeto de la distribución del evento

El objeto de la distribución del evento es un evento de clic (evento de toque) , y cuando el usuario toca la pantalla, se generará un evento de clic.

Hay cuatro tipos de eventos , como sigue:

Tipos de Descripción
MotionEvent.ACTION_DOWN El dedo acaba de tocar la pantalla, generalmente el comienzo del evento.
MotionEvent.ACTION_MOVE Cuando el dedo se mueve en la pantalla, se generarán múltiples eventos de movimiento durante el movimiento.
MotionEvent.ACTION_UP En el momento en que su dedo se suelta de la pantalla
MotionEvent.ACTION_CANCEL Terminar el evento, por razones no humanas.

La misma secuencia de eventos : se refiere a una serie de eventos desde el momento en que el dedo toca la pantalla hasta el momento en que el dedo sale de la pantalla. Esta secuencia generalmente comienza con un evento hacia abajo, contiene múltiples eventos de movimiento en el medio y termina con un evento final.

2. La naturaleza de la distribución de eventos

La esencia de la distribución de eventos es en realidad todo el proceso de pasar el evento de clic (MotionEvent) a un procesamiento de vista específico

3. La secuencia de distribución de eventos

La secuencia de entrega del evento: Actividad-> Ventana-> DecorView-> ViewGroup-> Ver. Después de que ocurre un evento de clic, siempre se pasa primero a la Actividad actual, luego se pasa a DecorView a través de la Ventana, luego se pasa al ViewGroup y finalmente se pasa a la Vista.

El orden de distribución de eventos en "Exploración de arte en desarrollo" es: Actividad-> Ventana-> Vista, y el orden en algunos blogs es: Actividad-> Grupo de vista-> Vista, pero de hecho los dos son iguales (lo siguiente será del análisis del código fuente). Window es una clase abstracta, y su única clase de implementación es PhoneWindow. PhoneWindow pasa eventos directamente a DecorView, mientras que DecorView hereda FrameLayout y FrameLayout es una subclase de ViewGroup, por lo que finalmente puede pensar que la implementación de la distribución de eventos de Windows es en realidad ViewGroup. Logrado. Por lo que también se puede considerar que el orden de entrega del evento es: Actividad-> ViewGroup-> View.

En segundo lugar, el método central

El mecanismo de distribución de eventos de View está controlado principalmente por los tres pasos de distribución de eventos -> intercepción de eventos -> procesamiento de eventos Es muy coincidente que estos tres pasos correspondan a los tres métodos principales.

1. Distribución de eventos: dispatchTouchEvent

Se utiliza para distribuir eventos. Si el evento se puede pasar a la Vista actual, se llamará a este método. El resultado devuelto se ve afectado por el onTouchEvent de la vista actual y el dispatchTouchEvent de nivel inferior, lo que indica si se consume el evento actual.

原型: public boolean dispatchTouchEvent (MotionEvent ev)

regreso :

  • tura: la vista actual consume todos los eventos
  • falso: detiene la distribución y entrega al método onTouchEvent del control superior de consumo. Si el control de esta capa es Actividad, el evento será consumido y procesado por el sistema

2. Intercepción de eventos: onInterceptTouchEvent

Tenga en cuenta que en Activity, ViewGroup y View, solo ViewGroup tiene este método. Por lo tanto, una vez que se pasa un evento de clic a la Vista, se llamará al método onTouchEvent de la Vista

Se utiliza internamente en dispatchTouch Event para determinar si se debe interceptar el evento. Si la Vista actual intercepta un evento, la Vista actual también procesa otros métodos de la secuencia de eventos, por lo que no se volverá a llamar a este método, porque ya no es necesario preguntar si quiere interceptar el evento.

原型: público booleano onInterceptTouchEvent (MotionEvent ev)

regreso :

  • Ture: intercepta el evento y entrégalo al onTouchEvent de esta capa para su procesamiento.
  • false: no interceptar, distribuir a la vista secundaria y gestionarlo mediante dispatchTouchEvent de la vista secundaria
  • super.onInterceptTouchEvent (ev): Sin interceptación por defecto

3. Procesamiento de eventos: onTouchEvent

Se llama en dispatchTouchEvent para controlar el evento de clic. El resultado devuelto indica si el evento actual se consume. De lo contrario, la vista actual ya no puede recibir los eventos restantes en la misma secuencia de eventos y el evento se volverá a enviar a su padre Se llamará al procesamiento de elementos, es decir, onTouchEvent del elemento padre

原型: público booleano onTouchEvent (MotionEvent ev)

regreso :

  • verdadero: indica que el evento actual se consume después de que se procesa onTouchEvent
  • falso: no responda al evento y lo pase continuamente al método onTouchEvent de la capa superior para su procesamiento. Hasta que el evento onTouchEvent de una vista devuelva verdadero, el evento se considera consumido. Si la vista superior aún devuelve falso, el evento no se consume y se entregará al procesamiento onTouchEvent de Activity.
  • super.onTouchEvent (ev): el evento actual se consume de forma predeterminada, lo que es consistente con devolver verdadero.

Tres, mecanismo de distribución de eventos

Al analizar el mecanismo de distribución de eventos, debemos analizar la secuencia de distribución de eventos paso a paso. De lo anterior sabemos que el orden de distribución de eventos es: Actividad-> Ventana-> DecorView-> ViewGroup-> View. Dado que Window y DecorView se pueden considerar como el proceso de Activity-> ViewGroup, aquí analizaremos el mecanismo de distribución de eventos a través del código fuente de tres partes:

  • Mecanismo de distribución de actividades para eventos de clic
  • Mecanismo de distribución de ViewGroup para eventos de clic
  • Ver el mecanismo de distribución para eventos de clic

1. Mecanismo de distribución de eventos de actividad

Sabemos que cuando ocurre un evento de clic, el evento siempre se envía primero a la Actividad actual y el evento dispatchTouchEvent de la Actividad envía el evento. La actividad pasará el evento al objeto Window para su distribución, y el objeto Window se pasará al DecorView. Se realizará el siguiente análisis del código fuente para verificar este proceso:

Código fuente: Activity # dispatchTouchEvent

    public boolean dispatchTouchEvent(MotionEvent ev) {
    	//点击事件的开始一般为按下事件,所以总是true
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();

        }
		//如果Activity所属Window的dispatchTouchEvent返回了ture
		//则Activity.dispatchTouchEvent返回ture,点击事件停止往下传递
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
		//如果Window的dispatchTouchEvent返回了false,则点击事件传递给Activity.onTouchEvent
        return onTouchEvent(ev);
    }

El código anterior es el código fuente de dispatchTouchEvent in Activity. A partir del código fuente, podemos saber que cuando ocurre un evento de clic, onUserInteraction () se ejecutará primero; ¿cuál es este método? No tienes prisa, seguiremos la pista.

	//空方法,当该Activity在栈顶时,触屏点击home,back,menu会触发此方法
    public void onUserInteraction() {
    }

Se puede ver en el código fuente que este método es un método vacío en la Actividad. Cuando la Actividad está en la parte superior de la pila, al hacer clic en Inicio, Atrás y en el menú de la pantalla táctil se activará este método, por lo que este método puede realizar la función de protector de pantalla. Regresemos al método dispatchTouchEvent en la Actividad, y luego llamemos al método getWindow (). SuperDispatchTouchEvent (ev) para entregar el evento a la Ventana adjunta a la Actividad para su distribución. Si el evento final se consume, devuelve verdadero. Si el evento no es un Procesamiento, la Actividad llama a su propio método onTouchEvent () para manejar el evento.

getWindow es un objeto Window. En el código fuente de Window, podemos encontrar que Window es en realidad una clase abstracta. Obviamente, su método es naturalmente un método abstracto, por lo que debemos averiguar su clase de implementación específica.

Código fuente: Window # superDispatchTouchEvent

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
    ...
     //抽象方法
    public abstract boolean superDispatchTouchEvent(MotionEvent event);
    ...
}

A partir del código fuente, podemos encontrar que hay explicaciones en los comentarios frente a la clase Window. ¡En este momento, debemos probar nuestra habilidad en inglés! De hecho, es bastante fácil de entender en general, de hecho, solo debemos prestar atención a los comentarios en la última parte.

La única implementación existente de esta clase abstracta es android.view.PhoneWindow, que debe instanciar cuando necesite una ventana.

Desde aquí podemos saber que su única clase de implementación es PhoneWindow, no hay muchas tonterías, echemos un vistazo a la implementación del método superDispatchTouchEvent en PhoneWindow.

Código fuente: PhoneWindow # superDispatchTouchEvent

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

Desde el código fuente, podemos encontrar que PhoneWindow pasa el evento directamente a DecorView, y ¿dónde está este DecorView? Como sigue

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {    
	......
	public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    ......
}

@RemoteView
public class FrameLayout extends ViewGroup {
    ......
}

De hecho, DecorView es el contenedor principal donde establecemos el diseño a través de setContentView. Podemos obtener el diseño establecido en setContentView a través de getWindow (). GetDecorView (). FindViewById (android.R.id.content) .getChildAt (0). Se puede encontrar en el código fuente de DecorView que DecorView hereda FrameLayout y FrameLayout hereda ViewGroup, por lo que la clase principal indirecta de DecorView es ViewGroup. En DecorView, el método superDispatchTouchEvent usa super para llamar al dispatchTouchEvent de la clase principal, por lo que es equivalente a llamar al método dispatchTouchEvent del ViewGroup. (del código fuente podemos saber que FrameLayout no tiene un método dispatchTouchEvent), por lo que DecorView pasa el evento al ViewGroup para su procesamiento. También se puede decir que el evento se ha enviado a la Vista de nivel superior, que es la Vista establecida por setContentView en la Actividad (la Vista de nivel superior suele ser un Grupo de Vistas).

El diagrama de flujo es el siguiente:

En este punto, también hemos verificado que la secuencia de distribución de eventos mencionada anteriormente es: Actividad-> Ventana-> DecorView-> ViewGroup. Entonces, ¿cómo pasa ViewGroup eventos a View? ¡Continuemos con el análisis!

2. Mecanismo de distribución de eventos de ViewGroup

A partir del mecanismo de distribución del evento Activity anterior, podemos saber que el mecanismo de distribución del evento ViewGroup comienza desde dispatchTouchEvent (), por lo que partimos del código fuente de esta parte del análisis, debido a la gran cantidad de código en este método, el siguiente código se publicará según sea necesario:

Código fuente: ViewGroup # dispatchTouchEvent

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        	........
            // Check for interception.
            final boolean intercepted;  //是否拦截
            /*
            * 当事件由ViewGroup的子元素处理时,mFirstTouchTarget会被赋值并指向子元素
            */
            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;
            }
    }

A partir del código fuente anterior, podemos saber que ViewGroup juzga si interceptar solo cuando ACTION_DOWN o mFirstTouchTarget! = Null. mFirstTouchTarget conoce su función por el código subyacente. Su función es: cuando el evento es manejado por una Vista secundaria de ViewGroup, mFirstTouchTarget apuntará a esta Vista secundaria. Entonces, cuando el evento es interceptado por este ViewGroup, la subclase no procesará este evento, por lo que mFirstTouchTarget = null, luego, cuando los eventos ACTION_MOVE y ACTION_UP lleguen en este momento, debido a que la condición de juicio es falsa, el evento onInterceptTouchEvent del ViewGroup no se volverá a llamar Luego, interceptado se da como verdadero, por lo que otros eventos en la misma secuencia de eventos se entregarán al ViewGroup para su procesamiento de forma predeterminada. También podemos encontrar una oración de este tipo en el código fuente anterior:

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

FLAG_DISALLOW_INTERCEPT es un bit de bandera, este bit de bandera se establece mediante el método requestDisallowInterceptTouchEvent (boolean disallowIntercept), que generalmente se usa en la vista secundaria. Si se establece FLAG_DISALLOW_INTERCEPT, ViewGroup no podrá interceptar otros eventos de clic excepto ACTION_DOWN. Esto se debe a que ViewGroup restablecerá el bit de marca de FLAG_DISALLOW_INTERCEPT si es un evento ACTION_DOWN en el evento de distribución. Echemos un vistazo al código fuente.

            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();  //对FLAG_DISALLOW_INTERCWPT进行重置

            }

El código fuente anterior está antes de juzgar si interceptar, por lo que el bit de la bandera se puede restablecer. Desde aquí también podemos encontrar que cuando el evento de clic es ACTION_DOWN, ViewGroup siempre llamará a su propio onInterceptTouchEvent para preguntar si quiere interceptar el evento.

El método requestDisallowInterceptTouchEvent está dirigido a eventos distintos de ACTION_DOWN y solo funcionará si no se intercepta el evento ACTION_DOWN.

A continuación, echemos un vistazo a la distribución de eventos cuando ViewGroup ya no intercepta eventos. El código fuente es el siguiente:

                        final View[] children = mChildren; 
                        for (int i = childrenCount - 1; i >= 0; i--) {  //遍历ViewGroup的所有子元素
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
							/**
							** 判断子元素是否能够接受到点击事件:
							** 子元素是否在播动画和点击事件的坐标是否落在子元素的区域内
							** 如果某个元素满足这两个条件,则事件交给它来处理
							**/

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);

							//dispatchTransformedTouchEvent实际调用的是子元素的dispatchTouchEvent方法

                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();

                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                //记录ACTION_DOWN事件已经被处理了
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }

A partir del código anterior, podemos saber que cuando el evento no es interceptado, primero atravesará todos los elementos secundarios del ViewGroup y luego determinará si el elemento secundario puede recibir el evento de clic. La base para juzgar es si el elemento hijo está reproduciendo animación y si las coordenadas del evento de clic se encuentran dentro del área del elemento hijo. Si se encuentra una Vista secundaria de destino para manejar el evento, se llama al método dispatchTransformedTouchEvent (). Eche un vistazo a la lógica de implementación importante de este método:

			  if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
				   ........
                    handled = child.dispatchTouchEvent(event);
                }

Se puede encontrar que debido a que el elemento secundario en lo anterior no es igual a nulo, el método dispatchTouchEvent del elemento secundario se llamará directamente para pasar el evento a la Vista secundaria y luego continuará distribuyéndose.

¿Crees que esto se acabó? La respuesta es definitivamente no. A partir del código fuente anterior, podemos encontrar que cuando dispatchTouchEvent del elemento secundario devuelve verdadero, hay operaciones correspondientes:

newTouchTarget = addTouchTarget(child, idBitsToAssign);
//记录ACTION_DOWN事件已经被处理了
alreadyDispatchedToNewTouchTarget = true;
break;

Estas líneas de código completan la asignación de mFirstTouchTarget y terminan el recorrido de los elementos secundarios. Si dispatchTouchEvent del elemento secundario devuelve falso, ViewGroup enviará el evento al siguiente elemento (si hay elementos secundarios). Es posible que se pregunte nuevamente cuando vea esto, ¿la asignación de mFirstTouchTarget? ¿Por qué no veo la sombra de mFirstTouchTarget? La respuesta está en el método addTouchTarget:

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

En este método se puede ver que mFirstTouchTarget es en realidad una estructura de lista vinculada individualmente. Primero, la Vista secundaria de destino se encuentra de acuerdo con el punto de coordenadas, y luego la Vista secundaria se coloca en la cabeza de la lista vinculada, ¡logrando así mFirstTouchTarget! = nulo.

En este punto, la distribución de eventos de ViewGroup se ha completado, pero aún no ha terminado. ¿Qué pasa si el evento no se maneja correctamente después de atravesar todos los elementos secundarios?

El trato inadecuado incluye dos situaciones:

  • ViewGroup no tiene elementos secundarios
  • El elemento secundario maneja el evento de clic, pero devuelve falso en dispatchTouchEvent (el valor predeterminado es devolver verdadero, solo este método que anula View o devuelve falso en onTouchEvent)

Luego, ViewGroup manejará el evento de clic por sí mismo en este momento (se realiza el mismo procesamiento cuando ViewGroup intercepta el evento).

            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            }

Se puede ver que el método dispatchTransformedTouchEvent todavía se llama en este momento, pero esta vez el tercer parámetro no es secundario sino nulo, por lo que se llama al siguiente código:

//dispatchTransformedTouchEvent
handled = super.dispatchTouchEvent(event);

Super es en realidad el método dispatchTouchEvent en la Vista, por lo que la Vista comienza a manejar el evento de clic.

ViewGroup no llamó a onTouchEvent y ViewGroup no anuló onTouchEvent

El diagrama de flujo es el siguiente:

3. Ver el mecanismo de distribución de eventos

De lo anterior, podemos ver en el mecanismo de distribución de eventos ViewGroup que el mecanismo de distribución de eventos View comienza desde dispatchTouchEvent.

Código fuente: Ver # dispatchTouchEvent

    public boolean dispatchTouchEvent(MotionEvent event) {
	    ......
        boolean result = false;
		......
		//判断窗口是否被遮挡,如果被遮挡则返回false,比如有时候两个View是会重叠的,导致其中一个被遮挡了。
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
		   //判断是否设置了mOnTouchListener,如果设置了onTouchListener,且onTouch方法返回了ture,
            //则result = true
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
			//在result = ture情况下,就不会调用onTouchEvent,可见onTouchListener的优先级高于onTouchEvent
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
		......
        return result;
    }

Dado que la Vista es un elemento separado, no hay elementos secundarios que puedan continuar transmitiendo eventos y solo pueden manejar eventos por sí mismos, por lo que el código se reducirá significativamente. A partir del código fuente anterior, podemos ver cómo la Vista maneja el evento de clic. El resultado representa si el evento se consume y luego se juzga onTouchListener. Si el método onTouch en onTouchListenter devuelve verdadero, entonces no se volverá a llamar al método onTouchEvent Esto muestra que la prioridad de onTouchListener es mayor que onTouchEvent.

Entonces echemos un vistazo a la implementación de 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; 
        }
		//如果VIew设置了代理,将会执行代理的onTouchEvent方法
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    .....
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    	  ......
                    	  //经过种种判断
                           performClickInternal();
                    break;

                case MotionEvent.ACTION_DOWN:
                    ....
                    break;

                case MotionEvent.ACTION_CANCEL:
                    ....
                    break;

                case MotionEvent.ACTION_MOVE:
                    ....
                    break;
            }
		   //若该控件可点击,就一定返回true
            return true;
        }
        //若该控件不可点击,就一定返回false
        return false;
    }

A partir del código anterior, podemos saber que siempre que uno de los CLICKABLE, LONG_CLICKABLE y CONTEXT_CLICKABLE de View sea verdadero, consumirá el evento, independientemente de si está en el estado DISABLE. Luego, si se puede hacer clic en el control, los cuatro tipos de eventos se procesan en consecuencia. Vale la pena mencionar aquí el evento ACTION_UP. Se puede encontrar en el código fuente que el método performClickInternal se activará cuando ocurra el evento ACTION_UP. ¿Cuál es la implementación interna de este método? como sigue:

    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();

        return performClick();
    }

Podemos encontrar que performClick se seguirá llamando al final, y dentro de performClick:

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

Siempre que registremos un evento de clic para la Vista a través de setOnClickListener, entonces se asignará un valor a li.mOnClickListener y se llamará al método onClick.

El diagrama de flujo es el siguiente:

Desde el diagrama de flujo podemos encontrar la prioridad de onTouch, onTouchEvent, onClick: onTouch> onTouchEvent> onClick

para resumir

Hasta ahora, hemos resuelto el mecanismo de distribución de eventos de clic a través del código fuente. El proceso general de distribución de eventos es el siguiente:

  • Cuando ocurre un evento de clic, siempre se pasa primero a la Actividad actual, distribuido por dispatchTouchEvent de la Actividad, y la Actividad pasará el evento a la Ventana, y luego la única clase de implementación de la Ventana PhoneWindow pasará el evento a el DecorView, y luego DecorView pasará el evento Pasado a su propia clase principal ViewGroup, el ViewGroup en este momento es el View establecido por setContentView, por lo que se puede llamar la Vista de nivel superior. En este momento, el ViewGroup puede manejar el evento por sí mismo o pasarlo a la Vista secundaria, pero eventualmente llamará al dispatchTouchEvent de la Vista. Manejo de eventos.
  • En dispatchTouchEvent de View, si onTouchListener está configurado, se llamará a su método onTouch. Si onTouch devuelve verdadero, ya no se llamará a onTouchEvent. Si hay un conjunto de eventos de clic, se llamará al método onClick en onTouchEvent. Si el onTouchEvent de la vista secundaria devuelve falso, significa que el evento no se consume y el evento se devolverá al onTouchEvent del ViewGroup anterior. Si todos los ViewGroups no devuelven verdadero, finalmente se devolverá al onTouchEvent de la actividad.

Blog de referencia:

Supongo que te gusta

Origin blog.csdn.net/zhireshini233/article/details/114946014
Recomendado
Clasificación