Xamarin Forms map: настройка закрепления карты

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

Каждое представление Xamarin.Forms имеет сопровождающий модуль отрисовки для каждой платформы, который создает экземпляр собственного элемента управления. Когда Map отрисовывается приложением Xamarin.Forms в iOS, создается Map Renderer экземпляр класса, который, в свою очередь, создает экземпляр собственного элемента MKMapView управления. На платформе Android MapRenderer класс создает собственный MapView элемент управления. На универсальной платформе Windows (UWP) MapRenderer класс создает собственный экземпляр MapControl.

Следующая диаграмма иллюстрирует взаимосвязь между Xamarin Maps и соответствующими собственными элементами управления, которые его реализуют:

Процесс рендеринга можно использовать для реализации настроек для конкретной платформы путем создания настраиваемого модуля рендеринга для Map каждой платформы. Процесс для этого выглядит следующим образом:

  1. Создайте пользовательскую карту Xamarin.Forms.
  2. Используйте пользовательскую карту из Xamarin.Forms .
  3. Создайте собственный рендерер для карты на каждой платформе.

Теперь каждый элемент будет обсуждаться по очереди, чтобы реализовать средство Custom Map визуализации, отображающее собственную карту с настроенным булавкой и настраиваемым представлением данных булавки на каждой платформе.

Xamarin.Forms. Maps должны быть инициализированы и настроены перед использованием. Для получения дополнительной информации см Maps Control.

Создание пользовательской карты

Пользовательский элемент управления картой можно создать путем создания подкласса Map класса, как показано в следующем примере кода:

public class CustomMap : Map
{
    public List CustomPins { get; set; }
}

Элемент Custom Map управления создается в проекте библиотеки .NET Standard и определяет API для пользовательской карты. Настраиваемая карта предоставляет CustomPins свойство, представляющее набор CustomPin объектов, которые будут отображаться с помощью собственного элемента управления картой на каждой платформе. Класс CustomPin показан в следующем примере кода:

public class CustomPin : Pin
{
   public string Name { get; set; }
   public string Url { get; set; }
}

Этот класс определяет CustomPin как наследующий свойства Pin класса и добавляющий Name и Url свойства.

Использование пользовательской карты

На Custom Map элемент управления можно ссылаться в XAML в проекте библиотеки .NET Standard, объявив пространство имен для его местоположения и используя префикс пространства имен в пользовательском элементе управления картой. В следующем примере кода показано, как Custom Map элемент управления может использоваться страницей XAML:

<contentpage ...xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer">
	<local:custommap x:name="customMap" maptype="Street"></local:custommap>
</contentpage>

Префикс local пространства имен может быть любым. Однако значения clr-name space и assembly должны соответствовать деталям пользовательской карты. После объявления пространства имен префикс используется для ссылки на пользовательскую карту.

В следующем примере кода показано, как Custom Map элемент управления может использоваться страницей C#:

public class MapPageCS : ContentPage
{
    public MapPageCS()
    {
        CustomMap customMap = new CustomMap
        {
            MapType = MapType.Street
        };
        // ...
        Content = customMap;
    }
}

Экземпляр Custom Map будет использоваться для отображения собственной карты на каждой платформе. Это Map Type свойство задает стиль отображения Map, возможные значения которого определяются в Map Type перечислении.

Местоположение карты и содержащиеся в ней выводы инициализируются, как показано в следующем примере кода:

public MapPage()
{
    // ...
    CustomPin pin = new CustomPin
    {
        Type = PinType.Place,
        Position = new Position(37.79752, -122.40183),
        Label = "Xamarin San Francisco Office",
        Address = "394 Pacific Ave, San Francisco CA",
        Name = "Xamarin",
        Url = "http://xamarin.com/about/"
    };
    customMap.CustomPins = new List { pin };
    customMap.Pins.Add(pin);
    customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(37.79752, -122.40183), Distance.FromMiles(1.0)));
}

Эта инициализация добавляет пользовательскую булавку и позиционирует вид карты с помощью MoveToRegion метода, который изменяет положение и уровень масштабирования карты, создавая Map Span из Position и Distance.

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

Создание пользовательского рендерера на каждой платформе

Процесс создания пользовательского класса визуализатора выглядит следующим образом:

  1. Создайте подкласс Map Renderer класса, который отображает пользовательскую карту.
  2. Переопределите On Element Changed метод, отображающий пользовательскую карту, и напишите логику для ее настройки. Этот метод вызывается при создании соответствующей пользовательской карты Xamarin.Forms.
  3. Добавьте ExportRenderer атрибут в класс пользовательского отрисовщика, чтобы указать, что он будет использоваться для отрисовки пользовательской карты Xamarin.Forms. Этот атрибут используется для регистрации пользовательского отрисовщика в Xamarin.Forms.

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

Следующая диаграмма иллюстрирует обязанности каждого проекта в примере приложения, а также отношения между ними:

Элемент CustomMap управления визуализируется классами средства визуализации для конкретной платформы, которые являются производными от MapRenderer класса для каждой платформы. Это приводит к тому, что каждый CustomMap элемент управления отображается с элементами управления для конкретной платформы, как показано на следующих снимках экрана:

Класс MapRenderer предоставляет OnElementChanged метод, который вызывается при создании пользовательской карты Xamarin.Forms для отрисовки соответствующего собственного элемента управления. Этот метод принимает ElementChangedEventArgs параметр, содержащий свойства OldElement и .NewElement. Эти свойства представляют собой элемент Xamarin.Forms, к которому был присоединен модуль визуализации, и элемент Xamarin.Forms, к которому присоединен модуль визуализации , соответственно. В примере приложения OldElement свойство будет null и New Element свойство будет содержать ссылку на Custom Map экземпляр.

Переопределенная версия OnElementChanged метода в каждом классе отрисовщика для конкретной платформы — это место для выполнения настройки собственного элемента управления. Доступ к типизированной ссылке на собственный элемент управления, используемый на платформе, можно получить через Control свойство. Кроме того, через свойство можно получить ссылку на отображаемый элемент управления Xamarin.Forms Element.

Следует соблюдать осторожность при подписке на обработчики событий в OnElementChanged методе, как показано в следующем примере кода:

protected override void OnElementChanged (ElementChangedEventArgs e)
{
  base.OnElementChanged (e);


  if (e.OldElement != null)
  {
      // Unsubscribe from event handlers
  }


  if (e.NewElement != null)
  {
      // Configure the native control and subscribe to event handlers
  }
}

Собственный элемент управления должен быть настроен, а обработчики событий должны быть подписаны только в том случае, если настраиваемый модуль отрисовки присоединен к новому элементу Xamarin.Forms. Точно так же любые обработчики событий, на которые была подписана, должны быть отменены только тогда, когда изменяется элемент, к которому привязан модуль визуализации. Принятие этого подхода поможет создать собственный модуль визуализации, который не страдает от утечек памяти.

Каждый класс пользовательского Export Renderer отрисовщика снабжен атрибутом, который регистрирует отрисовщик в Xamarin.Forms. Атрибут принимает два параметра — имя типа отображаемого пользовательского элемента управления Xamarin.Forms и имя типа пользовательского средства визуализации. Префикс assembly атрибута указывает, что атрибут применяется ко всей сборке.

В следующих разделах обсуждается реализация каждого класса пользовательского отрисовщика для конкретной платформы.

Создание пользовательского рендерера на iOS

На следующих снимках экрана показана карта до и после настройки:

В iOS булавка называется аннотацией и может быть либо пользовательским изображением, либо определенной системой булавкой различных цветов. Аннотации могут дополнительно отображать выноску, которая отображается в ответ на выбор пользователем аннотации. Выноска отображает свойства экземпляра и с необязательными левым и правым вспомогательными представлениями Label. На снимке экрана выше левое вспомогательное представление — это изображение обезьяны, а правое вспомогательное представление — кнопка « Информация » .AddressPin

