Контроль скольжения флаттера

основная структура

Если взять PageView в качестве примера
, pv настраивается на основе возможности прокрутки. Для выполнения этой функции используются четыре основных компонента: ScrollNotification, RawGestureDetector, ScrollController, ScrollPosition и ViewPort.

  • ScrollNotification: инкапсулировать Notificaiton, чтобы получить этот тип уведомления, определить, переключается ли страница, на основе смещения в информации уведомления, а затем выполнить обратный вызов onPageChanged.

  • RawGestureDetector: класс сбора жестов, метод setCanDrag класса Scrollable привязан к UpperDragGestureRecouncer или HorizontalDragGestureRecouncer для сбора информации о скольжении в обоих направлениях.

  • ScrollController и ScrollPosition: ScrollPosition — это объект, который фактически управляет скольжением в Scrollable. В методе присоединения SrcollController ScrollPosition добавит ScrollController в качестве наблюдателя к прослушивателям. Прослушиватели прокрутки часто добавляются с помощью метода ScrollController.addListener.

  • ViewPort: принимает смещение от ScrollPosition и рисует область для завершения скольжения.

анализ процесса

  • RawGestureDetector собирает информацию о жестах, когда палец скользит.
  • Информацию о жесте обратного вызова в прокручиваемую форму
  • После того как Scrollable получает информацию, он выполняет управление скольжением через ScrollPosition.
  • Измените смещение и нарисуйте разные области в окне просмотра.
  • Уведомить контроллер прокрутки для уведомления наблюдателя

Нажмите «Доставка мероприятия»

Собственные данные передаются через механизм C++.

Здесь мы выбираем некоторые методы для анализа

GestureBinding _handlePointerDataPacket (пакет ui.PointerDataPacket)

Сопоставьте данные в логических пикселях, а затем преобразуйте их в пиксели устройства.

//未处理的事件队列
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()

Вызов _handlePointerEvent для каждого события клика в списке для обработки.

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

GestureBinding _handlePointerEvent (событие PointerEvent)

Обработка событий опускания, подъема и перемещения, включая: добавление в коллекцию, извлечение, удаление и распространение.

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

тепловое испытание

///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 (результат BoxHitTestResult, { @required позиция смещения })

  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 (результат BoxHitTestResult, { @required позиция смещения })

Предоставляет все элементы управления рисованием в указанной позиции.

  • Возвращает true. Когда этот элемент управления или его подэлементы управления находятся в заданной позиции, добавьте нарисованный объект к данному hitResult, чтобы отметить, что текущий элемент управления поглотил событие щелчка, и другие элементы управления не будут реагировать.

  • Возвращает false, указывая, что это событие передается в элемент управления после текущего объекта на обработку.

Например, на клики могут реагировать несколько областей подряд.Пока первый блок может реагировать на клики, то нет необходимости судить, сможет ли он реагировать в дальнейшем.

Глобальные координаты преобразуются в координаты, связанные с RenderBox.RenderBox отвечает за определение того, включены ли координаты в текущий диапазон.

Этот метод основан на последней компоновке, а не на рисовании, поскольку необходимо только разметить область оценки.

Для каждого дочернего элемента вызывается свой hitTest, поэтому дочерний виджет с самой глубокой компоновкой размещается в начале.

Этот метод сначала проверяет, находится ли он в пределах диапазона. Если да, он вызывает hitTestChildren и рекурсивно вызывает hitTest дочернего виджета. Чем глубже виджет, тем раньше он добавляется в HitTestResult.

После выполнения HitTestResult получает коллекцию всех реагирующих элементов управления по координатам события щелчка и, наконец, добавляет себя в конец Result в 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;
}

Если hitTestResult не пуст, выполняется распределение событий и handleEvent каждого объекта в коллекции вызывается в цикле. Однако не все элементы управления будут обрабатывать handlerEvent. В большинстве случаев его будет обрабатывать только RenderPointerListener.

handleEvent вызовет соответствующую обработку жестов RawGestureDetector в соответствии с различными типами событий.

Экономить деньги

Происходит событие down, и hittest получает коллекцию объектов, которые могут реагировать на это событие в зависимости от позиции щелчка. Концом коллекции является GestureBinding, а события распределяются через диспетчеризацию. Однако не все подклассы RenderObject во всех пространствах
обрабатывают handleEvent . В большинстве случаев handleEvent обрабатывается объектом, встроенным в обработку RawGestureDetector.RenderPointerListener , handleEvent
вызывает обратно соответствующую обработку жестов RawGestureDetector в соответствии с различными типами событий.

соревнование жестов

Потому что обычно после нажатия будет возвращен набор компонентов, которые могут реагировать на события.Какой компонент нужно доставить в него для обработки?

  • GestureRecoginizer : базовый класс распознавателя жестов. По сути, события жестов, которые необходимо обработать в RenderPointListener, будут распределены в соответствующий GestureRecouncer, а затем распределены после обработки и конкуренции. Общие из них включают в себя: OneSequenceGestureRecouncer, MultiTapGestureRecouncer, ВертикальноеDragGestureRecouncer, TapGestureRecouncer и т. д.
  • GestureArenaManager : управление соревнованием жестов, управляет процессом соревнования, условия победы: первый участник, выигравший соревнование , или последний участник, который не будет отклонен .
  • GestureArenaEntry : объект, который предоставляет информацию о соревновании по жестам и инкапсулирует участников, участвующих в соревновании по событию.
  • GestureArenaMember : абстрактный объект участников, участвующих в соревновании. Внутри имеются методы AcceptGesture и RejectGesture. Он представляет участников соревнования жестов. Его реализует GestureArenaRecouncer по умолчанию. Всех участников соревнования можно понимать как соревнование между GestureRecouncer.
  • _GetureArena : арена в GestureArenaManager, содержащая список участников, участвующих в соревновании.

Когда жест пытается выиграть, когда арена открыта с isOpen = true , он становится объектом с атрибутом «жаждущий победы».
Когда арена закрыта, арена попытается найти объект, стремящийся к победе, чтобы стать новый участник.

GestureBinding handleEvent (событие PointerEvent, запись HitTestEntry)

События навигации запускают handleEvent GestureRecouncer. Обычно PointerDownEvent не обрабатывается при выполнении маршрута.

жестАрена ЖестАренаМенеджер

Событие Down приводит к закрытию арены

 // 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 (указатель int)

Вызывается после завершения распределения событий, чтобы предотвратить участие новых участников в соревновании.

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

GestureArenaManager _tryToResolveArena (указатель int, состояние _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);
  }
}

GestureArenaManager развертка (указатель int)

Принуждение арены к розыгрышу победителя
обычно происходит после события up. Это гарантирует, что конкуренция не вызывает лагов, мешающих пользователям взаимодействовать с приложением.

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

BaseTapGestureRecouncer AcceptGesture (указатель int)

Чтобы отметить победу в соревновании жестов, вызовите _checkDown(). Если он был обработан, он не будет обработан снова. Если он не был обработан, будет вызван 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;
}

BaseTapGestureRecouncer _checkUp()

Если это не победитель или событие пустое, вернитесь,
в противном случае обработайте событие up и выполните сброс.

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

TapGestureRecouncer#handleTapUp({PointerDownEvent вниз, PointerUpEvent вверх})

Сначала выполните onTapUp, а затем onTap, чтобы завершить распознавание события щелчка.

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

Экономить деньги:

  • Событие передается из уровня Native в уровень Dart через C++ и обрабатывается в GestureBinding после сопоставления с логическими пикселями.
  • Все жесты начинаются снизу. На этапе спуска HitTest начинается с корневого узла, ответственного за рисование дерева, рекурсивно добавляет элементы управления, которые могут реагировать на события, в HitTestResult и, наконец, добавляет GesureBinidng в конец, чтобы распределить события по каждому объекту в результате. .
  • RawGestureDetector обработает событие. На позднем этапе участники будут добавлены в _GestureArena для участия в соревнованиях и, наконец, вернутся в GestureBinding, чтобы закрыть арену. Если участвует только один RawGestureDetector, он примет жест напрямую, но onTap все равно не будет После завершения события up Триггер onTap после запуска onTapup
  • Когда соревнуются несколько RawGestureDetectors, победителем выбирается первый из них.
  • Скольжение создает победителя на этапе хода.

рекомендация

отblog.csdn.net/weixin_51109304/article/details/131994361