Personalizando um marcador de mapaCustomizing a Map Pin

Baixar Exemplo Baixar o exemploDownload Sample Download the sample

Este artigo demonstra como criar um renderizador personalizado para o Controle de Mapeamento, que exibe um mapa nativo com um marcador personalizado e uma exibição personalizada dos dados de marcador em cada plataforma.This article demonstrates how to create a custom renderer for the Map control, which displays a native map with a customized pin and a customized view of the pin data on each platform.

Cada Xamarin.Forms exibição tem um renderizador que acompanha para cada plataforma que cria uma instância de um controle nativo.Every Xamarin.Forms view has an accompanying renderer for each platform that creates an instance of a native control. Quando um Map é renderizado por um Xamarin.Forms aplicativo no Ios, a MapRenderer classe é instanciada, que por sua vez instancia um MKMapView controle nativo.When a Map is rendered by a Xamarin.Forms application in iOS, the MapRenderer class is instantiated, which in turn instantiates a native MKMapView control. Na plataforma Android, a classe MapRenderer cria uma instância de um controle MapView nativo.On the Android platform, the MapRenderer class instantiates a native MapView control. Na UWP (Plataforma Universal do Windows), a classe MapRenderer cria uma instância de um MapControl nativo.On the Universal Windows Platform (UWP), the MapRenderer class instantiates a native MapControl. Para obter mais informações sobre o renderizador e as classes de controle nativo que Xamarin.Forms controlam o mapa para o, consulte classes base do processador e controles nativos.For more information about the renderer and native control classes that Xamarin.Forms controls map to, see Renderer Base Classes and Native Controls.

O diagrama a seguir ilustra a relação entre o Map e os controles nativos correspondentes que o implementam:The following diagram illustrates the relationship between the Map and the corresponding native controls that implement it:

Relação entre o Controle de Mapeamento e a implementação de controles nativos

O processo de renderização pode ser usado para implementar personalizações específicas da plataforma criando um renderizador personalizado para um Map em cada plataforma.The rendering process can be used to implement platform-specific customizations by creating a custom renderer for a Map on each platform. O processo para fazer isso é o seguinte:The process for doing this is as follows:

  1. Crie um Xamarin.Forms mapa personalizado.Create a Xamarin.Forms custom map.
  2. Consuma o mapa personalizado de Xamarin.Forms .Consume the custom map from Xamarin.Forms.
  3. Criar o renderizador personalizado para o mapa em cada plataforma.Create the custom renderer for the map on each platform.

Cada item agora será abordado por vez, para implementar um renderizador CustomMap que exibe um mapa nativo com um marcador personalizado e uma exibição personalizada dos dados do marcador em cada plataforma.Each item will now be discussed in turn, to implement a CustomMap renderer that displays a native map with a customized pin and a customized view of the pin data on each platform.

Observação

Xamarin.Forms.Maps deve ser inicializado e configurado antes do uso.Xamarin.Forms.Maps must be initialized and configured before use. Para obter mais informações, confira Maps Control.For more information, see Maps Control.

Criando o mapa personalizadoCreating the Custom Map

Um controle de mapa personalizado pode ser criado por meio da subclasse da Map classe, conforme mostrado no exemplo de código a seguir:A custom map control can be created by subclassing the Map class, as shown in the following code example:

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

O controle CustomMap é criado no projeto da biblioteca do .NET Standard e define a API para o mapa personalizado.The CustomMap control is created in the .NET Standard library project and defines the API for the custom map. O mapa personalizado expõe a propriedade CustomPins que representa a coleção de objetos CustomPin que serão renderizados pelo Controle de Mapeamento nativo em cada plataforma.The custom map exposes the CustomPins property that represents the collection of CustomPin objects that will be rendered by the native map control on each platform. A classe CustomPin é mostrada no seguinte exemplo de código:The CustomPin class is shown in the following code example:

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

Essa classe define um CustomPin como herdando as propriedades da Pin classe e adicionando Name Url Propriedades e.This class defines a CustomPin as inheriting the properties of the Pin class, and adding Name and Url properties.

Consumindo o mapa personalizadoConsuming the Custom Map

O controle CustomMap pode ser referenciado em XAML no projeto da biblioteca do .NET Standard declarando um namespace para sua localização e usando o prefixo do namespace no Controle de Mapeamento personalizado.The CustomMap control can be referenced in XAML in the .NET Standard library project by declaring a namespace for its location and using the namespace prefix on the custom map control. O seguinte exemplo de código mostra como o controle CustomMap pode ser consumido por uma página XAML:The following code example shows how the CustomMap control can be consumed by a XAML page:

<ContentPage ...
                   xmlns:local="clr-namespace:CustomRenderer;assembly=CustomRenderer">
    <local:CustomMap x:Name="customMap"
                   MapType="Street" />