В следующем примере кода показан пользовательский модуль визуализации для платформы iOS:

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.iOS
{
    public class CustomMapRenderer : MapRenderer
    {
        UIView customPinView;
        List customPins;


        protected override void OnElementChanged(ElementChangedEventArgs e)
        {
            base.OnElementChanged(e);


            if (e.OldElement != null)
            {
                var nativeMap = Control as MKMapView;
                if (nativeMap != null)
                {
                    nativeMap.RemoveAnnotations(nativeMap.Annotations);
                    nativeMap.GetViewForAnnotation = null;
                    nativeMap.CalloutAccessoryControlTapped -= OnCalloutAccessoryControlTapped;
                    nativeMap.DidSelectAnnotationView -= OnDidSelectAnnotationView;
                    nativeMap.DidDeselectAnnotationView -= OnDidDeselectAnnotationView;
                }
            }


            if (e.NewElement != null)
            {
                var formsMap = (CustomMap)e.NewElement;
                var nativeMap = Control as MKMapView;
                customPins = formsMap.CustomPins;


                nativeMap.GetViewForAnnotation = GetViewForAnnotation;
                nativeMap.CalloutAccessoryControlTapped += OnCalloutAccessoryControlTapped;
                nativeMap.DidSelectAnnotationView += OnDidSelectAnnotationView;
                nativeMap.DidDeselectAnnotationView += OnDidDeselectAnnotationView;
             }
        }
        // ...
    }
}

Метод OnElementChanged выполняет следующую настройку MKMapView экземпляра при условии, что пользовательский модуль отрисовки подключен к новому элементу Xamarin.Forms.

  • Свойство GetViewForAnnotation устанавливается в GetViewForAnnotation метод. Этот метод вызывается, когда местоположение аннотации становится видимым на карте , и используется для настройки аннотации перед отображением.
  • Обработчики событий CalloutAccessoryControlTapped, DidSelectAnnotationView и DidDeselectAnnotationView зарегистрированы. Эти события запускаются, когда пользователь касается нужного аксессуара в выноске , а также когда пользователь выбирает и отменяет выбор аннотации соответственно. События отписываются только тогда, когда изменяется элемент, к которому привязан рендерер.

Отображение аннотации

Метод GetViewForAnnotation вызывается, когда местоположение аннотации становится видимым на карте, и используется для настройки аннотации перед отображением. Аннотация состоит из двух частей:

  • Mk Annotation – включает заголовок, подзаголовок и расположение аннотации.
  • Mk AnnotationView – содержит изображение, представляющее аннотацию, и, необязательно, выноску, которая отображается, когда пользователь касается аннотации.

Метод Get View For Annotation принимает объект IMKAnnotation, содержащий данные аннотации, и возвращает объект MKAnnotationView для отображения на карте, как показано в следующем примере кода:

protected override MKAnnotationView GetViewForAnnotation(MKMapView mapView, IMKAnnotation annotation)
{
    MKAnnotationView annotationView = null;


    if (annotation is MKUserLocation)
        return null;


    var customPin = GetCustomPin(annotation as MKPointAnnotation);
    if (customPin == null)
    {
        throw new Exception("Custom pin not found");
    }


    annotationView = mapView.DequeueReusableAnnotation(customPin.Name);
    if (annotationView == null)
    {
        annotationView = new CustomMKAnnotationView(annotation, customPin.Name);
        annotationView.Image = UIImage.FromFile("pin.png");
        annotationView.CalloutOffset = new CGPoint(0, 0);
        annotationView.LeftCalloutAccessoryView = new UIImageView(UIImage.FromFile("monkey.png"));
        annotationView.RightCalloutAccessoryView = UIButton.FromType(UIButtonType.DetailDisclosure);
        ((CustomMKAnnotationView)annotationView).Name = customPin.Name;
        ((CustomMKAnnotationView)annotationView).Url = customPin.Url;
    }
    annotationView.CanShowCallout = true;


    return annotationView;
}

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

1) Метод GetCustomPin вызывается для возврата пользовательских данных вывода для аннотации.

2) Для экономии памяти представление аннотации объединяется для повторного использования с вызовом DequeueReusableAnnotation.

3) Класс CustomMKAnnotationView расширяет MKAnnotationView класс свойствами Nameи Url, которые соответствуют идентичным свойствам в CustomPin экземпляре. Создается новый экземпляр при CustomMKAnnotationView условии, что аннотация null:

  • Свойству Custom MKAnnotationView.Image присваивается изображение, которое будет представлять аннотацию на карте.
  • Свойство Custom MKAnnotationView.CalloutOffset имеет значение a CGPoint, указывающее, что выноска будет центрирована над аннотацией.
  • Свойство Custom MKAnnotationView.LeftCalloutAccessoryView настроено на изображение обезьяны, которое будет отображаться слева от заголовка и адреса аннотации.
  • Свойство Custom MKAnnotationView.RightCalloutAccessoryView настроено на информационную кнопку, которая появится справа от заголовка и адреса аннотации.
  • Свойству Custom MKAnnotationView.Name присваивается CustomPin.Name свойство, возвращаемое GetCustomPin методом. Это позволяет идентифицировать аннотацию, чтобы при желании ее выноску можно было дополнительно настроить .
  • Свойству Custom MKAnnotationView.Url присваивается CustomPin.Url свойство, возвращаемое GetCustomPin методом. URL-адрес будет перемещен, когда пользователь коснется кнопки, отображаемой в правом представлении аксессуара выноски .

4) Свойство MKAnnotationView.CanShowCallout настроено true таким образом, чтобы выноска отображалась при касании аннотации.

5) Аннотация возвращается для отображения на карте.

Выбор аннотации

Когда пользователь нажимает на аннотацию, DidSelectAnnotationView срабатывает событие, которое, в свою очередь, выполняет OnDidSelectAnnotationView метод:

void OnDidSelectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
    CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
    customPinView = new UIView();


    if (customView.Name.Equals("Xamarin"))
    {
        customPinView.Frame = new CGRect(0, 0, 200, 84);
        var image = new UIImageView(new CGRect(0, 0, 200, 84));
        image.Image = UIImage.FromFile("xamarin.png");
        customPinView.AddSubview(image);
        customPinView.Center = new CGPoint(0, -(e.View.Frame.Height + 75));
        e.View.AddSubview(customPinView);
    }
}

Этот метод расширяет существующую выноску (содержащую левое и правое вспомогательные представления), добавляя UIView к ней экземпляр, содержащий изображение логотипа Xamarin, при условии, что для Name свойства выбранной аннотации установлено значение Xamarin. Это позволяет использовать сценарии, в которых для разных аннотаций могут отображаться разные выноски. Экземпляр UIView будет отображаться по центру над существующей выноской.

Нажатие на правой выноске AccessoryView

Когда пользователь нажимает кнопку «Информация» в правом вспомогательном представлении выноски, CalloutAccessoryControlTapped срабатывает событие, которое, в свою очередь, выполняет OnCalloutAccessoryControlTapped метод:

void OnCalloutAccessoryControlTapped(object sender, MKMapViewAccessoryTappedEventArgs e)
{
    CustomMKAnnotationView customView = e.View as CustomMKAnnotationView;
    if (!string.IsNullOrWhiteSpace(customView.Url))
    {
        UIApplication.SharedApplication.OpenUrl(new Foundation.NSUrl(customView.Url));
    }
}

Этот метод открывает веб-браузер и переходит к адресу, хранящемуся в CustomMKAnnotationView.Url свойстве. Обратите внимание, что адрес был определен при создании CustomPin коллекции в проекте библиотеки .NET Standard.

Отмена выбора аннотации

Когда отображается аннотация и пользователь нажимает на карту, возникает DidDeselectAnnotationView событие, которое, в свою очередь, выполняет OnDidDeselectAnnotationView метод:

void OnDidDeselectAnnotationView(object sender, MKAnnotationViewEventArgs e)
{
    if (!e.View.Selected)
    {
        customPinView.RemoveFromSuperview();
        customPinView.Dispose();
        customPinView = null;
    }
}

Этот метод гарантирует, что если существующая выноска не выбрана, расширенная часть выноски (изображение логотипа Xamarin) также перестанет отображаться, а ее ресурсы будут освобождены.

Создание пользовательского рендерера на Android

На следующих снимках экрана показана карта до и после настройки:

В Android булавка называется маркером и может быть либо пользовательским изображением, либо определенным системой маркером различных цветов Маркеры могут отображать информационное окно которое отображается в ответ на нажатие пользователем маркера. Информационное окно отображает Label и Address свойства Pin экземпляра и может быть настроено для включения другого содержимого. Однако одновременно может отображаться только одно информационное окно.

В следующем примере кода показан пользовательский модуль визуализации для платформы Android:

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.Droid
{
    public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter
    {
        List customPins;


        public CustomMapRenderer(Context context) : base(context)
        {
        }


        protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs e)
        {
            base.OnElementChanged(e);


            if (e.OldElement != null)
            {
                NativeMap.InfoWindowClick -= OnInfoWindowClick;
            }


            if (e.NewElement != null)
            {
                var formsMap = (CustomMap)e.NewElement;
                customPins = formsMap.CustomPins;
            }
        }


        protected override void OnMapReady(GoogleMap map)
        {
            base.OnMapReady(map);


            NativeMap.InfoWindowClick += OnInfoWindowClick;
            NativeMap.SetInfoWindowAdapter(this);
        }
        ...
    }
}

При условии, что настраиваемый модуль отрисовки подключен к новому элементу Xamarin.Forms, OnElementChanged метод извлекает список настраиваемых контактов из элемента управления. Как только GoogleMap экземпляр будет доступен, OnMapReady переопределение будет вызвано. Этот метод регистрирует обработчик событий для InfoWindowClick события, которое срабатывает при щелчке информационного окна и отменяет подписку только тогда, когда изменяется элемент, к которому привязан модуль визуализации. Переопределение OnMapReady также вызывает SetInfoWindowAdapter метод, чтобы указать, что CustomMapRenderer экземпляр класса будет предоставлять методы для настройки информационного окна.

Класс CustomMapRenderer реализует GoogleMap.IInfoWindowAdapter интерфейс для настройки информационного окна Этот интерфейс указывает, что должны быть реализованы следующие методы:

  • public Android.Views.View GetInfoWindow (Marker marker) – Этот метод вызывается для возврата пользовательского информационного окна для маркера. Если он возвращает null, то будет использоваться рендеринг окна по умолчанию. Если он возвращает View, то View будет помещен в рамку информационного окна.
  • public Android.Views.View GetInfoContents (Marker marker) – Этот метод вызывается для возврата View содержимого информационного окна и будет вызываться только в том случае, если GetInfoWindow метод возвращает null. Если он возвращает null, то будет использоваться рендеринг содержимого информационного окна по умолчанию.

В примере приложения настраивается только содержимое информационного окна, поэтому GetInfoWindow метод возвращается null, чтобы включить это.

Настройка маркера

Значок, используемый для представления маркера, можно настроить, вызвав MarkerOptions.SetIcon метод. Этого можно добиться, переопределив CreateMarker метод, который вызывается для каждого Pin добавления на карту:

protected override MarkerOptions CreateMarker(Pin pin)
{
    var marker = new MarkerOptions();
    marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
    marker.SetTitle(pin.Label);
    marker.SetSnippet(pin.Address);
    marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));
    return marker;
}

Этот метод создает новый MarkerOption экземпляр для каждого Pin экземпляра. После установки положения, метки и адреса маркера его значок устанавливается с помощью SetIcon метода. Этот метод принимает Bitmap Descriptor объект, содержащий данные, необходимые для отображения значка, а BitmapDescriptorFactory класс предоставляет вспомогательные методы для упрощения создания файла BitmapDescriptor.

При необходимости этот GetMarkerForPin метод можно вызвать в средстве визуализации карты для извлечения файла Marker из файла Pin.

Настройка информационного окна

Когда пользователь нажимает на маркер, GetInfoContents метод выполняется при условии, что GetInfoWindow метод возвращает null. В следующем примере кода показан GetInfoContents метод:

public Android.Views.View GetInfoContents(Marker marker)
{
    var inflater = Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService) as Android.Views.LayoutInflater;
    if (inflater != null)
    {
        Android.Views.View view;


        var customPin = GetCustomPin(marker);
        if (customPin == null)
        {
            throw new Exception("Custom pin not found");
        }


        if (customPin.Name.Equals("Xamarin"))
        {
            view = inflater.Inflate(Resource.Layout.XamarinMapInfoWindow, null);
        }
        else
        {
            view = inflater.Inflate(Resource.Layout.MapInfoWindow, null);
        }


        var infoTitle = view.FindViewById(Resource.Id.InfoWindowTitle);
        var infoSubtitle = view.FindViewById(Resource.Id.InfoWindowSubtitle);


        if (infoTitle != null)
        {
            infoTitle.Text = marker.Title;
        }
        if (infoSubtitle != null)
        {
            infoSubtitle.Text = marker.Snippet;
        }


        return view;
    }
    return null;
}

Этот метод возвращает View содержимое информационного окна. Это достигается следующим образом:

  • Экземпляр LayoutInflater получен. Это используется для преобразования XML-файла макета в соответствующий файл View.
  • Метод GetCustomPin вызывается для возврата пользовательских данных вывода для информационного окна.
  • Макет XamarinMapInfoWindow завышен, если CustomPin.Name свойство равно Xamarin. В противном случае MapInfoWindow макет раздувается. Это позволяет использовать сценарии, в которых разные макеты информационных окон могут отображаться для разных маркеров.
  • Ресурсы InfoWindowTitle и InfoWindowSubtitle извлекаются из расширенного макета, и их Text свойства устанавливаются на соответствующие данные из Marker экземпляра, при условии, что ресурсы не являются null.
  • Экземпляр View возвращается для отображения на карте.

