TGViewer
Flutter Friendly Flutter Friendly @flutterfriendly · 996 subscribers
Post #189 913
Привет, это Роза, Flutter Dev Friflex👋

Лучше всего мы учимся на практике, поэтому сегодня создадим окно фильтра, которое будет появляться рядом с кнопкой при нажатии.

Конечно, можно использовать готовые решения — например, PopupMenuButton с кастомным PopupMenuEntry или диалог. Но если вам нужно больше контроля над позиционированием и поведением окна, лучше реализовать его вручную.

Для этого используем три компонента:
1️⃣ Overlay — для отрисовки поверх текущего интерфейса
2️⃣ CompositedTransformTarget — якорь для привязки
3️⃣ CompositedTransformFollower — виджет, который будет следовать за якорем

Немного теории
Overlay — специальный стек, поверх которого можно отрисовывать виджеты вне основного дерева. Элементы добавляются в Overlay через OverlayEntry.

Чтобы связать два виджета в пространстве, используем:
✔️ CompositedTransformTarget — задает якорь
✔️ CompositedTransformFollower — следует за якорем, даже если его позиция меняется

CompositedTransformTarget(
  link: _layerLink,
  child: ..., // ваш виджет
)


CompositedTransformFollower(
  link: _layerLink, 
  offset: Offset.zero,
  targetAnchor: Alignment.bottomLeft,
  followerAnchor: Alignment.topLeft,
  child: ..., // всплывающее окно
)


Где:
🔵 link — объект LayerLink, соединяющий Target и Follower
🔵 showWhenUnlinked — показывает Follower, если связь потеряна
🔵 offset — смещение относительно якоря
🔵 targetAnchor и followerAnchor — точки привязки

Реализация
Для начала создадим StatefulWidget для управления окном. В State определим:
➡️ LayerLink — для связи кнопки и окна
➡️ OverlayEntry — элемент, который добавим в Overlay
➡️ OverlayState — текущий Overlay из контекста

    final LayerLink _layerLink = LayerLink();
OverlayEntry? _overlayEntry;
OverlayState? _overlayState;


В методе build оборачиваем кнопку в CompositedTransformTarget:

@override
Widget build(BuildContext context) {
  return CompositedTransformTarget(
    link: _layerLink,
    child: LocalizationFilledIconButton(
      icon: widget.icon ?? const FilterIcon(),
      onPressed: _toggleDialog,
    ),
  );
}


Добавим логику открытия/закрытия:

void _toggleDialog() {
  // Если оверлей ещё не создан – создаем его
  if (_overlayEntry == null) {
    _overlayEntry = _createOverlayEntry(); // Создаём OverlayEntry
    _overlayState = Overlay.of(context);   // Получаем текущее состояние Overlay
    _overlayState?.insert(_overlayEntry!); // Вставляем OverlayEntry в Overlay
  } else {
    // Если оверлей уже открыт – удаляем его
    _overlayEntry?.remove(); // Удаляем OverlayEntry из Overlay
    _overlayEntry = null;    // Обнуляем ссылку на OverlayEntry
  }
}


Создаем само окно через CompositedTransformFollower:

OverlayEntry _createOverlayEntry() {
  return OverlayEntry(
    builder: (context) {
      return GestureDetector(
        behavior: HitTestBehavior.translucent, // Позволяет "ловить" тап вне окна
        onTap: _toggleDialog, // Закрываем окно при тапе вне его области
        child: Stack(
          children: [
            Positioned(
              width: MediaQuery.sizeOf(context).width / 3, // Ширина окна – треть экрана
              child: CompositedTransformFollower(
                link: _layerLink, // Связь с CompositedTransformTarget
                showWhenUnlinked: false, // Скрываем окно, если связь потеряна
                targetAnchor: Alignment.bottomLeft, // Привязываемся к нижнему левому углу кнопки
                child: FilterDialog(toggleDialog: _toggleDialog), // Само окно фильтра
              ),
            ),
          ],
        ),
      );
    },
  );
}


Не забываем удалить OverlayEntry при закрытии:

@override
void dispose() {
  _overlayEntry?.remove();
  super.dispose();
}


Готово!
Теперь у нас есть кастомное окно с фильтрами, которое:
✔️ Открывается по нажатию на кнопку
✔️ Позиционируется рядом с ней
✔️ Закрывается при клике вне области

❤️ — если было полезно
  • ❤ 12
  • 👍 7
  • 🔥 5
More from @flutterfriendly
  1. May 14, 2026👀Какой сложный вопрос или тема по Flutter вас сейчас беспокоят? Может, задача не идет, ба…
  2. Apr 17, 2026💭Привет! Это Роза, Flutter-разработчица Friflex! Уверена, многие из вас знакомы с Dart De…
  3. Apr 8, 2026Привет, друзья! Делимся нашей страничкой на Хабре, чтобы всегда оставаться на связи. Там е…
  4. Apr 3, 2026🌸Апрель в календаре и на экране Весна зовет обновлять визуальное: убирать темные темы и в…
  5. Apr 1, 2026Всегда с нетерпением ждем этого дня, чтобы сделать подборку ИТ-мемов Пусть поводов для улы…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →