Hablando sobre el mecanismo central de Flutter --- distribución de eventos

prefacio

Los eventos en Flutter se envían a la capa del marco a través de la capa del motor, y GestureBinding es responsable del procesamiento de los eventos de la capa del marco, y luego GestureBinding se usa como entrada para el análisis.

Clasificación de señal de evento de dispositivo

1. Flutter define eventos táctiles, mouse, stylus, invertedStylus y unknown para el dispositivo. Los eventos generados por el toque en la pantalla del teléfono móvil corresponden al tipo de toque. Otros incluyen lápiz óptico, mouse y otros dispositivos.

enum PointerDeviceKind {
  /// A touch-based pointer device.
  touch,
  /// A mouse-based pointer device.
  mouse,
  /// A pointer device with a stylus.
  stylus,
  /// A pointer device with a stylus that has been inverted.
  invertedStylus,
  /// An unknown pointer device.
  unknown
}

2. Los datos de las fuentes de eventos anteriores se convierten a través del método PointerEventConverter#expand, y los eventos finalmente reflejados en el lado comercial de Flutter se abstraerán en PointerEvent.

  void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    // We convert pointer data to logical pixels so that e.g. the touch slop can be
    // defined in a device-independent manner.
    1,转换数据成PointerEvent
    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
    if (!locked)
    2,处理事件
      _flushPointerEventQueue();
  }

tipo de evento

Los eventos desencadenados por la pantalla en Flutter definen los siguientes tipos:

  • PointerAddedEvent: pantalla táctil (obtener evento, rastreador de eventos creado)
  • PointerHoverEvent: evento de desplazamiento (el rastreador de eventos se creó anteriormente, use el rastreador de eventos nuevamente, mutuamente excluyente con PointerAddedEvent)
  • PointerDownEvent: evento de prensa (ocurre después de PointerAddedEvent o PointerHoverEvent)
  • PointerMoveEvent: evento deslizante
  • PointerUpEvent: evento completamente fuera de la pantalla
  • PointerCancelEvent : cancelación de eventos (el rastreador de eventos se desconecta, como cuando el foco del diálogo cambia mientras se desliza, el evento se cancela, emparejado con PointerAddedEvent)
  • PointerRemovedEvent: es posible que el evento se haya desviado del rango de detección del dispositivo o que se haya desconectado por completo del sistema.

3. Procesar eventos para actualizar la cola de eventos

  void _flushPointerEventQueue() {
    assert(!locked);
    //处理队列里面所有的事件
    while (_pendingPointerEvents.isNotEmpty)
      handlePointerEvent(_pendingPointerEvents.removeFirst());
  
  void handlePointerEvent(PointerEvent event) {
    ...
    _handlePointerEventImmediately(event);
  }
  void _handlePointerEventImmediately(PointerEvent event) {
    HitTestResult? hitTestResult;
 1.  if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent) {
      assert(!_hitTests.containsKey(event.pointer));
      hitTestResult = HitTestResult();
 2.    hitTest(hitTestResult, event.position);
      if (event is PointerDownEvent) {
        _hitTests[event.pointer] = hitTestResult;
      }
​
3. } else if (event is PointerUpEvent || event is PointerCancelEvent) {
      hitTestResult = _hitTests.remove(event.pointer);
4.} else if (event.down) {
      // Because events that occur with the pointer down (like
      // [PointerMoveEvent]s) should be dispatched to the same place that their
      // initial PointerDownEvent was, we want to re-use the path we found when
      // the pointer went down, rather than do hit detection each time we get
      // such an event.
      hitTestResult = _hitTests[event.pointer];
    }
​
    if (hitTestResult != null ||
        event is PointerAddedEvent ||
        event is PointerRemovedEvent) {
 
5.      dispatchEvent(event, hitTestResult);
    }
  }
  1. Los eventos down y hover crearán un HitTestResult. Si es un evento down, se almacenará en una colección (distribución completa de eventos con eventos posteriores de movimiento y subida)

  2. El método esclavo hitTest aquí está anulado por RendererBinding, la raíz es hitTest y se agrega una raíz

      @override
      void hitTest(HitTestResult result, Offset position) {
        assert(renderView != null);
        assert(result != null);
        assert(position != null);
        //renderView对事件进行hitTest冒泡
        renderView.hitTest(result, position: position);
        //调用super添加一个根部的HitTestEntry节点
        super.hitTest(result, position);
      }

    renderView.hitTest

     bool hitTest(HitTestResult result, { required Offset position }) {
        if (child != null)
        //对child进行层级遍历hitTest,对命中的会 result.add(HitTestEntry());进行事件处理 
          child!.hitTest(BoxHitTestResult.wrap(result), position: position);
       //对命中的创建一个HitTestEntry进行事件处理   
        result.add(HitTestEntry(this));
        return true;
      }
  3. El evento up elimina HitTestResult de la colección y la cadena de eventos finaliza esta vez.

  4. event.down aquí se refiere a PointerMoveEvent

  5. dispatchEvent evento despachador

  void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
    assert(!locked);
    // No hit test information implies that this is a [PointerHoverEvent],
    // [PointerAddedEvent], or [PointerRemovedEvent]. These events are specially
    // routed here; other events will be routed through the `handleEvent` below.
    if (hitTestResult == null) {
      assert(event is PointerAddedEvent || event is PointerRemovedEvent);
      try {
        pointerRouter.route(event);
      } catch (exception, stack) {
​
      }
      return;
    }