Информационное окно не является живым View. Вместо этого Android преобразует в View статическое растровое изображение и отображает его как изображение. Это означает, что хотя информационное окно может реагировать на событие щелчка, оно не может реагировать на какие-либо события касания или жесты, а отдельные элементы управления в информационном окне не могут реагировать на свои собственные события щелчка.

Нажав на информационное окно

Когда пользователь нажимает на информационное окно, InfoWindowClick срабатывает событие, которое, в свою очередь, выполняет OnInfoWindowClick метод:

void OnInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
    var customPin = GetCustomPin(e.Marker);
    if (customPin == null)
    {
        throw new Exception("Custom pin not found");
    }


    if (!string.IsNullOrWhiteSpace(customPin.Url))
    {
        var url = Android.Net.Uri.Parse(customPin.Url);
        var intent = new Intent(Intent.ActionView, url);
        intent.AddFlags(ActivityFlags.NewTask);
        Android.App.Application.Context.StartActivity(intent);
    }
}

Этот метод открывает веб-браузер и переходит к адресу, хранящемуся в Url свойстве извлеченного CustomPin экземпляра для Marker. Обратите внимание, что адрес был определен при создании CustomPin коллекции в проекте библиотеки .NET Standard.

Создание пользовательского средства визуализации на универсальной платформе Windows

На следующих снимках экрана показана карта до и после настройки:

В UWP булавка называется значком карты и может быть либо пользовательским изображением, либо изображением по умолчанию, определенным системой. На значке карты может отображаться значок UserControl, который отображается в ответ на нажатие пользователем значка карты. Может UserControl отображать любое содержимое, включая свойства и Label экземпляра .AddressPin

В следующем примере кода показан пользовательский отрисовщик UWP:

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace CustomRenderer.UWP
{
    public class CustomMapRenderer : MapRenderer
    {
        MapControl nativeMap;
        List customPins;
        XamarinMapOverlay mapOverlay;
        bool xamarinOverlayShown = false;


        protected override void OnElementChanged(ElementChangedEventArgs e)
        {
            base.OnElementChanged(e);


            if (e.OldElement != null)
            {
                nativeMap.MapElementClick -= OnMapElementClick;
                nativeMap.Children.Clear();
                mapOverlay = null;
                nativeMap = null;
            }


            if (e.NewElement != null)
            {
                var formsMap = (CustomMap)e.NewElement;
                nativeMap = Control as MapControl;
                customPins = formsMap.CustomPins;


                nativeMap.Children.Clear();
                nativeMap.MapElementClick += OnMapElementClick;


                foreach (var pin in customPins)
                {
                    var snPosition = new BasicGeoposition { Latitude = pin.Pin.Position.Latitude, Longitude = pin.Pin.Position.Longitude };
                    var snPoint = new Geopoint(snPosition);


                    var mapIcon = new MapIcon();
                    mapIcon.Image = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx:///pin.png"));
                    mapIcon.CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible;
                    mapIcon.Location = snPoint;
                    mapIcon.NormalizedAnchorPoint = new Windows.Foundation.Point(0.5, 1.0);


                    nativeMap.MapElements.Add(mapIcon);
                }
            }
        }
        ...
    }
}

