Добавление маркера виджета на карту Mapbox во Flutter стало проще

Готовы ли вы добавить немного волшебства в свое приложение Flutter Mapbox? Сегодня мы собираемся погрузиться в мир маркеров виджетов.
Flutter сегодня — отличный инструмент для создания приложений.

Если вы не являетесь разработчиком Flutter, я предлагаю вам начать его сегодня. Перейдите в раздел Flutter Website 🫤.

В последние годы карты становятся все более популярными. От транспортных услуг до социальных развлечений — мы используем карту для улучшения нашей повседневной жизни. Поэтому нам нужно создать какое-то приложение, которое сделает работу пользователей более комфортной. 🎉

В этом уроке я буду использовать:

Полный код доступен здесь

Что такое маркер виджета?

Маркеры — это символы, обозначающие определенное место на карте. Но при работе с картой с помощью Flutter возможности настройки этих ребят очень ограничены.

Маркеры виджетов в Mapbox позволяют использовать виджеты Flutter в качестве маркеров на карте. Это означает, что вы можете добавлять собственные значки, изображения или даже интерактивные виджеты в качестве маркеров. Это как добавить индивидуальности вашим картам! И это потрясающе. используйте всю мощь виджетов Flutter прямо на карте 🤯🥵

Настраивать

Завершите настройку пакета mapbox_map_flutter и добавьте виджет карты в свое приложение.

Вы получите что-то вроде этого

import 'package:flutter/material.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';

class MapPage extends StatefulWidget {
  const MapPage({super.key});

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

class _MapPageState extends State<MapPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          MapWidget(
            onScrollListener: (coordinate) {},
            key: const ValueKey("mapBoxMap"),
            cameraOptions: CameraOptions(),
            onMapCreated: (controller) {},
            resourceOptions: ResourceOptions(
              accessToken: "PUT YOUR PUBLIC ACCESS TOKEN HERE",
            ),
          ),
        ],
      ),
    );
  }

У вас будет вот такой экран 🥲. Заметьте, ничего очень крутого!

Давайте добавим местоположение пользователя на карте

Для этого нам нужно определить MapController:

class _MapPageState extends State<MapPage> {
late MapboxMap _mapController; // Here we declare the mapController
//...

onMapCreated: (controller) {
   // after the map is created we retrieve the controller
  _mapController = controller;  
  // We tell the controller to show the user location on the map
  _mapController.location
  .updateSettings(LocationComponentSettings(enabled: true));
  },

  //...
}

теперь это должно выглядеть так; обратите внимание на синюю точку на карте. Это ваше местоположение

Теперь давайте начнем войну

Создайте файл с именем маркер.dart со следующим кодом.

маркер.дротик

import 'package:flutter/widgets.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
import 'package:mountain_map/app/services/latlng.dart';

class Marker extends StatefulWidget {
  // The widget position on the UI
  final ValueNotifier<ScreenCoordinate> screenPosition;

  // The Marker id
  final String id;

  // The marker geo data
  final LatLng geoCoordinate;

  // Your widget coming from your imagination feel free 😎
  final Widget child;
  Marker({
    super.key,
    required ScreenCoordinate position,
    required this.geoCoordinate,
    required this.child,
    required this.id,
  }) : screenPosition = ValueNotifier(position);

  @override
  MarkerState createState() => MarkerState();
}

class MarkerState extends State<Marker> {
  MarkerState();

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

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: widget.screenPosition,
      builder: (context, pos, child) {
        return Positioned(
          left: pos.x,
          top: pos.y,
          child: GestureDetector(
            onTap: () {},
            child: widget.child,
          ),
        );
      },
    );
  }
}  

Создайте виджет MapMarkers:
Этот виджет представляет собой стек, который отображает список виджетов-маркеров поверх карты.

В качестве параметров принимает список маркеров

class MapMarkers extends StatelessWidget {
  final List<Marker> markers;
  const MapMarkers({super.key, required this.markers});

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: markers.toList(),
    );
  }
}

теперь обновите MapPage, добавив виджет MapMarkers и набор маркеров 😁. Спроси меня, почему Сетт>. О структуре данных я расскажу позже.

class _MapPageState extends State<MapPage> {
  late MapboxMap _mapController;
  final markerList = <Marker>{};

@override
  Widget build(BuildContext context) {
//...
// add line after the MapWidget
  MapMarkers(
            markers: markerList.toList(),
          )
//..
 }
}

latlng.dart
Файл, содержащий класс для простой обработки данных широты и долготы

class LatLng {
  final double lat;
  final double lng;

  LatLng({
    required this.lat,
    required this.lng,
  });
}

geolocator.dart
Этот файл содержит функцию для получения данных о вашем текущем местоположении (широта, долгота).

import 'package:geolocator/geolocator.dart';
import 'package:mountain_map/app/services/latlng.dart';

Future<LatLng> getCurrentLatLng() async {
  final data = await Geolocator.getCurrentPosition();
  return LatLng(lat: data.latitude, lng: data.longitude);
}

Теперь в map.page.dart мы добавляем плавающую кнопку действия, чтобы добавить новый маркер на карту в каркасе.

floatingActionButton: FloatingActionButton(
        onPressed: () async {
      // We will add new marker widget logic here
        },
        child: const Icon(Icons.location_history),
      ),

Наша страница теперь будет выглядеть так. обратите внимание на новую плавающую кнопку

Чтобы добавить новый маркер, добавьте следующую функциюg в виджет MapPage.

Future<void> addMarker() async {
    // We get our current location data
    final myLocationData = await getCurrentLatLng();

    /* Important
     We ask the mapcontroller to give the screen coordinate based on
     our current location data (myLocationData) of type LatLng */
    final screenCoordinate = await _mapController.pixelForCoordinate(
        Point(coordinates: Position(myLocationData.lng, myLocationData.lat))
            .toJson());

    /* 
     We create the marker widget with the required data
    */
    final marker = Marker(
      position: screenCoordinate, // the screen position
      geoCoordinate: myLocationData, // the geospatial position
      id: "1", // the id (feel free to change)
      child: Image.asset(
        "assets/avatar.png",
        height: 24,
        width: 24,
      ), // My Widget I want to show on the map
    );

    // Add the new marker to list
    markerList.add(marker);

    // Trigger ui refresh
    setState(() {});
  }

и обновите метод плавающей кнопки

floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await addMarker(); // Our guy 😄
        },
        child: const Icon(Icons.location_history),
 ),

и это произойдет

Теперь у нас есть наш виджет на карте. но если вы начнете прокручивать карту, вы заметите, что виджет не обновляется. Благодаря Mapbox реализована функция, позволяющая предпринимать некоторые действия при смене камеры на карте. Итак, давайте исправим проблему:

Обновите MapWidget, добавив новое свойство.

   MapWidget(
     onCameraChangeListener: (cameraChangedEventData) {
     // The function to trigger every time we move the map
     updateMarkersPosition(); 
  },
// .. rest of the code 
)

Теперь давайте обновим функцию updateMarkersPosition().

  Future<void> updateMarkersPosition() async {
    // We check if any marker is present
    if (markerList.isNotEmpty) {
      for (var m in markerList) {
        /* For ever marker previously added
          we ask the mapboxcontroller to give the screenCoordinate corresponding to the geospatial coordinate
         */
        final screenCoordinate = await _mapController.pixelForCoordinate(Point(
                coordinates: Position(m.geoCoordinate.lng, m.geoCoordinate.lat))
            .toJson());
        // We update the new screen position
        m.screenPosition.value = screenCoordinate;
        // And finally we request a ui refresh
        setState(() {});
      }
    }
  }

И вуаля!! запустите приложение, и вы увидите волшебство. ваша карта готова.

Заключение

Вот оно! Теперь у вас есть все необходимое для добавления маркеров виджетов на карту Mapbox во Flutter. Это все равно, что придать вашей карте яркую индивидуальность, и возможности безграничны. Итак, давайте, добавляйте виджеты, добавляйте атиеке на свою карту и сделайте ее шумной 🤣!

Приятного кодирования!