​
    for (final HitTestEntry entry in hitTestResult.path) {
      try {
       //调消费事件
        entry.target.handleEvent(event.transformed(entry.transform), entry);
      } catch (exception, stack) {
        
          },
        ));
      }
    }
  }

Diagrama de flujo de distribución de eventos

resumen

Se agregará un HitTestEntry a HitTestResult solo si se acierta y, finalmente, el método handleEvent se ejecutará después de recibir el procesamiento del evento. El método handleEvent redistribuirá el evento al reconocedor de gestos. Desde el método hitest en el RenderObject anterior, puede se ve que se llama primero al método más alto en el niño, de modo que la prioridad del evento se le puede dar al niño con el nivel más profundo

evento de reconocimiento de gestos

El método handleEvent en GestureBinding es el último que se ejecuta, y principalmente realiza competencia de eventos de gestos.

Miembros de reconocimiento de gestos:

  1. Gerente de Atletismo GestureArenaManager

  2. puntero Un valor int, el valor del puntero en el enlace completo de abajo hacia arriba no cambiará

  3. Competidor GestureArenaMember (reconocedor de gestos específico, como: TapGestureRecognizer, VerticalDragGestureRecognizer)

  4. _GestureArena One Pointer solo crea uno

  5. GestureArenaEntry Una clase compuesta, un puntero creará múltiples enlaces para registrar eventos (correspondientes a GestureArenaManager.add)

Gerente de GestureArena

GestureArenaManager es una clase de gestión con solo una creada globalmente, y solo expone cinco métodos: agregar, barrer, mantener, cerrar, liberar

  1. add : Agrega un reconocedor de gestos (GestureArenaMember)
  2. cerrar: cierra la arena de gestos y prohíbe la entrada de nuevos miembros
  3. Barrido: deje que gane el primer GestureArenaMember (reconocedor de gestos) en _GestureArena (arena), cuando mantenga presionado = verdadero en _GestureArena, retrase y deje que gane el primero
  4. hold : marca el punto como hold
  5. liberación: borre la bandera de retención y fuerce al primero a ganar (igual que barrer)
1
  GestureArenaEntry add(int pointer, GestureArenaMember member) {
    final _GestureArena state = _arenas.putIfAbsent(pointer, () {
      assert(_debugLogDiagnostic(pointer, '★ Opening new gesture arena.'));
      return _GestureArena();
    });
    state.add(member);
    assert(_debugLogDiagnostic(pointer, 'Adding: $member'));
    return GestureArenaEntry._(this, pointer, member);
  }
​
2
  void close(int pointer) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    state.isOpen = false;
    assert(_debugLogDiagnostic(pointer, 'Closing', state));
    _tryToResolveArena(pointer, state);
  }
​
3
  void sweep(int pointer) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    assert(!state.isOpen);
    if (state.isHeld) {
      state.hasPendingSweep = true;
      assert(_debugLogDiagnostic(pointer, 'Delaying sweep', state));
      return; // This arena is being held for a long-lived member.
    }
    assert(_debugLogDiagnostic(pointer, 'Sweeping', state));
    _arenas.remove(pointer);
    if (state.members.isNotEmpty) {
      
      state.members.first.acceptGesture(pointer);
      // Give all the other members the bad news.
      for (int i = 1; i < state.members.length; i++)
        state.members[i].rejectGesture(pointer);
    }
  }
​
4
  void hold(int pointer) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    state.isHeld = true;
    assert(_debugLogDiagnostic(pointer, 'Holding', state));
  }
​
5
  void release(int pointer) {
    final _GestureArena? state = _arenas[pointer];
    if (state == null)
      return; // This arena either never existed or has been resolved.
    state.isHeld = false;
    assert(_debugLogDiagnostic(pointer, 'Releasing', state));
    if (state.hasPendingSweep)
      sweep(pointer);
  }

GestureArenaMiembro

GestureArenaMember es la clase de interfaz de nivel superior del reconocedor de gestos. Después de la competencia, si el reconocedor actual tiene éxito en la captura, se llamará al método acceptGesture y al método rechazóGesture.

abstract class GestureArenaMember {
  /// Called when this member wins the arena for the given pointer id.
  void acceptGesture(int pointer);
​
  /// Called when this member loses the arena for the given pointer id.
  void rejectGesture(int pointer);
}
复制代码

GestoArenaEntrada

Registre la devolución de llamada de cada evento de puntero del marco y cree un GestureArenaEntry una vez que la devolución de llamada