</ContentPage>

O prefixo do namespace local pode ser qualquer nome.The local namespace prefix can be named anything. No entanto, os valores de clr-namespace e assembly devem corresponder aos detalhes do mapa personalizado.However, the clr-namespace and assembly values must match the details of the custom map. Quando o namespace é declarado, o prefixo é usado para referenciar o mapa personalizado.Once the namespace is declared, the prefix is used to reference the custom map.

O seguinte exemplo de código mostra como o controle CustomMap pode ser consumido por uma página em C#:The following code example shows how the CustomMap control can be consumed by a C# page:

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

A instância CustomMap será usada para exibir o mapa nativo em cada plataforma.The CustomMap instance will be used to display the native map on each platform. MapTypeA propriedade é define o estilo de exibição do Map , com os valores possíveis definidos na MapType enumeração.It's MapType property sets the display style of the Map, with the possible values being defined in the MapType enumeration.

A localização do mapa e os marcadores que ele contém são inicializados conforme mostrado no seguinte exemplo de código:The location of the map, and the pins it contains, are initialized as shown in the following code example:

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<CustomPin> { pin };
    customMap.Pins.Add(pin);
    customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(37.79752, -122.40183), Distance.FromMiles(1.0)));
}

Essa inicialização adiciona um PIN personalizado e posiciona o modo de exibição do mapa com o MoveToRegion método, que altera a posição e o nível de zoom do mapa, criando um MapSpan de a Position e um Distance .This initialization adds a custom pin and positions the map's view with the MoveToRegion method, which changes the position and zoom level of the map by creating a MapSpan from a Position and a Distance.

Agora, um renderizador personalizado pode ser adicionado a cada projeto de aplicativo para personalizar os controles de mapa nativos.A custom renderer can now be added to each application project to customize the native map controls.

Criando o renderizador personalizado em cada plataformaCreating the Custom Renderer on each Platform

O processo para criar a classe do renderizador personalizado é a seguinte:The process for creating the custom renderer class is as follows:

  1. Criar uma subclasse da classe MapRenderer que renderiza o mapa personalizado.Create a subclass of the MapRenderer class that renders the custom map.
  2. Substituir o método OnElementChanged que renderiza o mapa personalizado e escrever a lógica para personalizá-lo.Override the OnElementChanged method that renders the custom map and write logic to customize it. Esse método é chamado quando o Xamarin.Forms mapa personalizado correspondente é criado.This method is called when the corresponding Xamarin.Forms custom map is created.
  3. Adicione um ExportRenderer atributo à classe de processador personalizado para especificar que ele será usado para renderizar o Xamarin.Forms mapa personalizado.Add an ExportRenderer attribute to the custom renderer class to specify that it will be used to render the Xamarin.Forms custom map. Esse atributo é usado para registrar o renderizador personalizado com Xamarin.Forms .This attribute is used to register the custom renderer with Xamarin.Forms.

Observação

O fornecimento de um renderizador personalizado em cada projeto da plataforma é opcional.It is optional to provide a custom renderer in each platform project. Se um renderizador personalizado não estiver registrado, será usado o renderizador padrão da classe base do controle.If a custom renderer isn't registered, then the default renderer for the control's base class will be used.

O diagrama a seguir ilustra as responsabilidades de cada projeto no aplicativo de exemplo, bem como as relações entre elas:The following diagram illustrates the responsibilities of each project in the sample application, along with the relationships between them:

Responsabilidades do projeto de renderizador personalizado de CustomMap

O controle CustomMap é renderizado por classes de renderizador específicas da plataforma, que derivam da classe MapRenderer para cada plataforma.The CustomMap control is rendered by platform-specific renderer classes, which derive from the MapRenderer class for each platform. Isso faz com que cada controle CustomMap seja renderizado com controles específicos da plataforma, conforme mostrado nas seguintes capturas de tela:This results in each CustomMap control being rendered with platform-specific controls, as shown in the following screenshots:

CustomMap em cada plataforma

A MapRenderer classe expõe o OnElementChanged método, que é chamado quando o Xamarin.Forms mapa personalizado é criado para renderizar o controle nativo correspondente.The MapRenderer class exposes the OnElementChanged method, which is called when the Xamarin.Forms custom map is created to render the corresponding native control. Esse método usa um parâmetro ElementChangedEventArgs, que contém as propriedades OldElement e NewElement.This method takes an ElementChangedEventArgs parameter that contains OldElement and NewElement properties. Essas propriedades representam o Xamarin.Forms elemento ao qual o renderizador foi anexado e o Xamarin.Forms elemento ao qual o renderizador está anexado, respectivamente.These properties represent the Xamarin.Forms element that the renderer was attached to, and the Xamarin.Forms element that the renderer is attached to, respectively. No aplicativo de exemplo, a propriedade OldElement será null e a propriedade NewElement conterá uma referência à instância de CustomMap.In the sample application the OldElement property will be null and the NewElement property will contain a reference to the CustomMap instance.

Uma versão de substituição do método OnElementChanged, em cada classe de renderizador específica da plataforma, é o lugar para realização da personalização do controle nativo.An overridden version of the OnElementChanged method, in each platform-specific renderer class, is the place to perform the native control customization. Uma referência tipada ao controle nativo que está sendo usado na plataforma pode ser acessada por meio da propriedade Control.A typed reference to the native control being used on the platform can be accessed through the Control property. Além disso, uma referência ao Xamarin.Forms controle que está sendo processado pode ser obtida por meio da Element propriedade.In addition, a reference to the Xamarin.Forms control that's being rendered can be obtained through the Element property.

É necessário ter cuidado ao assinar manipuladores de eventos no método OnElementChanged, conforme demonstrado no seguinte exemplo de código:Care must be taken when subscribing to event handlers in the OnElementChanged method, as demonstrated in the following code example:

protected override void OnElementChanged (ElementChangedEventArgs<Xamarin.Forms.View> 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
  }
}

O controle nativo deve ser configurado e os manipuladores de eventos se inscreveram somente quando o renderizador personalizado é anexado a um novo Xamarin.Forms elemento.The native control should be configured and event handlers subscribed to only when the custom renderer is attached to a new Xamarin.Forms element. De forma semelhante, a assinatura dos manipuladores de eventos assinados só deverá ser cancelada quando o elemento ao qual o renderizador está anexado for alterado.Similarly, any event handlers that were subscribed to should be unsubscribed from only when the element that the renderer is attached to changes. Adotar essa abordagem ajudará a criar um renderizador personalizado que não sofre perdas de memória.Adopting this approach will help to create a custom renderer that doesn't suffer from memory leaks.

Cada classe de processador personalizado é decorada com um ExportRenderer atributo que registra o renderizador com Xamarin.Forms .Each custom renderer class is decorated with an ExportRenderer attribute that registers the renderer with Xamarin.Forms. O atributo usa dois parâmetros – o nome do tipo do Xamarin.Forms controle personalizado que está sendo renderizado e o nome do tipo do renderizador personalizado.The attribute takes two parameters – the type name of the Xamarin.Forms custom control being rendered, and the type name of the custom renderer. O prefixo assembly do atributo especifica que o atributo se aplica a todo o assembly.The assembly prefix to the attribute specifies that the attribute applies to the entire assembly.

As seções a seguir abordam a implementação de cada classe de renderizador personalizado específica da plataforma.The following sections discuss the implementation of each platform-specific custom renderer class.

Criando o renderizador personalizado no iOSCreating the Custom Renderer on iOS

As seguintes capturas de tela mostram o mapa, antes e após a personalização:The following screenshots show the map, before and after customization:

As capturas de tela mostram um dispositivo móvel com um PIN comum e um PIN anotado.

No iOS, o marcador é chamado de anotação e pode ser uma imagem personalizada ou um marcador definido pelo sistema de várias cores.On iOS the pin is called an annotation, and can be either a custom image or a system-defined pin of various colors. As anotações podem opcionalmente mostrar um texto explicativo, que é exibido em resposta à seleção da anotação pelo usuário.Annotations can optionally show a callout, which is displayed in response to the user selecting the annotation. O texto explicativo exibe o Label e as propriedades Address da instância Pin, com as exibições acessório direita e esquerda opcionais.The callout displays the Label and Address properties of the Pin instance, with optional left and right accessory views. Na captura de tela acima, a exibição acessório esquerda é a imagem de um macaco, com a exibição acessório direita sendo o botão Informações.In the screenshot above, the left accessory view is the image of a monkey, with the right accessory view being the Information button.

O exemplo de código a seguir mostra o renderizador personalizado para a plataforma iOS:The following code example shows the custom renderer for the iOS platform:

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

        protected override void OnElementChanged(ElementChangedEventArgs<View> 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;
            }
        }
        // ...
    }
}

O OnElementChanged método executa a seguinte configuração da MKMapView instância, desde que o renderizador personalizado esteja anexado a um novo Xamarin.Forms elemento:The OnElementChanged method performs the following configuration of the MKMapView instance, provided that the custom renderer is attached to a new Xamarin.Forms element:

Exibindo a anotaçãoDisplaying the Annotation

O método GetViewForAnnotation é chamado quando a localização da anotação se torna visível no mapa e é usado para personalizar a anotação antes da exibição.The GetViewForAnnotation method is called when the location of the annotation becomes visible on the map, and is used to customize the annotation prior to display. Uma anotação tem duas partes:An annotation has two parts:

  • MkAnnotation – inclui o título, o subtítulo e a localização da anotação.MkAnnotation – includes the title, subtitle, and location of the annotation.
  • MkAnnotationView – contém a imagem para representar a anotação e, opcionalmente, um texto explicativo que é mostrado quando o usuário toca a anotação.MkAnnotationView – contains the image to represent the annotation, and optionally, a callout that is shown when the user taps the annotation.

O método GetViewForAnnotation aceita uma IMKAnnotation que contém os dados da anotação e retorna uma MKAnnotationView para exibição no mapa e é mostrado no seguinte exemplo de código:The GetViewForAnnotation method accepts an IMKAnnotation that contains the annotation's data and returns an MKAnnotationView for display on the map, and is shown in the following code example:

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;
}

Esse método garante que a anotação seja exibida como uma imagem personalizada, em vez de como um marcador definido pelo sistema, e que quando a anotação for tocada, um texto explicativo seja exibido que inclua conteúdo adicional à esquerda e à direita do título da anotação e do endereço.This method ensures that the annotation will be displayed as a custom image, rather than as system-defined pin, and that when the annotation is tapped a callout will be displayed that includes additional content to the left and right of the annotation title and address. Isso é feito da seguinte maneira:This is accomplished as follows:

  1. O método GetCustomPin é chamado para retornar os dados de marcador personalizados da anotação.The GetCustomPin method is called to return the custom pin data for the annotation.
  2. Para conservar a memória, a exibição da anotação é agrupada para reutilização com a chamada para DequeueReusableAnnotation .To conserve memory, the annotation's view is pooled for reuse with the call to DequeueReusableAnnotation.
  3. A classe CustomMKAnnotationView estende a classe MKAnnotationView com as propriedades Name e Url que correspondem às propriedades idênticas na instância CustomPin.The CustomMKAnnotationView class extends the MKAnnotationView class with Name and Url properties that correspond to identical properties in the CustomPin instance. Uma nova instância da CustomMKAnnotationView é criada, desde que a anotação seja null:A new instance of the CustomMKAnnotationView is created, provided that the annotation is null:
    • A propriedade CustomMKAnnotationView.Image está definida como a imagem que representará a anotação no mapa.The CustomMKAnnotationView.Image property is set to the image that will represent the annotation on the map.
    • A propriedade CustomMKAnnotationView.CalloutOffset é definida como um CGPoint que especifica que o texto explicativo será centralizado acima da anotação.The CustomMKAnnotationView.CalloutOffset property is set to a CGPoint that specifies that the callout will be centered above the annotation.
    • A propriedade CustomMKAnnotationView.LeftCalloutAccessoryView é definida como uma imagem de um macaco que será exibido à esquerda do título da anotação e do endereço.The CustomMKAnnotationView.LeftCalloutAccessoryView property is set to an image of a monkey that will appear to the left of the annotation title and address.
    • A propriedade CustomMKAnnotationView.RightCalloutAccessoryView é definida como um botão Informações que será exibido à direita do título da anotação e do endereço.The CustomMKAnnotationView.RightCalloutAccessoryView property is set to an Information button that will appear to the right of the annotation title and address.
    • A propriedade CustomMKAnnotationView.Name é definida como a propriedade CustomPin.Name retornada pelo método GetCustomPin.The CustomMKAnnotationView.Name property is set to the CustomPin.Name property returned by the GetCustomPin method. Isso permite que a anotação seja identificada para que seu texto explicativo possa ser personalizado ainda mais, se desejado.This enables the annotation to be identified so that it's callout can be further customized, if desired.
    • A propriedade CustomMKAnnotationView.Url é definida como a propriedade CustomPin.Url retornada pelo método GetCustomPin.The CustomMKAnnotationView.Url property is set to the CustomPin.Url property returned by the GetCustomPin method. A URL será direcionada quando o usuário tocar o botão exibido na exibição acessório direita do texto explicativo.The URL will be navigated to when the user taps the button displayed in the right callout accessory view.
  4. A MKAnnotationView.CanShowCallout propriedade é definida como para true que o texto explicativo seja exibido quando a anotação é tocada.The MKAnnotationView.CanShowCallout property is set to true so that the callout is displayed when the annotation is tapped.
  5. A anotação é retornada para a exibição no mapa.The annotation is returned for display on the map.

Selecionando a anotaçãoSelecting the Annotation

Quando o usuário toca a anotação, o evento DidSelectAnnotationView é disparado, que, por sua vez, executa o método OnDidSelectAnnotationView:When the user taps on the annotation, the DidSelectAnnotationView event fires, which in turn executes the OnDidSelectAnnotationView method:

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);
    }
}

Esse método estende o texto explicativo existente (que contém as exibições acessório esquerda e direita), adicionando uma instância UIView a ele que contém uma imagem do logotipo do Xamarin, desde que a anotação selecionada tenha sua propriedade Name definida como Xamarin.This method extends the existing callout (that contains left and right accessory views) by adding a UIView instance to it that contains an image of the Xamarin logo, provided that the selected annotation has its Name property set to Xamarin. Isso permite cenários em que diferentes textos explicativos podem ser exibidos para diferentes anotações.This allows for scenarios where different callouts can be displayed for different annotations. A instância UIView será exibida centralizada acima do texto explicativo existente.The UIView instance will be displayed centered above the existing callout.

Tocando a exibição acessório direita do texto explicativoTapping on the Right Callout Accessory View

Quando o usuário toca o botão Informações na exibição acessório direita do texto explicativo, o evento CalloutAccessoryControlTapped é disparado, que, por sua vez, executa o método OnCalloutAccessoryControlTapped:When the user taps on the Information button in the right callout accessory view, the CalloutAccessoryControlTapped event fires, which in turn executes the OnCalloutAccessoryControlTapped method:

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));
    }
}

Esse método abre um navegador da Web e navega para o endereço armazenado na propriedade CustomMKAnnotationView.Url.This method opens a web browser and navigates to the address stored in the CustomMKAnnotationView.Url property. Observe que o endereço foi definido durante a criação da coleção CustomPin no projeto da biblioteca do .NET Standard.Note that the address was defined when creating the CustomPin collection in the .NET Standard library project.

Cancelando a seleção da anotaçãoDeselecting the Annotation

Quando a anotação é exibida e o usuário toca o mapa, o evento DidDeselectAnnotationView é disparado, que, por sua vez, executa o método OnDidDeselectAnnotationView:When the annotation is displayed and the user taps on the map, the DidDeselectAnnotationView event fires, which in turn executes the OnDidDeselectAnnotationView method:

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

Esse método garante que, quando o texto explicativo existente não estiver selecionado, a parte estendida do texto explicativo (a imagem do logotipo do Xamarin) também não será mais exibida e seus recursos serão liberados.This method ensures that when the existing callout is not selected, the extended part of the callout (the image of the Xamarin logo) will also stop being displayed, and its resources will be released.

Para obter mais informações sobre como personalizar uma instância MKMapView, confira Mapas do iOS.For more information about customizing a MKMapView instance, see iOS Maps.

Criando o renderizador personalizado no AndroidCreating the Custom Renderer on Android

As seguintes capturas de tela mostram o mapa, antes e após a personalização:The following screenshots show the map, before and after customization:

As capturas de tela mostram um dispositivo móvel com um marcador comum e um marcador personalizado.

No Android, o marcador é chamado de marcador e pode ser uma imagem personalizada ou um marcador definido pelo sistema de várias cores.On Android the pin is called a marker, and can either be a custom image or a system-defined marker of various colors. Os marcadores podem mostrar uma janela de informações, que é exibida na resposta ao toque do usuário no marcador.Markers can show an info window, which is displayed in response to the user tapping on the marker. A janela de informações exibe as propriedades Label e Address da instância Pin e pode ser personalizada para incluir outros tipos de conteúdo.The info window displays the Label and Address properties of the Pin instance, and can be customized to include other content. No entanto, apenas uma janela de informações pode ser mostrada por vez.However, only one info window can be shown at once.

O exemplo de código a seguir mostra o renderizador personalizado para a plataforma Android:The following code example shows the custom renderer for the Android platform:

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

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

        protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> 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);
        }
        ...
    }
}

Desde que o renderizador personalizado esteja anexado a um novo Xamarin.Forms elemento, o OnElementChanged método recupera a lista de Pins personalizados do controle.Provided that the custom renderer is attached to a new Xamarin.Forms element, the OnElementChanged method retrieves the list of custom pins from the control. Depois que a instância GoogleMap estiver disponível, a substituição OnMapReady será invocada.Once the GoogleMap instance is available, the OnMapReady override will be invoked. Esse método registra um manipulador de eventos para o evento InfoWindowClick, que é disparado quando a janela de informações recebe um clique e tem a assinatura cancelada somente quando o elemento ao qual o renderizador está anexado é alterado.This method registers an event handler for the InfoWindowClick event, which fires when the info window is clicked, and is unsubscribed from only when the element the renderer is attached to changes. A substituição OnMapReady também chama o método SetInfoWindowAdapter para especificar que a instância da classe CustomMapRenderer fornecerá os métodos para personalizar a janela de informações.The OnMapReady override also calls the SetInfoWindowAdapter method to specify that the CustomMapRenderer class instance will provide the methods to customize the info window.

A classe CustomMapRenderer implementa a interface GoogleMap.IInfoWindowAdapter para personalizar a janela de informações.The CustomMapRenderer class implements the GoogleMap.IInfoWindowAdapter interface to customize the info window. Essa interface especifica que os seguintes métodos precisam ser implementados:This interface specifies that the following methods must be implemented:

  • public Android.Views.View GetInfoWindow(Marker marker) – esse método é chamado para retornar uma janela de informações personalizada para um marcador.public Android.Views.View GetInfoWindow(Marker marker) – This method is called to return a custom info window for a marker. Se ele retornar null, a renderização de janela padrão será usada.If it returns null, then the default window rendering will be used. Se ele retornar uma View, essa View será colocada dentro do quadro da janela de informações.If it returns a View, then that View will be placed inside the info window frame.
  • public Android.Views.View GetInfoContents(Marker marker) – esse método é chamado para retornar uma View que traz o conteúdo da janela de informações e só será chamado se o método GetInfoWindow retornar null.public Android.Views.View GetInfoContents(Marker marker) – This method is called to return a View containing the content of the info window, and will only be called if the GetInfoWindow method returns null. Se ele retornar null, a renderização padrão do conteúdo da janela de informações será usada.If it returns null, then the default rendering of the info window content will be used.

No aplicativo de exemplo, somente o conteúdo da janela de informações é personalizado e, portanto, o método GetInfoWindow retorna null para permitir isso.In the sample application, only the info window content is customized, and so the GetInfoWindow method returns null to enable this.

Personalizando o marcadorCustomizing the Marker

O ícone usado para representar um marcador pode ser personalizado por meio da chamada ao método MarkerOptions.SetIcon.The icon used to represent a marker can be customized by calling the MarkerOptions.SetIcon method. Isso pode ser feito pela substituição do método CreateMarker, que é invocado para cada Pin adicionado ao mapa:This can be accomplished by overriding the CreateMarker method, which is invoked for each Pin that's added to the map:

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;
}

Esse método cria uma instância MarkerOption para cada instância Pin.This method creates a new MarkerOption instance for each Pin instance. Depois de definir a posição, o rótulo e o endereço do marcador, seu ícone será definido com o método SetIcon.After setting the position, label, and address of the marker, its icon is set with the SetIcon method. Esse método usa um objeto BitmapDescriptor que contém os dados necessários para renderizar o ícone, com a classe BitmapDescriptorFactory fornecendo métodos auxiliares para simplificar a criação do BitmapDescriptor.This method takes a BitmapDescriptor object containing the data necessary to render the icon, with the BitmapDescriptorFactory class providing helper methods to simplify the creation of the BitmapDescriptor. Para obter mais informações sobre como usar a classe BitmapDescriptorFactory para personalizar um marcador, confira Personalizando um marcador.For more information about using the BitmapDescriptorFactory class to customize a marker, see Customizing a Marker.

Observação

Se necessário, o método GetMarkerForPin pode ser invocado no renderizador de mapa para recuperar um Marker de um Pin.If required, the GetMarkerForPin method can be invoked in your map renderer to retrieve a Marker from a Pin.

Personalizando a janela de informaçõesCustomizing the Info Window

Quando um usuário toca o marcador, o método GetInfoContents é executado, desde que o método GetInfoWindow retorne null.When a user taps on the marker, the GetInfoContents method is executed, provided that the GetInfoWindow method returns null. O seguinte exemplo de código mostra o método GetInfoContents:The following code example shows the GetInfoContents method:

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<TextView>(Resource.Id.InfoWindowTitle);
        var infoSubtitle = view.FindViewById<TextView>(Resource.Id.InfoWindowSubtitle);

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

        return view;
    }
    return null;
}

Esse método retorna uma View que traz o conteúdo da janela de informações.This method returns a View containing the contents of the info window. Isso é feito da seguinte maneira:This is accomplished as follows:

  • Uma instância LayoutInflater é recuperada.A LayoutInflater instance is retrieved. Isso é usado para criar uma instância de um arquivo XML de layout em sua View correspondente.This is used to instantiate a layout XML file into its corresponding View.
  • O método GetCustomPin é chamado para retornar os dados de marcador personalizados para a janela de informações.The GetCustomPin method is called to return the custom pin data for the info window.
  • O layout XamarinMapInfoWindow é inflado se a propriedade CustomPin.Name é igual a Xamarin.The XamarinMapInfoWindow layout is inflated if the CustomPin.Name property is equal to Xamarin. Caso contrário, o layout MapInfoWindow é inflado.Otherwise, the MapInfoWindow layout is inflated. Isso permite cenários em que diferentes layouts da janela de informações podem ser exibidos para diferentes marcadores.This allows for scenarios where different info window layouts can be displayed for different markers.
  • Os recursos InfoWindowTitle e InfoWindowSubtitle são recuperados do layout inflado e suas propriedades Text são definidas com os dados correspondentes por meio da instância Marker, desde que os recursos não sejam null.The InfoWindowTitle and InfoWindowSubtitle resources are retrieved from the inflated layout, and their Text properties are set to the corresponding data from the Marker instance, provided that the resources are not null.
  • A instância View é retornada para exibição no mapa.The View instance is returned for display on the map.

Observação

Uma janela de informações não é uma View dinâmica.An info window is not a live View. Em vez disso, o Android converterá a View em um bitmap estático e os exibirá como uma imagem.Instead, Android will convert the View to a static bitmap and display that as an image. Isso significa que, embora uma janela de informações possa responder a um evento de clique, ela não pode responder a eventos de toque ou gestos e os controles individuais na janela de informações não podem responder a seus próprios eventos de clique.This means that while an info window can respond to a click event, it cannot respond to any touch events or gestures, and the individual controls in the info window cannot respond to their own click events.

Clicando na janela de informaçõesClicking on the Info Window

Quando o usuário clica a janela de informações, o evento InfoWindowClick é disparado, que, por sua vez, executa o método OnInfoWindowClick:When the user clicks on the info window, the InfoWindowClick event fires, which in turn executes the OnInfoWindowClick method:

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);
    }
}

Esse método abre um navegador da Web e navega para o endereço armazenado na propriedade Url da instância CustomPin recuperada para o Marker.This method opens a web browser and navigates to the address stored in the Url property of the retrieved CustomPin instance for the Marker. Observe que o endereço foi definido durante a criação da coleção CustomPin no projeto da biblioteca do .NET Standard.Note that the address was defined when creating the CustomPin collection in the .NET Standard library project.

Para obter mais informações sobre como personalizar uma instância MapView, confira API de Mapas.For more information about customizing a MapView instance, see Maps API.

Criando o renderizador personalizado na Plataforma Universal do WindowsCreating the Custom Renderer on the Universal Windows Platform

As seguintes capturas de tela mostram o mapa, antes e após a personalização:The following screenshots show the map, before and after customization:

As capturas de tela mostram um dispositivo móvel com um ícone de mapa comum e um ícone de mapa personalizado.

No UWP, o marcador é chamado de ícone de mapa e pode ser uma imagem personalizada ou a imagem padrão definida pelo sistema.On UWP the pin is called a map icon, and can either be a custom image or the system-defined default image. Um ícone de mapa pode mostrar um UserControl, que é exibido em resposta ao toque do usuário no ícone de mapa.A map icon can show a UserControl, which is displayed in response to the user tapping on the map icon. O UserControl pode exibir qualquer conteúdo, incluindo as propriedades Label e Address da instância Pin.The UserControl can display any content, including the Label and Address properties of the Pin instance.

O seguinte exemplo de código mostra o renderizador personalizado do UWP:The following code example shows the UWP custom renderer:

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

        protected override void OnElementChanged(ElementChangedEventArgs<Map> 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);                    
                }
            }
        }
        ...
    }
}

O OnElementChanged método executa as seguintes operações, desde que o renderizador personalizado esteja anexado a um novo Xamarin.Forms elemento:The OnElementChanged method performs the following operations, provided that the custom renderer is attached to a new Xamarin.Forms element:

  • Ele limpa a coleção MapControl.Children para remover os elementos de interface do usuário existentes do mapa, antes de registrar um manipulador de eventos para o evento MapElementClick.It clears the MapControl.Children collection to remove any existing user interface elements from the map, before registering an event handler for the MapElementClick event. Esse evento é disparado quando o usuário toca um MapElement ou clica nele no MapControl e tem a assinatura cancelada somente quando o elemento ao qual o renderizador está anexado é alterado.This event fires when the user taps or clicks on a MapElement on the MapControl, and is unsubscribed from only when the element the renderer is attached to changes.
  • Cada marcador da coleção customPins é exibido na localização geográfica correta no mapa da seguinte maneira:Each pin in the customPins collection is displayed at the correct geographic location on the map as follows:
    • A localização para o marcador é criado como uma instância Geopoint.The location for the pin is created as a Geopoint instance.
    • Uma instância MapIcon é criada para representar o marcador.A MapIcon instance is created to represent the pin.
    • A imagem usada para representar o MapIcon é especificada definindo a propriedade MapIcon.Image.The image used to represent the MapIcon is specified by setting the MapIcon.Image property. No entanto, não há a garantia de que a imagem do ícone de mapa seja sempre mostrada, pois ela pode ser obscurecida por outros elementos no mapa.However, the map icon's image is not always guaranteed to be shown, as it may be obscured by other elements on the map. Portanto, a propriedade CollisionBehaviorDesired do ícone de mapa é definida como MapElementCollisionBehavior.RemainVisible, para garantir que ela permaneça visível.Therefore, the map icon's CollisionBehaviorDesired property is set to MapElementCollisionBehavior.RemainVisible, to ensure that it remains visible.
    • A localização do MapIcon é especificado definindo a propriedade MapIcon.Location.The location of the MapIcon is specified by setting the MapIcon.Location property.
    • A propriedade MapIcon.NormalizedAnchorPoint é definida como a localização aproximada do ponteiro na imagem.The MapIcon.NormalizedAnchorPoint property is set to the approximate location of the pointer on the image. Se essa propriedade retiver seu valor padrão de (0,0), que representa o canto superior esquerdo da imagem, as alterações no nível de aplicação de zoom do mapa poderão fazer com que a imagem aponte para uma localização diferente.If this property retains its default value of (0,0), which represents the upper left corner of the image, changes in the zoom level of the map may result in the image pointing to a different location.
    • A instância MapIcon é adicionada à coleção MapControl.MapElements.The MapIcon instance is added to the MapControl.MapElements collection. Isso resulta na exibição do ícone de mapa no MapControl.This results in the map icon being displayed on the MapControl.

Observação

Ao usar a mesma imagem para vários ícones de mapa, a instância RandomAccessStreamReference deve ser declarada no nível de página ou de aplicativo para melhor desempenho.When using the same image for multiple map icons, the RandomAccessStreamReference instance should be declared at the page or application level for best performance.

Exibindo o UserControlDisplaying the UserControl

Quando um usuário toca o ícone de mapa, o método OnMapElementClick é executado.When a user taps on the map icon, the OnMapElementClick method is executed. O seguinte exemplo de código mostra esse método:The following code example shows this method:

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;
        }
    }
}

Esse método cria uma instância UserControl que exibe informações sobre o marcador.This method creates a UserControl instance that displays information about the pin. Isso é feito da seguinte maneira:This is accomplished as follows:

  • A instância MapIcon é recuperada.The MapIcon instance is retrieved.
  • O método GetCustomPin é chamado para retornar os dados de marcador personalizados que serão exibidos.The GetCustomPin method is called to return the custom pin data that will be displayed.
  • Uma instância XamarinMapOverlay é criada para exibir os dados de fixação personalizados.A XamarinMapOverlay instance is created to display the custom pin data. Essa classe é um controle de usuário.This class is a user control.
  • A localização geográfica na qual exibir a instância XamarinMapOverlay no MapControl é criada como uma instância Geopoint.The geographic location at which to display the XamarinMapOverlay instance on the MapControl is created as a Geopoint instance.
  • A instância XamarinMapOverlay é adicionada à coleção MapControl.Children.The XamarinMapOverlay instance is added to the MapControl.Children collection. Essa coleção contém elementos de interface do usuário XAML que serão exibidos no mapa.This collection contains XAML user interface elements that will be displayed on the map.
  • A localização geográfica da instância XamarinMapOverlay no mapa é definida chamando o método SetLocation.The geographic location of the XamarinMapOverlay instance on the map is set by calling the SetLocation method.
  • A localização relativa na instância XamarinMapOverlay, que corresponde à localização especificada, é definida chamando o método SetNormalizedAnchorPoint.The relative location on the XamarinMapOverlay instance, that corresponds to the specified location, is set by calling the SetNormalizedAnchorPoint method. Isso garante que as alterações no nível de aplicação de zoom do mapa resultem na exibição constante da instância XamarinMapOverlay na localização correta.This ensures that changes in the zoom level of the map result in the XamarinMapOverlay instance always being displayed at the correct location.

Como alternativa, se as informações sobre o marcador já estiverem sendo exibidas no mapa, o toque no mapa removerá a instância XamarinMapOverlay da coleção MapControl.Children.Alternatively, if information about the pin is already being displayed on the map, tapping on the map removes the XamarinMapOverlay instance from the MapControl.Children collection.

Tocando no botão de informaçõesTapping on the Information Button

Quando o usuário toca o botão Informações no controle de usuário XamarinMapOverlay, o evento Tapped é disparado, que, por sua vez, executa o método OnInfoButtonTapped:When the user taps on the Information button in the XamarinMapOverlay user control, the Tapped event fires, which in turn executes the OnInfoButtonTapped method:

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

Esse método abre um navegador da Web e navega para o endereço armazenado na propriedade Url da instância CustomPin.This method opens a web browser and navigates to the address stored in the Url property of the CustomPin instance. Observe que o endereço foi definido durante a criação da coleção CustomPin no projeto da biblioteca do .NET Standard.Note that the address was defined when creating the CustomPin collection in the .NET Standard library project.

Para obter mais informações sobre como personalizar uma instância MapControl, confira Visão geral de mapas e localização no MSDN.For more information about customizing a MapControl instance, see Maps and Location Overview on MSDN.