Как центрировать камеру 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 применяются неконсистентно:
- Начальный расчёт макета: виджет Google Maps рассчитывает доступное пространство на основе текущего дерева виджетов.
- Интеграция с нативным SDK: нативный SDK Google Maps определяет свой видимый регион на основе этого расчёта.
- Изменения в Scaffold: при изменении AppBar, bottomNavigationBar или настроек extendBody доступное пространство смещается.
- Несоответствие позиции камеры: координаты цели камеры остаются прежними, но визуальное представление смещается из‑за изменения расчёта видимой области.
Усиление при холодном старте
Проблема более выражена при холодном старте, потому что:
- Виджет карты может инициализироваться до завершения всех расчётов макета.
- Нативный SDK может не иметь правильной информации о видимой области во время начального рендеринга.
- Различия во времени инициализации на платформах (iOS vs Android).
Эффективные решения
Решение 1: Корректная конфигурация Scaffold
Самый надёжный способ — поддерживать консистентную конфигурацию Scaffold, при этом используя extendBody: true:
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:
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, чтобы гарантировать правильный расчёт пространства:
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: Пересоздание позиции камеры с задержкой
Добавьте небольшую задержку, чтобы убедиться в полной инициализации:
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: более склонен к смещению позиции во время инициализации.
Платформенные настройки
Рассмотрите добавление платформенно‑специфической обработки:
void _centerCamera() {
if (_mapController == null) return;
if (Platform.isIOS) {
_mapController!.moveCamera(
CameraUpdate.newCameraPosition(_getCurrentPosition()),
);
} else {
// Android может потребовать другую обработку
_mapController!.animateCamera(
CameraUpdate.newCameraPosition(_getCurrentPosition()),
);
}
}
Лучшие практики надёжной реализации
1. Консистентная инициализация
Всегда обеспечивайте правильное время инициализации:
@override
void initState() {
super.initState();
_setupMapController();
}
void _setupMapController() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_ensureMapCentering();
});
}
void _ensureMapCentering() {
if (_mapController != null && mounted) {
_centerCamera();
}
}
2. Обработка изменений конфигурации
Обеспечьте надёжную обработку изменений конфигурации:
@override
void didChangeDependencies() {
super.didChangeDependencies();
_ensureMapCentering();
}
@override
void didUpdateWidget(MyMap oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.center != widget.center) {
_centerCamera();
}
}
3. Мониторинг позиции камеры
Добавьте мониторинг позиции камеры для отладки:
void _onCameraMove(CameraPosition position) {
setState(() {
_currentPosition = position;
});
// Логирование для отладки
print('Camera moved to: ${position.target}');
}
// В виджете GoogleMap:
onCameraMove: _onCameraMove,
4. Использование SafeArea для корректного расчёта макета
Рассмотрите SafeArea для корректного расчёта макета:
Scaffold(
// ... другие свойства
body: SafeArea(
child: GoogleMap(
// ... свойства карты
),
),
)
Вывод
Проблема центрирования камеры Google Maps в Flutter в основном вызвана конфликтами расчёта макета между SDK Google Maps и компонентами Scaffold. Ключевые решения:
- Поддерживать консистентную конфигурацию Scaffold с
extendBody: true, когда это необходимо. - Реализовать надёжное время инициализации с помощью
WidgetsBinding.addPostFrameCallback. - Добавить логику пересоздания позиции камеры, которая запускается после завершения расчётов макета.
- Учесть различия платформ между iOS и Android.
- Мониторить позицию камеры для отладки и проверки.
Для вашего конкретного случая, когда требуется extendBody: true для UI/UX, наиболее эффективный подход — сочетать правильную конфигурацию Scaffold с задержкой пересоздания камеры. Это гарантирует, что карта получит правильную информацию о видимой области до позиционирования камеры, предотвращая смещение в левый‑верхний угол как при холодном старте, так и при обычной навигации.
Не забывайте тщательно тестировать как на iOS, так и на Android, поскольку они обрабатывают позиционирование карты по‑разному и могут потребовать платформенно‑специфических настроек для согласованного поведения на всех устройствах.
Источники
- Google Maps Flutter camera center appears in top-left corner - Stack Overflow
- Can’t Keep Camera In Center When Rotating - Flutter GitHub Issue #37384
- Google maps camera position is moved after widget built - Flutter GitHub Issue #152805
- Google Map’s camera is not positioned correctly at initialization - Flutter GitHub Issue #27550
- Google maps camera position is wrong in Android - Flutter GitHub Issue #40671
- Scaffold extendBody property - Flutter Documentation
- Adding Google Maps to a Flutter app - Google Codelabs