Метод OnElementChanged выполняет следующие операции при условии, что настраиваемый модуль отрисовки присоединен к новому элементу Xamarin.Forms.

  • Он очищает MapControl.Children коллекцию, чтобы удалить все существующие элементы пользовательского интерфейса с карты перед регистрацией обработчика событий для MapElementClick события. Это событие срабатывает, когда пользователь касается или щелкает на MapElement элементе MapControl, и отменяет подписку только тогда, когда изменяется элемент, к которому привязан модуль визуализации.
  • Каждая булавка в customPins коллекции отображается в правильном географическом месте на карте следующим образом:
  • Местоположение для булавки создается как Geopoint экземпляр.
  • Экземпляр MapIcon создается для представления булавки.
  • Изображение, используемое для представления MapIcon, задается установкой MapIcon.Image свойства. Однако не всегда гарантируется, что изображение значка карты будет отображаться, так как оно может быть скрыто другими элементами на карте. Поэтому для свойства значка карты CollisionBehaviorDesired установлено значение MapElementCollisionBehavior.RemainVisible, чтобы он оставался видимым.
  • Местоположение MapIcon задается установкой MapIcon.Location свойства.
  • Свойству MapIcon.NormalizedAnchorPoint задается приблизительное расположение указателя на изображении. Если это свойство сохраняет значение по умолчанию (0,0), которое представляет левый верхний угол изображения, изменения уровня масштабирования карты могут привести к тому, что изображение будет указывать на другое место.
  • Экземпляр MapIcon добавлен в MapControl.MapElements коллекцию. Это приводит к тому, что значок карты отображается на странице MapControl.

При использовании одного и того же изображения для нескольких значков карт RandomAccessStreamReference экземпляр должен быть объявлен на уровне страницы или приложения для лучшей производительности.

Отображение объекта UserControl

Когда пользователь касается значка карты, выполняется метод OnMapElementClick. Этот метод показан в следующем примере кода:

private void OnMapElementClick(MapControl sender, MapElementClickEventArgs args)
{
    var mapIcon = args.MapElements.FirstOrDefault(x => x is MapIcon) as MapIcon;
    if (mapIcon != null)
    {
        if (!xamarinOverlayShown)
        {
            var customPin = GetCustomPin(mapIcon.Location.Position);
            if (customPin == null)
            {
                throw new Exception("Custom pin not found");
            }


            if (customPin.Name.Equals("Xamarin"))
            {
                if (mapOverlay == null)
                {
                    mapOverlay = new XamarinMapOverlay(customPin);
                }


                var snPosition = new BasicGeoposition { Latitude = customPin.Position.Latitude, Longitude = customPin.Position.Longitude };
                var snPoint = new Geopoint(snPosition);


                nativeMap.Children.Add(mapOverlay);
                MapControl.SetLocation(mapOverlay, snPoint);
                MapControl.SetNormalizedAnchorPoint(mapOverlay, new Windows.Foundation.Point(0.5, 1.0));
                xamarinOverlayShown = true;
            }
        }
        else
        {
            nativeMap.Children.Remove(mapOverlay);
            xamarinOverlayShown = false;
        }
    }
}

Этот метод создает экземпляр UserControl, который отображает информацию о закреплении. Это реализуется следующим образом:

  • Извлекается экземпляр MapIcon.
  • Вызывается метод GetCustomPin, возвращающий данные настраиваемого закрепления, которые требуется отобразить.
  • Создается экземпляр XamarinMapOverlay для отображения данных настраиваемого закрепления. Этот класс представляет пользовательский элемент управления.
  • Географическое расположение, в котором отображается экземпляр XamarinMapOverlay на объекте MapControl, создается в виде экземпляра Geopoint.
  • Экземпляр XamarinMapOverlay добавляется в коллекцию MapControl.Children. Эта коллекция содержит элементы пользовательского интерфейса XAML, которые будут отображаться на карте.
  • Географическое местоположение XamarinMapOverlay экземпляра на карте задается посредством вызова метода SetLocation.
  • Относительное положение экземпляра XamarinMapOverlay, которое соответствует указанному местоположению, задается посредством вызова метода SetNormalizedAnchorPoint. Благодаря этому гарантируется, что при изменении масштаба карты экземпляр Xamarin MapOverlay всегда будет отображаться в правильном местоположении.

Также, если на карте уже отображается информация о закреплении, при касании карты экземпляр XamarinMapOverlay удаляется из коллекции MapControl.Children.

Касание кнопки "Сведения"

Когда пользователь касается кнопки Сведения в пользовательском элементе управления XamarinMapOverlay, вызывается событие Tapped, которое, в свою очередь, вызывает метод OnInfoButtonTapped:

private async void OnInfoButtonTapped(object sender, TappedRoutedEventArgs e)
{
    await Launcher.LaunchUriAsync(new Uri(customPin.Url));
}

Этот метод открывает веб-браузер и выполняет переход по адресу, который хранится в свойстве Url экземпляра CustomPin. Обратите внимание, что этот адрес определяется при создании коллекции CustomPin в проекте библиотеки .NET Standard.

Оглавление

Материалы по теме