Control deslizante de aleteo

estructura central

Tomando PageView como ejemplo
, pv se personaliza según el desplazamiento y hay cuatro componentes principales para completar la función: ScrollNotification, RawGestureDetector, ScrollController, ScrollPosition y ViewPort.

  • ScrollNotification: encapsule Notificaiton para obtener este tipo de notificación, determine si la página se cambia según el desplazamiento en la información de notificación y luego vuelva a llamar a onPageChanged

  • RawGestureDetector: clase de colección de gestos, el método setCanDrag de Scrollable está vinculado a VerticalDragGestureRecognizer u HorizontalDragGestureRecognizer para recopilar información de deslizamiento en ambas direcciones.

  • ScrollController y ScrollPosition: ScrollPosition es el objeto que realmente controla el deslizamiento en Scrollable. En el método adjunto de SrcollController, ScrollPosition agregará ScrollController como su observador a los oyentes. Los oyentes de desplazamiento a menudo se agregan a través del método ScrollController.addListener.

  • ViewPort: acepta el desplazamiento de ScrollPosition y dibuja el área para completar el deslizamiento

Análisis de proceso

  • RawGestureDetector recopila información de gestos cuando el dedo se desliza
  • Devolver la información del gesto a Scrollable
  • Después de que Scrollable recibe la información, realiza el control deslizante a través de ScrollPosition.
  • Modifique el desplazamiento y dibuje diferentes áreas a través de la ventana gráfica
  • Notificar al controlador de desplazamiento para la notificación del observador

Haga clic en entrega de eventos

Los datos nativos se reenvían para fluir a través del motor C++.

Aquí seleccionamos algunos métodos de análisis.

GestureBinding _handlePointerDataPacket (paquete ui.PointerDataPacket)

Asigne los datos en datos a píxeles lógicos y luego conviértalos en píxeles del dispositivo

//未处理的事件队列
final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();
//这里的packet是一个点的信息
void _handlePointerDataPacket(ui.PointerDataPacket packet) {
    
    
  // 将data中的数据,映射到为逻辑像素,再转变为设备像素
  _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
  if (!locked)
    _flushPointerEventQueue();
}

GestureBinding _flushPointerEventQueue()

Llame a _handlePointerEvent para cada evento de clic en la lista para procesar

void _flushPointerEventQueue() {
    
    
  assert(!locked);
  while (_pendingPointerEvents.isNotEmpty)
     //处理每个点的点击事件
    _handlePointerEvent(_pendingPointerEvents.removeFirst());
}

GestureBinding _handlePointerEvent (evento PointerEvent)

Procesamiento de eventos de bajada, subida y movimiento, incluidos: agregar a la colección, sacar, eliminar y distribuir

final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{
    
    };
void _handlePointerEvent(PointerEvent event) {
    
    
  HitTestResult hitTestResult;
  if (event is PointerDownEvent || event is PointerSignalEvent) {
    
    
    //down事件进行hitTest
    hitTestResult = HitTestResult();
    hitTest(hitTestResult, event.position);
    if (event is PointerDownEvent) {
    
    
      // dowmn事件:操作开始,对这个hitTest集合赋值
      _hitTests[event.pointer] = hitTestResult;
    }
  } else if (event is PointerUpEvent || event is PointerCancelEvent) {
    
    
    // up事件:操作结束,所以移除
    hitTestResult = _hitTests.remove(event.pointer);
  } else if (event.down) {
    
    
    // move事件也被分发在down事件初始点击的区域  
    // 比如点击了列表中的A item这个时候开始滑动,那处理这个事件的始终只是列表和A item
    // 只是如果滑动的话事件是由列表进行处理
    hitTestResult = _hitTests[event.pointer];
  }
  // 分发事件
  if (hitTestResult != null ||
      event is PointerHoverEvent ||
      event is PointerAddedEvent ||
      event is PointerRemovedEvent) {
    
    
    dispatchEvent(event, hitTestResult);
  }
}

prueba de calor

///renderview:负责绘制的root节点
RenderView get renderView => _pipelineOwner.rootNode;
///绘制树的owner,负责绘制,布局,合成
PipelineOwner get pipelineOwner => _pipelineOwner;

void hitTest(HitTestResult result, Offset position) {
    
    
  assert(renderView != null);
  renderView.hitTest(result, position: position);
  super.hitTest(result, position);
  =>
  GestureBinding#hitTest(HitTestResult result, Offset position) {
    
    
    result.add(HitTestEntry(this));
  }
}

RenderView hitTest (resultado de BoxHitTestResult, { @required Posición de desplazamiento })

  bool hitTest(HitTestResult result, {
    
     Offset position }) {
    
    
    if (child != null)RenderBox)child.hitTest(BoxHitTestResult.wrap(result), position: position);
    result.add(HitTestEntry(this));
    return true;
  }

RenderBox hitTest (resultado de BoxHitTestResult, { @required Posición de desplazamiento })

Proporciona todos los controles de dibujo en la posición especificada.

  • Devuelve verdadero. Cuando este control o sus subcontroles están en la posición dada, agregue el objeto dibujado al hitResult dado para marcar que el control actual ha absorbido el evento de clic y otros controles no responderán.

  • Devuelve falso, lo que indica que este evento se entrega al control después del objeto actual para su procesamiento.

Por ejemplo, en una fila, varias áreas pueden responder a los clics. Siempre que el primer bloque pueda responder a los clics, no es necesario juzgar si podrá responder en el futuro.

Las coordenadas globales se convierten a las coordenadas asociadas con RenderBox. RenderBox es responsable de determinar si las coordenadas están incluidas en el rango actual.

Este método se basa en el diseño más reciente en lugar de en la pintura, porque el área de juicio solo necesita diseñarse

Para cada niño, se llama su propio hitTest, por lo que el elemento secundario con el diseño más profundo se coloca al principio.

Este método primero verifica si está dentro del rango. Si es así, llama a hitTestChildren y llama recursivamente al hitTest del widget secundario. Cuanto más profundo sea el widget, antes se agregará a HitTestResult.

Después de la ejecución, HitTestResult obtiene la colección de todos los controles de respuesta en las coordenadas del evento de clic y finalmente se agrega al final de Result en GestureBinding.

bool hitTest(BoxHitTestResult result, {
    
      Offset position }) {
    
    
  if (_size.contains(position)) {
    
    
    if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
    
    
      result.add(BoxHitTestEntry(this, position));
      return true;
    }
  }
  return false;
}

Cuando hitTestResult no está vacío, se realiza la distribución de eventos y se llama en un bucle al handleEvent de cada objeto de la colección. Sin embargo, no todos los controles manejarán handlerEvent. La mayoría de las veces, solo RenderPointerListener lo manejará.

handleEvent volverá a llamar al procesamiento de gestos relevante de RawGestureDetector según los diferentes tipos de eventos.

Ahorrar dinero

Se produce el evento down y hittest obtiene una colección de objetos que pueden responder al evento según la posición en la que se hace clic. El final de la colección es GestureBinding y los eventos se distribuyen a través de despachoEvent. Sin embargo, no todas las subclases de RenderObject
en todos los espacios manejan handleEvent La mayoría de las veces, handleEvent es procesado por el objeto incrustado en RawGestureDetector. Procesamiento RenderPointerListener ,
handleEvent vuelve a llamar al procesamiento de gestos relacionado de RawGestureDetector de acuerdo con diferentes tipos de eventos.

competencia de gestos

Porque generalmente después de hacer clic, se devolverá un conjunto de componentes que pueden responder a eventos, ¿qué componente se le debe entregar para su procesamiento?

  • GestureRecoginizer : clase base del reconocedor de gestos. Básicamente, los eventos de gestos que deben procesarse en RenderPointListener se distribuirán a su GestureRecognizer correspondiente y luego se distribuirán después del procesamiento y la competencia. Los más comunes incluyen: OneSequenceGestureRecognizer, MultiTapGestureRecognizer, VerticalDragGestureRecognizer, TapGestureRecognizer, etc.
  • GestureArenaManager : Gestión de la competencia de gestos, gestiona el proceso de la competencia, las condiciones para ganar son: el primer miembro en ganar la competencia o el último miembro en no ser rechazado .
  • GestureArenaEntry : entidad que proporciona información sobre la competencia del evento de gestos y encapsula a los miembros que participan en la competencia del evento.
  • GestureArenaMember : Objeto abstracto de los miembros que participan en la competencia. Hay métodos de aceptación y rechazo de gestos internamente. Representa a los miembros de la competencia de gestos. El GestureArenaRecognizer predeterminado lo implementa. Todos los miembros de la competencia pueden entenderse como una competencia entre GestureRecognizers.
  • _GetureArena : La arena en GestureArenaManager, que contiene la lista de miembros que participan en la competencia.

Cuando un gesto intenta ganar cuando la arena está abierta con isOpen = true , se convertirá en un objeto con el atributo "ansioso por ganar".
Cuando la arena está cerrada, la arena intentará encontrar un objeto ansioso por ganar para convertirse en un nuevo participante.

GestureBinding handleEvent (evento PointerEvent, entrada HitTestEntry)

Los eventos de navegación activan el handleEvent de GestureRecognizer. Generalmente, PointerDownEvent no se procesa en la ejecución de la ruta.

gestoArena GestoArenaManager

Un evento cerrado obliga a cerrar la arena

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

GestureArenaManager cierre vacío (puntero int)

Se llama después de que se completa la distribución del evento para evitar que nuevos miembros ingresen a la competencia.

void close(int pointer) {
    
    
   //拿到上面 addPointer 时添加的成员封装
  final _GestureArena state = _arenas[pointer];
  //关闭竞技场
  state.isOpen = false;
  //决出胜者
  _tryToResolveArena(pointer, state);
}

GestureArenaManager _tryToResolveArena (puntero int, estado _GestureArena)

void _tryToResolveArena(int pointer, _GestureArena state) {
    
    
  if (state.members.length == 1) {
    
    
    //只有一个竞技成员的话,直接获胜,触发对应空间的acceptGesture
    scheduleMicrotask(() => _resolveByDefault(pointer, state));
  }
  else if (state.members.isEmpty) {
    
    
    //无竞技成员
    _arenas.remove(pointer);
  } 
  else if (state.eagerWinner != null) {
    
    
    //多个竞技成员
    _resolveInFavorOf(pointer, state, state.eagerWinner);
  }
}

Barrido de GestureArenaManager (puntero int)

Obligar a la arena a realizar un
barrido ganador generalmente ocurre después del evento up. Garantiza que la competencia no provoque retrasos que impidan a los usuarios interactuar con la aplicación.

void sweep(int pointer) {
    
    
  ///获取竞争的对象
  final _GestureArena state = _arenas[pointer];
  if (state.isHeld) {
    
    
    state.hasPendingSweep = true;
    return;
  }
  _arenas.remove(pointer);
  if (state.members.isNotEmpty) {
    
    
    //第一个竞争者获取胜利,就是Widget树中最深的组件
    state.members.first.acceptGesture(pointer);
    for (int i = 1; i < state.members.length; i++)
      ///让其他的竞争者拒绝接收手势
      state.members[i].rejectGesture(pointer);
  }
}

BaseTapGestureRecognizer aceptarGesture (puntero int)

Para marcar la victoria en la competencia de gestos, llame a _checkDown(). Si se ha procesado, no se volverá a procesar. Si no se ha procesado, se llamará a handleTapDown.


void acceptGesture(int pointer) {
    
    
  //标志已经获得了手势的竞争
  super.acceptGesture(pointer);
  if (pointer == primaryPointer) {
    
    
    _checkDown();
    _wonArenaForPrimaryPointer = true;
    _checkUp();
  }
}
void _checkDown() {
    
    
   //如果已经处理过了,就不会再次处理!!
   if (_sentTapDown) {
    
    
     return;
   }
   //交给子控件处理down事件
   handleTapDown(down: _down);
   _sentTapDown = true;
}

BaseTapGestureRecognizer _checkUp()

Si no es el ganador o el evento está vacío, regrese;
de ​​lo contrario, maneje el evento y reinicie

void _checkUp() {
    
    
  ///_up为空或者不是手势竞争的胜利者,则直接返回
  if (!_wonArenaForPrimaryPointer || _up == null) {
    
    
    return;
  }
  handleTapUp(down: _down, up: _up);
  _reset();
}

TapGestureRecognizer#handleTapUp({PointerDownEvent abajo, PointerUpEvent arriba})

Primero ejecute onTapUp y luego onTap para completar el reconocimiento de un evento de clic.

void handleTapUp({
    
    PointerDownEvent down, PointerUpEvent up}) {
    
    
  final TapUpDetails details = TapUpDetails(
    globalPosition: up.position,
    localPosition: up.localPosition,
  );
  switch (down.buttons) {
    
    
    case kPrimaryButton:
      if (onTapUp != null)
        invokeCallback<void>('onTapUp', () => onTapUp(details));
      if (onTap != null)
        invokeCallback<void>('onTap', onTap);
      break;
    case kSecondaryButton:
      if (onSecondaryTapUp != null)
        invokeCallback<void>('onSecondaryTapUp',
          () => onSecondaryTapUp(details));
      break;
    default:
  }
}

Ahorrar dinero:

  • El evento se pasa de la capa Nativa a la capa Dart a través de C++ y se procesa en GestureBinding después de asignarlo a píxeles lógicos.
  • Todos los gestos comienzan desde abajo. En la fase descendente, HitTest comienza desde el nodo raíz responsable de dibujar el árbol, agrega recursivamente controles que pueden responder a eventos a HitTestResult y finalmente agrega GesureBinidng al final para distribuir eventos a cada objeto en el resultado. .
  • RawGestureDetector procesará el evento. En la etapa inferior, los competidores se agregarán a _GestureArena para competir y finalmente regresarán a GestureBinding para cerrar la arena. Si solo hay un RawGestureDetector compitiendo, aceptará Gesture directamente, pero onTap aún no estará disponible. activado Después de que finaliza el evento up Activar onTap después de activar onTapup
  • Cuando compiten varios RawGestureDetectors, el primero en barrer es seleccionado como el ganador.
  • Deslizarse crea un ganador en la etapa de movimiento.

Supongo que te gusta

Origin blog.csdn.net/weixin_51109304/article/details/131994361
Recomendado
Clasificación