class GestureArenaEntry {
  GestureArenaEntry._(this._arena, this._pointer, this._member);
​
  final GestureArenaManager _arena;
  final int _pointer;
  final GestureArenaMember _member;
​
  /// Call this member to claim victory (with accepted) or admit defeat (with rejected).
  ///
  /// It's fine to attempt to resolve a gesture recognizer for an arena that is
  /// already resolved.
  void resolve(GestureDisposition disposition) {
    _arena._resolve(_pointer, _member, disposition);
  }
}

_GestureArena

_GestureArena, que gestiona todos los GestureArenaMembers del puntero actual (competidores, reconocedores de gestos específicos) y un ganador proudWinner

RenderObjeto

Todas las competiciones de reconocimiento de gestos se activan directa o indirectamente a través del método RenderObject#handleEvent. Hablamos sobre el proceso de distribución de eventos anteriormente. Llamará al método GestureRecognizer#addPointer. Aquí tomamos RangeSlider como ejemplo. El RenderObject correspondiente a RangeSlider es _RenderRangeSlider

  @override
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (event is PointerDownEvent && isEnabled) {
      // We need to add the drag first so that it has priority.
      _drag.addPointer(event);
      _tap.addPointer(event);
    }
  }
复制代码

El método de procesamiento anterior es dar prioridad al reconocedor de gestos del evento de deslizamiento para ingresar primero a la arena.

  1. addPointer
  void addPointer(PointerDownEvent event) {
    _pointerToKind[event.pointer] = event.kind;
    if (isPointerAllowed(event)) {
      addAllowedPointer(event);
    } else {
      handleNonAllowedPointer(event);
    }
  }
复制代码
  1. isPointerAllowed

El método isPointerAllowed será anulado por el reconocedor de gestos específico y determinará si rastrear este evento de acuerdo con la lógica comercial específica.

  1. PunteroRouter#addRoute

    Eventualmente se llamará al método PointerRouter#addRoute cuando isPointerAllowed devuelva verdadero

  @protected
  void startTrackingPointer(int pointer, [Matrix4? transform]) {
   // 添加到路由里面
    GestureBinding.instance!.pointerRouter.addRoute(pointer, handleEvent, transform);
    _trackedPointers.add(pointer);
    assert(!_entries.containsValue(pointer));
    _entries[pointer] = _addPointerToArena(pointer);
  }
复制代码
  1. GestureBinding#handleEvent activa el enrutamiento

    Se sabe por lo anterior que el último HitTestTarget es GestureBinding, por lo que está al final de la distribución del evento.

    @override // from HitTestTarget
     void handleEvent(PointerEvent event, HitTestEntry entry) {
     // 执行路由的回调
       pointerRouter.route(event);
       
       if (event is PointerDownEvent) {
       	//关闭竞技场禁止新成员加入,强制让第一个取胜
         gestureArena.close(event.pointer);
       } else if (event is PointerUpEvent) {
       	//强制让第一个获取胜利 
         gestureArena.sweep(event.pointer);
       } else if (event is PointerSignalEvent) {
         pointerSignalResolver.resolve(event);
       }
     }
  1. PointerRouter#ruta llamada ruta

      void route(PointerEvent event) {
        final Map<PointerRoute, Matrix4?>? routes = _routeMap[event.pointer];
        final Map<PointerRoute, Matrix4?> copiedGlobalRoutes = Map<PointerRoute, Matrix4?>.from(_globalRoutes);
        if (routes != null) {
          _dispatchEventToRoutes(
            event,
            routes,
            Map<PointerRoute, Matrix4?>.from(routes),
          );
        }
        _dispatchEventToRoutes(event, _globalRoutes, copiedGlobalRoutes);
      }
      
        void _dispatchEventToRoutes(
        PointerEvent event,
        Map<PointerRoute, Matrix4?> referenceRoutes,
        Map<PointerRoute, Matrix4?> copiedRoutes,
      ) {
        copiedRoutes.forEach((PointerRoute route, Matrix4? transform) {
          if (referenceRoutes.containsKey(route)) {
            _dispatch(event, route, transform);
          }
        });
      }
      
        void _dispatch(PointerEvent event, PointerRoute route, Matrix4? transform) {
        try {
          event = event.transformed(transform);
          route(event);
        } catch (exception, stack) {      
        }
      }
    1. GestureRecognizer#handleEvent

      La ruta corresponde al método handleEvent en el reconocedor de gestos

      El proceso general es el siguiente:

Resumir

Los eventos de Flutter se distribuyen primero a RenderObject, y cuanto más profundo sea el nivel de RenderObject, mayor será la prioridad de procesamiento (consulte el método hitTest). RenderOnject luego registra el evento específico en el reconocedor de gestos y luego participa en la competencia de gestos GestureBinding permitirá que gane el primer reconocedor de gestos que aún esté en la arena.

Supongo que te gusta

Origin blog.csdn.net/jdsjlzx/article/details/123551138
Recomendado
Clasificación