Для своего проекта G2048 мне потребовалось сделать чтобы плитки двигались по свайпам. И я очень удивился когда не нашел такой штуки в GestureDetector... Это что свайпы никому не нужны что ли?
Единственная более или менее подходящий виджет это Dismissible, но в нём есть свайпы только по горизонтали, а мне надо еще и по вертикали.
На стековерфлоу рекомендуют делать своё, через velocity... Кстати, какой-то странный параметр - скорость движения пальца. На pub.dev на тот момент я нашел всего один пакет, который почему-то не внушил мне доверия (сейчас глянул там раз, два, три - в общем полно вариантов, каждый изгаляется как может) и я решил делать сам (велосипедостроение я не рекомендую, только если в обучающих целях).
И так - начнем.
Подготавливаем тестовый проект
Тестовый проект будет выглядеть вот так:
В середине контейнер, который должен реагировать на свайпы - текст внутри будет меняться в зависимости от того в какую сторону мы свайпнули.
Вот тут можно посмотреть исходный код: https://github.com/devmaslove/flutter_swipes_detector_example
Вариант №1 - нерабочее решение в лоб
Решаем задачу в лоб. Делаем GestureDetector и перегружаем у него onVerticalDragEnd и onHorizontalDragEnd. Смотрим velocity если меньше нуля, то вверх, если ниже нуля, то вниз... Запускаем...
Работает! Круто! Только вот ложных срабатываний дофига... И чего-то показывает не в ту сторону через раз... И главное больше и зацепиться не за что, есть только одно velocity.
В таком исполнении нам можно использовать параметр primatyVelocity и он там точно не нул, потому что мы используем не onPanEnd, а жесты в конкретных направлениях. Для нашего варианта это довольно удобно, чтобы не высчитывать самим направление свайпа, будем использовать его и дальше.
Вариант №2 - решение через смещение
Давайте править багу с направлением свайпа. Решение на поверхности - нужно считать смещение. Если будет минус, то влево, если плюс то вправо. А на знак velocity внимание обращать не будем, да и вообще он нам не нужен. Правда нам придется хранить промежуточные состояния, по этому переделываем всё под StatefullWidget.
Теперь будем следить за изменениями. При конце свайпа смотрим расстояние между начальной и конечными точками, берем смещение по главной оси и по нему смотрим в какую сторону свайп.
Вот это уже чётенько работает! Теперь осталось избавиться от ложных срабатываний - когда медленно введем по экрану, когда палец на пол миллиметра сдвинули и когда по диагонали пальцами елозим.
Вариант №3 - со всеми проверками, конечный
Первое, что мы видим - это одинаковый код на DragEnd, давайте вынесем его отдельно. Всё что нужно туда сразу кинем на вход, чтобы внутри него уже делать все проверки:
Передаём в функцию горизонтальный это драг или вертикальный, в зависимости от этого будем брать смещение по dx или по dy. Также передаём калбеки, которые нужно вызвать при положительном или негативном смещении - у нас четыре варианта: Влево-Вправо и Вниз-Вверх.
Соответствующим образом переделываем GesterDetector, к нему возвращаться больше не будем:
Начнем с того, что на поверхности - на сколько нужно сдвинуть палец в нужном направлении, чтобы засчиталось как свайп. Поставим 50 пикселей (а почему бы и нет). У нас есть знак, по этому сравниваем значение по модулю.
Отлично! Теперь поправим, что нельзя елозить пальцем перпендикулярно, тоже поставим 50 пикселей. Тут всё тоже просто - берем secondaryOffset.
И теперь добавим проверку на скорость - velocity. Чтобы слишком медленное движение пальцем по экрану не засчитывалось. Поставим, как вы уже наверное догадались 50 попугаев (пискелей в секунду?)
И на этом этапе возникла проблема... Периодически velocity прилетает равное 0.0, толи эмулятор чудит, толи бага какая-то, толи я как-то не верно осознаю этот параметр...
Исправляем GestureDetector velocity = 0.0
На самом деле он достаточно не хитро считается - надо смещение разделить на время за которое она сделано. К счастью время записывается в DragStart и DragUpdate, а значит у нас есть всё, чтобы рассчитать скорость.
Теперь у нас по-любому будет velocity! Добавляем к нам в проверку.
Всё! Наш свайп детектор готов! Вот такой получился стейт:
Итоги
Мы сделали свайп детектор свой. Конечно можно дальше причесать - вынести все волшебные числа как входящие параметры (помните числа 50?), сделать также передачу GestureDetector behavior (обязательно прокидывать). Это вам в качестве домашнего задания :)
Исходный код результата можно посмотреть тут: