Другое

Как центрировать камеру Google Maps в Flutter с Scaffold

Решите проблему центрирования камеры Google Maps в Flutter с Scaffold, AppBar и bottomNavigationBar. Узнайте правильное время и настройки инициализации для позиции карты.

Google Maps Flutter: центр камеры смещается в левый верхний угол в зависимости от конфигурации Scaffold (extendBody / AppBar / bottomNavigationBar)

Я создаю пользовательскую страницу карты в Flutter, используя пакет google_maps_flutter (и обёртку‑виджет из google_maps_drawing_tools). У меня возникла проблема с расположением: цель камеры не визуально центрирована на карте. Проблема существовала ещё до добавления google_maps_drawing_tools.

Центр карты смещается к левому верхнему углу экрана, хотя CameraPosition.target задан правильно.

Проблема проявляется, если отсутствует один из следующих условий:

  • extendBody: false
  • присутствует AppBar
  • присутствует bottomNavigationBar

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

Я пробовал несколько решений без успеха:

  • Задержка операций с картой после onMapCreated
  • Future.delayed(...) перед логикой переназначения центра
  • WidgetsBinding.instance.addPostFrameCallback
  • Проверка controller.getVisibleRegion() после onMapCreated
  • Обёртывание карты в LayoutBuilder
  • Переключение extendBody/extendBodyBehindAppBar, resizeToAvoidBottomInset
  • Добавление/удаление отступов
  • Тестирование с дополнительными оверлеями против упрощённой версии

Для моего UI/UX важно иметь extendBody: true, чтобы карта отображалась за AppBar и BottomBar.

Как исправить проблему центрирования камеры Google Maps в Flutter при разных конфигурациях Scaffold?

Проблема центрирования камеры Google Maps в Flutter возникает, когда расчёт доступного пространства виджета карты конфликтует с компонентами макета Scaffold. Это происходит из‑за того, что SDK Google Maps затрудняется определить истинный видимый регион, когда AppBar, bottomNavigationBar или настройки extendBody применяются неконсистентно. Решение заключается в обеспечении правильного времени инициализации и использовании корректной конфигурации Scaffold, чтобы сохранить визуальное центрирование в разных состояниях.

Contents

Понимание проблемы центрирования

Проблема центрирования камеры проявляется, когда виджет Google Maps смещается к левому‑верхнему углу экрана, несмотря на правильную позицию CameraPosition. Согласно обсуждениям на Stack Overflow, это происходит, когда любой из следующих компонентов Scaffold отсутствует или настроен неконсистентно:

  • extendBody: false (если вам нужно true для UI/UX)
  • Отсутствует AppBar
  • Отсутствует bottomNavigationBar

Проблема особенно заметна при холодном старте и при первом инициализации карты. Когда все три компонента настроены корректно, камера обычно центрируется правильно, но это создаёт конфликт UI/UX, если вам действительно нужно extendBody: true, чтобы карта отображалась за навигационными панелями.

Ключевой вывод: проблема не в координатах CameraPosition, а в том, как SDK Google Maps рассчитывает и отображает видимую область относительно доступного пространства экрана.

Анализ причины

Причина кроется в взаимодействии системы компоновки Flutter и нативного SDK Google Maps. Как объясняется в GitHub issue #37384, iOS и Android обрабатывают жесты прокрутки и позиционирование камеры по‑разному, что усугубляет проблему.

Конфликты расчёта макета

Когда компоненты Scaffold применяются неконсистентно:

  1. Начальный расчёт макета: виджет Google Maps рассчитывает доступное пространство на основе текущего дерева виджетов.
  2. Интеграция с нативным SDK: нативный SDK Google Maps определяет свой видимый регион на основе этого расчёта.
  3. Изменения в Scaffold: при изменении AppBar, bottomNavigationBar или настроек extendBody доступное пространство смещается.
  4. Несоответствие позиции камеры: координаты цели камеры остаются прежними, но визуальное представление смещается из‑за изменения расчёта видимой области.

Усиление при холодном старте

Проблема более выражена при холодном старте, потому что:

  • Виджет карты может инициализироваться до завершения всех расчётов макета.
  • Нативный SDK может не иметь правильной информации о видимой области во время начального рендеринга.
  • Различия во времени инициализации на платформах (iOS vs Android).

Эффективные решения

Решение 1: Корректная конфигурация Scaffold

Самый надёжный способ — поддерживать консистентную конфигурацию Scaffold, при этом используя extendBody: true:

dart
Scaffold(
  appBar: AppBar(
    title: Text('Map'),
    elevation: 2,
  ),
  body: GoogleMap(
    // Your map configuration
  ),
  bottomNavigationBar: BottomNavigationBar(
    items: [...],
    currentIndex: 0,
  ),
  extendBody: true, // Это ключевой параметр для вашего UI/UX
  extendBodyBehindAppBar: true, // Опционально, если хотите карту за AppBar
)

Решение 2: Пересоздание позиции камеры

Реализуйте надёжный механизм пересоздания позиции камеры с помощью WidgetsBinding:

dart
class _MyMapState extends State<MyMap> {
  GoogleMapController? _mapController;
  final LatLng _center = LatLng(45.521563, -122.677433);
  
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _centerCamera();
    });
  }
  
  void _centerCamera() {
    if (_mapController != null) {
      _mapController!.moveCamera(
        CameraUpdate.newCameraPosition(
          CameraPosition(
            target: _center,
            zoom: 13.0,
          ),
        ),
      );
    }
  }
  
  void _onMapCreated(GoogleMapController controller) {
    _mapController = controller;
    _centerCamera();
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),
      bottomNavigationBar: BottomNavigationBar(...),
      extendBody: true,
      body: GoogleMap(
        onMapCreated: _onMapCreated,
        initialCameraPosition: CameraPosition(
          target: _center,
          zoom: 13.0,
        ),
        // other map properties
      ),
    );
  }
}

Решение 3: Подход с LayoutBuilder

Используйте LayoutBuilder, чтобы гарантировать правильный расчёт пространства:

dart
Scaffold(
  appBar: AppBar(...),
  bottomNavigationBar: BottomNavigationBar(...),
  extendBody: true,
  body: LayoutBuilder(
    builder: (context, constraints) {
      return GoogleMap(
        onMapCreated: _onMapCreated,
        initialCameraPosition: CameraPosition(
          target: _center,
          zoom: 13.0,
        ),
        // other map properties
      );
    },
  ),
)

Решение 4: Пересоздание позиции камеры с задержкой

Добавьте небольшую задержку, чтобы убедиться в полной инициализации:

dart
void _onMapCreated(GoogleMapController controller) {
  _mapController = controller;
  
  // Небольшая задержка, чтобы убедиться, что макет полностью рассчитан
  Future.delayed(const Duration(milliseconds: 100), () {
    if (mounted) {
      _centerCamera();
    }
  });
}

Учет платформенных особенностей

Различия между iOS и Android

Как отмечено в GitHub issue #37384, iOS и Android ведут себя по‑разному:

  • iOS: обычно обрабатывает позиционирование камеры более последовательно, когда scrollGesturesEnabled: false.
  • Android: более склонен к смещению позиции во время инициализации.

Платформенные настройки

Рассмотрите добавление платформенно‑специфической обработки:

dart
void _centerCamera() {
  if (_mapController == null) return;
  
  if (Platform.isIOS) {
    _mapController!.moveCamera(
      CameraUpdate.newCameraPosition(_getCurrentPosition()),
    );
  } else {
    // Android может потребовать другую обработку
    _mapController!.animateCamera(
      CameraUpdate.newCameraPosition(_getCurrentPosition()),
    );
  }
}

Лучшие практики надёжной реализации

1. Консистентная инициализация

Всегда обеспечивайте правильное время инициализации:

dart
@override
void initState() {
  super.initState();
  _setupMapController();
}

void _setupMapController() {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    _ensureMapCentering();
  });
}

void _ensureMapCentering() {
  if (_mapController != null && mounted) {
    _centerCamera();
  }
}

2. Обработка изменений конфигурации

Обеспечьте надёжную обработку изменений конфигурации:

dart
@override
void didChangeDependencies() {
  super.didChangeDependencies();
  _ensureMapCentering();
}

@override
void didUpdateWidget(MyMap oldWidget) {
  super.didUpdateWidget(oldWidget);
  if (oldWidget.center != widget.center) {
    _centerCamera();
  }
}

3. Мониторинг позиции камеры

Добавьте мониторинг позиции камеры для отладки:

dart
void _onCameraMove(CameraPosition position) {
  setState(() {
    _currentPosition = position;
  });
  
  // Логирование для отладки
  print('Camera moved to: ${position.target}');
}

// В виджете GoogleMap:
onCameraMove: _onCameraMove,

4. Использование SafeArea для корректного расчёта макета

Рассмотрите SafeArea для корректного расчёта макета:

dart
Scaffold(
  // ... другие свойства
  body: SafeArea(
    child: GoogleMap(
      // ... свойства карты
    ),
  ),
)

Вывод

Проблема центрирования камеры Google Maps в Flutter в основном вызвана конфликтами расчёта макета между SDK Google Maps и компонентами Scaffold. Ключевые решения:

  1. Поддерживать консистентную конфигурацию Scaffold с extendBody: true, когда это необходимо.
  2. Реализовать надёжное время инициализации с помощью WidgetsBinding.addPostFrameCallback.
  3. Добавить логику пересоздания позиции камеры, которая запускается после завершения расчётов макета.
  4. Учесть различия платформ между iOS и Android.
  5. Мониторить позицию камеры для отладки и проверки.

Для вашего конкретного случая, когда требуется extendBody: true для UI/UX, наиболее эффективный подход — сочетать правильную конфигурацию Scaffold с задержкой пересоздания камеры. Это гарантирует, что карта получит правильную информацию о видимой области до позиционирования камеры, предотвращая смещение в левый‑верхний угол как при холодном старте, так и при обычной навигации.

Не забывайте тщательно тестировать как на iOS, так и на Android, поскольку они обрабатывают позиционирование карты по‑разному и могут потребовать платформенно‑специфических настроек для согласованного поведения на всех устройствах.

Источники

  1. Google Maps Flutter camera center appears in top-left corner - Stack Overflow
  2. Can’t Keep Camera In Center When Rotating - Flutter GitHub Issue #37384
  3. Google maps camera position is moved after widget built - Flutter GitHub Issue #152805
  4. Google Map’s camera is not positioned correctly at initialization - Flutter GitHub Issue #27550
  5. Google maps camera position is wrong in Android - Flutter GitHub Issue #40671
  6. Scaffold extendBody property - Flutter Documentation
  7. Adding Google Maps to a Flutter app - Google Codelabs
Авторы
Проверено модерацией
Модерация