← Назад к вопросам

Как работает GestureDetector во Flutter?

1.3 Junior🔥 172 комментариев
#Flutter виджеты

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

GestureDetector во Flutter

GestureDetector — это виджет, который отслеживает жесты пользователя (нажатия, свайпы, масштабирование и т.д.) и вызывает соответствующие колбэки. Это фундамент всей системы взаимодействия в Flutter.

Как работает GestureDetector?

GestureDetector перехватывает события касания (PointerEvent) от операционной системы и преобразует их в высокоуровневые жесты:

  1. PointerEvent (от ОС) → тач-координаты, давление, время
  2. GestureArena — система, которая определяет, какой жест произошел
  3. Жест (tap, swipe, drag и т.д.) → вызов колбэка

Основные типы жестов

1. Tap (нажатие)

GestureDetector(
  onTap: () => print(Один клик),
  onDoubleTap: () => print(Двойной клик),
  onLongPress: () => print(Долгое нажатие),
  child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
  ),
)

Особенность: onTap и onDoubleTap конкурируют между собой. Если жест может быть двойным — onTap задерживается на ~300ms.

2. Drag (движение)

GestureDetector(
  onPanDown: (details) {
    print(Начало движения: ${details.globalPosition});
  },
  onPanUpdate: (details) {
    print(Движение: ${details.delta});
  },
  onPanEnd: (details) {
    print(Конец движения: ${details.velocity});
  },
  onPanCancel: () => print(Отмена),
  child: Container(
    width: 100,
    height: 100,
    color: Colors.red,
  ),
)

Разница между методами:

  • onPanDown — палец коснулся
  • onPanUpdate — палец движется (вызывается множество раз)
  • onPanEnd — палец поднят
  • onPanCancel — жест прерван системой

3. Swipe (быстрое движение)

GestureDetector(
  onHorizontalDragEnd: (details) {
    // velocity.pixelsPerSecond — скорость свайпа
    if (details.velocity.pixelsPerSecond.dx > 500) {
      print(Свайп вправо);
    } else if (details.velocity.pixelsPerSecond.dx < -500) {
      print(Свайп влево);
    }
  },
  child: Container(
    width: 200,
    height: 100,
    color: Colors.green,
  ),
)

4. Scale (масштабирование)

GestureDetector(
  onScaleStart: (details) {
    print(Начало масштабирования);
  },
  onScaleUpdate: (details) {
    print(Масштаб: ${details.scale});
    // Используй для изменения размера виджета
  },
  onScaleEnd: (details) {
    print(Конец масштабирования);
  },
  child: Transform.scale(
    scale: _scale,
    child: Image.asset(image.jpg),
  ),
)

Практический пример: Драгируемая карточка

class DraggableCard extends StatefulWidget {
  @override
  State<DraggableCard> createState() => _DraggableCardState();
}

class _DraggableCardState extends State<DraggableCard> {
  double _x = 0;
  double _y = 0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          _x += details.delta.dx;
          _y += details.delta.dy;
        });
      },
      onPanEnd: (details) {
        // Можно добавить инерцию (fling)
        print(Скорость: ${details.velocity.pixelsPerSecond});
      },
      child: Transform.translate(
        offset: Offset(_x, _y),
        child: Card(
          child: Padding(
            padding: EdgeInsets.all(16),
            child: Text(Перетащи меня!),
          ),
        ),
      ),
    );
  }
}

Gesture Arena — система разрешения жестов

Когда несколько GestureDetector-ов накладываются, система Gesture Arena решает, кому обработать жест:

Stack(
  children: [
    GestureDetector(
      onTap: () => print(Внешний),
      child: Container(
        width: 200,
        height: 200,
        color: Colors.blue,
        child: GestureDetector(
          onTap: () => print(Внутренний),
          child: Container(
            width: 100,
            height: 100,
            color: Colors.red,
          ),
        ),
      ),
    ),
  ],
)
// Результат: "Внутренний" (более специфичный жест побеждает)

Параметры GestureDetector

GestureDetector(
  // Нажатия
  onTap: () {},
  onDoubleTap: () {},
  onLongPress: () {},
  
  // Движение вертикальное/горизонтальное
  onVerticalDragDown: (details) {},
  onVerticalDragUpdate: (details) {},
  onVerticalDragEnd: (details) {},
  
  onHorizontalDragDown: (details) {},
  onHorizontalDragUpdate: (details) {},
  onHorizontalDragEnd: (details) {},
  
  // Общее движение
  onPanDown: (details) {},
  onPanUpdate: (details) {},
  onPanEnd: (details) {},
  
  // Масштабирование
  onScaleStart: (details) {},
  onScaleUpdate: (details) {},
  onScaleEnd: (details) {},
  
  // Настройки
  behavior: HitTestBehavior.opaque, // область обнаружения
  dragStartBehavior: DragStartBehavior.start, // когда начинать drag
  
  child: Widget(),
)

HitTestBehavior — область обнаружения

// Только на видимом контенте
GestureDetector(
  behavior: HitTestBehavior.deferToChild,
  onTap: () {},
  child: Text(Нажми на текст),
)

// На весь контейнер, даже если пусто
GestureDetector(
  behavior: HitTestBehavior.opaque,
  onTap: () {},
  child: Container(
    width: 100,
    height: 100,
    // Видимый контент может быть меньше!
  ),
)

// Стандартное поведение
GestureDetector(
  behavior: HitTestBehavior.translucent,
  onTap: () {},
  child: Container(),
)

Проблемы и решения

Проблема 1: onTap не срабатывает

// ❌ Неправильно — Container без размера
GestureDetector(
  onTap: () {},
  child: Container(),
)

// ✅ Правильно — явно задайте размер
GestureDetector(
  onTap: () {},
  child: Container(width: 100, height: 100),
)

// ✅ Или используйте opaque
GestureDetector(
  onTap: () {},
  behavior: HitTestBehavior.opaque,
  child: Container(),
)

Проблема 2: Конфликт между onTap и onDoubleTap

// ⚠️ onTap вызовется с задержкой в ~300ms
GestureDetector(
  onTap: () => print(Один клик),
  onDoubleTap: () => print(Двойной клик),
  child: Container(),
)

// Если не нужен двойной клик — удали onDoubleTap

Альтернативы GestureDetector

Для простых случаев используй готовые виджеты:

// Вместо GestureDetector + Container
InkWell(
  onTap: () {},
  child: Text(Нажми),
)

// Для кнопок
ElevatedButton(
  onPressed: () {},
  child: Text(Кнопка),
)

// Для слайдера
Slider(
  value: _value,
  onChanged: (value) {},
)

Сравнение: GestureDetector vs InkWell

ФункцияGestureDetectorInkWell
ЖестыВсеОсновные (tap, long press)
Визуальный эффектНетRipple эффект
ПростотаНизкаяВысокая
УправлениеПолнаяСтандартная

Вывод: GestureDetector — это основной инструмент для обработки жестов. Используй для сложных взаимодействий, а для простых случаев предпочитай InkWell и готовые виджеты.