Controle deslizante de vibração

estrutura central

Tomando PageView como exemplo
, pv é personalizado com base em rolagem. Existem quatro componentes principais para completar a função: ScrollNotification, RawGestureDetector, ScrollController, ScrollPosition e ViewPort

  • ScrollNotification: Encapsule Notificaiton para obter esse tipo de notificação, determine se a página foi trocada com base no deslocamento nas informações de notificação e, em seguida, ligue novamente para onPageChanged

  • RawGestureDetector: classe de coleção de gestos, o método setCanDrag de Scrollable está vinculado a VerticalDragGestureRecognizer ou HorizontalDragGestureRecognizer para coletar informações deslizantes em ambas as direções.

  • ScrollController e ScrollPosition: ScrollPosition é o objeto que realmente controla o deslizamento em Scrollable. No método de anexação de SrcollController, ScrollPosition adicionará ScrollController como seu observador aos ouvintes. Os ouvintes de rolagem geralmente são adicionados por meio do método ScrollController.addListener.

  • ViewPort: aceita o deslocamento de ScrollPosition e desenha a área para completar o deslizamento

análise de processo

  • RawGestureDetector coleta informações de gestos quando o dedo desliza
  • Chame de volta informações de gesto em Scrollable
  • Após Scrollable receber as informações, ele realiza o controle deslizante através de ScrollPosition.
  • Modifique o deslocamento e desenhe diferentes áreas através da viewport
  • Notificar o scrollcontroller para notificação do observador

Clique em entrega de evento

Os dados nativos são encaminhados para vibração por meio do mecanismo C++.

Aqui selecionamos alguns métodos para análise

GestureBinding _handlePointerDataPacket (pacote ui.PointerDataPacket)

Mapeie os dados em pixels lógicos e depois converta-os em pixels do 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()

Chame _handlePointerEvent para cada evento de clique na lista a ser processado

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

GestureBinding _handlePointerEvent(evento PointerEvent)

Processamento de eventos down, up e move, incluindo: adição à coleção, retirada, remoção e distribuição

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

teste 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 BoxHitTestResult, { @required Offset position })

  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 BoxHitTestResult, { @required Offset position })

Fornece todos os controles de desenho na posição especificada

  • Retorna verdadeiro.Quando este controle ou seus subcontroles estão na posição determinada,adicione o objeto desenhado ao hitResult fornecido para marcar que o controle atual absorveu o evento de clique e outros controles não responderão.

  • Retorna falso, indicando que este evento é entregue ao controle após o objeto atual para processamento

Por exemplo, em uma fileira, várias áreas podem responder a cliques. Contanto que o primeiro bloco possa responder a cliques, não há necessidade de julgar se ele poderá responder no futuro.

As coordenadas globais são convertidas nas coordenadas associadas ao RenderBox.O RenderBox é responsável por determinar se as coordenadas estão incluídas no intervalo atual.

Este método depende do layout mais recente em vez da pintura, porque a área de julgamento só precisa ser definida

Para cada filho, seu próprio hitTest é chamado, então o wiget filho com o layout mais profundo é colocado no início.

Este método primeiro verifica se está dentro do intervalo. Nesse caso, ele chama hitTestChildren e recursivamente chama o hitTest do widget filho. Quanto mais profundo o widget, mais cedo ele é adicionado ao HitTestResult.

Após a execução, HitTestResult obtém a coleção de todos os controles responsivos nas coordenadas do evento de clique e, finalmente, adiciona-se ao final do Result em 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;
}

Quando hitTestResult não está vazio, a distribuição de eventos é executada e o handleEvent de cada objeto da coleção é chamado em um loop. No entanto, nem todos os controles irão manipular handlerEvent. Na maioria das vezes, apenas RenderPointerListener irá lidar com isso.

handleEvent retornará o processamento de gestos relevante do RawGestureDetector de acordo com os diferentes tipos de eventos.

Economizar

O evento down ocorre e hittest obtém uma coleção de objetos que podem responder ao evento com base na posição clicada. O final da coleção é GestureBinding e os eventos são distribuídos por meio de dispatchEvent . No entanto, nem todas as subclasses RenderObject
em todos os espaços manipulam handleEvent Na maioria das vezes, o handleEvent é processado pelo objeto incorporado no RawGestureDetector. Processamento RenderPointerListener ,
handleEvent chama de volta ao processamento de gestos relacionados do RawGestureDetector de acordo com diferentes tipos de eventos.

competição de gestos

Porque normalmente após clicar, será retornado um conjunto de componentes que podem responder a eventos.Qual componente precisa ser entregue a ele para processamento?

  • GestureRecoginizer : classe base do reconhecedor de gestos. Basicamente, os eventos de gesto que precisam ser processados ​​no RenderPointListener serão distribuídos para seu GestureRecognizer correspondente e depois distribuídos após processamento e competição. Os mais comuns incluem: OneSequenceGestureRecognizer, MultiTapGestureRecognizer, VerticalDragGestureRecognizer, TapGestureRecognizer, etc.
  • GestureArenaManager : Gestão gestual da competição, gerencia o processo da competição, as condições de vitória são: o primeiro membro a vencer a competição ou o último membro a não ser rejeitado .
  • GestureArenaEntry : Uma entidade que fornece informações sobre a competição do evento de gestos e encapsula os membros participantes da competição do evento.
  • GestureArenaMember : Objeto abstrato dos membros participantes da competição. Existem métodos acceptGesture e rejeitarGesture internamente. Ele representa os membros da competição de gestos. O GestureArenaRecognizer padrão o implementa. Todos os membros da competição podem ser entendidos como competição entre GestureRecognizers.
  • _GetureArena : A arena no GestureArenaManager, contendo a lista dos membros participantes da competição

Quando um gesto tenta vencer quando a arena está aberta com isOpen = true , ele se tornará um objeto com o atributo "ansioso para vencer".Quando
a arena estiver fechada, a arena tentará encontrar um objeto ansioso para vencer para se tornar um novo participante.

GestureBinding handleEvent (evento PointerEvent, entrada HitTestEntry)

Os eventos de navegação acionam o handleEvent do GestureRecognizer. Geralmente, PointerDownEvent não é processado na execução da rota.

gestoArena GestureArenaManager

Evento inativo leva ao fechamento da 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 void close (ponteiro int)

Chamado após a conclusão da distribuição do evento para evitar que novos membros entrem na competição

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

GestureArenaManager _tryToResolveArena (ponteiro 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);
  }
}

Varredura do GestureArenaManager (ponteiro interno)

Forçar a arena a fazer uma
varredura vencedora geralmente ocorre após o evento ascendente. Garante que a concorrência não cause atrasos que impeçam os usuários de interagir com o aplicativo.

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 aceitarGesture (ponteiro int)

Para marcar a vitória da competição de gestos, chame _checkDown(). Se tiver sido processado, não será processado novamente. Se não tiver sido processado, será chamado 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()

Se não for o vencedor ou o evento estiver vazio, retorne,
caso contrário, administre o evento up e reinicie

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

TapGestureRecognizer#handleTapUp({PointerDownEvent desativado, PointerUpEvent ativo})

Primeiro execute onTapUp e depois onTap para completar o reconhecimento de um evento de clique.

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

Economizar:

  • O evento é passado da camada Native para a camada Dart por meio de C++ e é processado em GestureBinding após ser mapeado para pixels lógicos.
  • Todos os gestos começam de baixo. Na fase de baixo, HitTest começa no nó raiz responsável por desenhar a árvore, adiciona recursivamente controles que podem responder a eventos ao HitTestResult e, finalmente, adiciona GesureBinidng ao final para distribuir eventos para cada objeto no resultado .
  • O RawGestureDetector processará o evento. Na fase inferior, os competidores serão adicionados ao _GestureArena para competir e, finalmente, retornarão ao GestureBinding para fechar a arena. Se houver apenas um RawGestureDetector competindo, ele aceitará o Gesture diretamente, mas o onTap ainda não será acionado. Depois que o evento up terminar, acione o onTap após acionar o onTapup
  • Quando vários RawGestureDetectors competem, o primeiro na varredura é selecionado como vencedor.
  • Deslizar cria um vencedor na fase de movimento.

Acho que você gosta

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