Введение

Понадобилась мне как-то карта во флаттер-приложении. Гугл и Яндекс карты использовать не хотелось и оставалось только воспользоваться OSM. Карту сделать довольно просто, но и понадобилось добавить всплывающее окно при нажатии на маркер положения на карте. Перед тем как писать что-то самостоятельно решил поискать уже готовые решения и нашел плагин flutter_map_marker_popup.

Смотрим плагин

Зависимости которые потребуются:

dependencies:
  flutter:
    sdk: flutter
  flutter_map: any
  latlong2: any
  flutter_map_marker_popup: any

Для начала добавим карту Flutter_map. Из важного тут - urlTemplate, который указывает на сервер OSM. Настройки в MapOptions передадим извне.

class MapPage extends StatefulWidget {
  MapPage({super.key, required this.center, double? zoom}){
    this.zoom = zoom ?? 9.0;
  }

  final LatLng center;
  late final double zoom;

  @override
  State<MapPage> createState() => _MapPageState();
}

class _MapPageState extends State<MapPage> {

  final urlTemplate = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Map page"),
      ),
      body: FlutterMap(
        mapController: MapController(),
        options: MapOptions(
          center: widget.center,
          zoom: widget.zoom,
        ),
        children: [
          TileLayer(
            urlTemplate: urlTemplate,
          ),
        ],
      ),
    );
  }
}

Дальше будем, например по долгому нажатию, создавать маркер на карте. Напишем функцию которая будет добавлять новый маркер в массив маркеров и передадим ее в MapOptions onLongPress: addMarker.

 final List<Marker> _markers = [];

  addMarker(tapPosition, point){
    _markers.add(Marker(
      point: point,
      builder: (c) => const Icon(Icons.location_on, size: 40),
      width: 40,
      height: 40,));
  }

Теперь эти маркеры можно стандартно отобразить с помощью слоя MarkerLayer(markers: _markers), но тогда не получится отслеживать нажатие по ним и отображать что-либо. Для этих задач в плагине flutter_map_marker_popup есть PopupMarkerLayerWidget. Добавляем этот слой:

PopupMarkerLayerWidget(
  options: PopupMarkerLayerOptions(
    popupController: _popupLayerController,
    markers: _markers,
    markerRotateAlignment:
    PopupMarkerLayerOptions.rotationAlignmentFor(AnchorAlign.top),
    popupBuilder: (BuildContext context, Marker marker) =>
        ExamplePopup(marker),
  ),
),

В этот слой передаются маркеры, контроллер, и способ создания попапа. ExamplePopup - это виджет который будет появляться при нажатии на маркер. В нем и будут правила отображения всплывающего окна.

class ExamplePopup extends StatefulWidget {
  final Marker marker;

  const ExamplePopup(this.marker, {Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _ExamplePopupState();
}

class _ExamplePopupState extends State<ExamplePopup> {
  int _currentIcon = 0;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: InkWell(
        onTap: () => setState(() {
          _currentIcon = (_currentIcon + 1) % 4;
        }),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(left: 20, right: 10),
              child: Image.asset('assets/${_currentIcon+1}.png', width: 40, height: 40,),
            ),
            _cardDescription(context),
          ],
        ),
      ),
    );
  }

  Widget _cardDescription(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Container(
        constraints: const BoxConstraints(minWidth: 100, maxWidth: 200),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisAlignment: MainAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            const Text(
              'Popup for a marker!',
              overflow: TextOverflow.fade,
              softWrap: false,
              style: TextStyle(
                fontWeight: FontWeight.w500,
                fontSize: 14.0,
              ),
            ),
            const Padding(padding: EdgeInsets.symmetric(vertical: 4.0)),
            Text(
              'Position: ${widget.marker.point.latitude}, ${widget.marker.point.longitude}',
              style: const TextStyle(fontSize: 12.0),
            ),
            Text(
              'Marker size: ${widget.marker.width}, ${widget.marker.height}',
              style: const TextStyle(fontSize: 12.0),
            ),
          ],
        ),
      ),
    );
  }
}

Примечание, для получения картинок из папки assets надо в pubspec.yml добавить настройку:

flutter:
  assets:
    - assets/

И вот у нас по нажатию на маркер всплывает окно с котиками.

Всем спасибо! Если интересно, есть телеграмм, заходите присаживайтесь).

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


  1. Chelidonium
    22.09.2022 14:43

    а для десктопных ещё один момент удобен и важен,
    например скажем, - на карте с web-sdr чтобы жамкая
    на ссылку во всплывшем окне получать переход
    на новую страницу, иначе юзеру жать Ctrl каждый раз )