основная структура
Если взять 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, победителем выбирается первый из них.
- Скольжение создает победителя на этапе хода.