지도 핀 사용자 지정

Download Sample 샘플 다운로드

이 문서에서는 각 플랫폼에서 사용자 지정된 핀과 사용자 지정된 핀 데이터 보기가 있는 네이티브 맵을 나타내는 맵 컨트롤에 대한 사용자 지정 렌더러를 만드는 방법을 설명합니다.

모든 Xamarin.Forms 보기의 모양과 동작을 효과적으로 사용자 지정할 수 있습니다. iOS의 Xamarin.Forms 애플리케이션에서 Map를 렌더링하는 경우 MapRenderer 클래스가 인스턴스화되며, 차례로 네이티브 MKMapView 컨트롤이 인스턴스화됩니다. Android 플랫폼에서 MapRenderer 클래스는 네이티브 MapView 컨트롤을 인스턴스화합니다. UWP(유니버설 Windows 플랫폼)에서 MapRenderer 클래스는 네이티브 MapControl을 인스턴스화합니다. Xamarin.Forms 컨트롤에 매핑되는 렌더러 및 네이티브 컨트롤 클래스에 대한 자세한 내용은 렌더러 기본 클래스 및 네이티브 컨트롤을 참조하세요.

다음 다이어그램은 Map 및 이를 구현하는 해당 네이티브 컨트롤 간의 관계를 보여줍니다.

Relationship Between the Map Control and the Implementing Native Controls

렌더링 프로세스는 각 플랫폼에서 Map에 대한 사용자 지정 렌더러를 만들어 플랫폼별 사용자 지정을 구현하는 데 사용할 수 있습니다. 이 작업을 수행하는 프로세스는 다음과 같습니다.

  1. Xamarin.Forms 사용자 지정 맵을 만듭니다.
  2. Xamarin.Forms에서 사용자 지정 맵을 사용합니다.
  3. 각 플랫폼의 맵에 대한 사용자 지정 렌더러를 만듭니다.

이제 각 항목을 차례로 설명하여, 사용자 지정된 핀이 있는 네이티브 맵과 각 플랫폼에 있는 핀 데이터의 사용자 지정된 보기를 표시하는 CustomMap 랜더러를 구현합니다.

참고 항목

Xamarin.Forms.Maps는 사용 전에 초기화되고 구성되어야 합니다. 자세한 내용은 Maps Control를 참조하세요.

사용자 지정 맵 만들기

다음 코드 예제와 같이 Map 클래스를 서브클래싱하여 사용자 지정 맵 컨트롤을 만들 수 있습니다.

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

CustomMap 컨트롤은 .NET Standard 라이브러리 프로젝트에서 생성되고 사용자 지정 맵에 대한 API를 정의합니다. 사용자 지정 맵은 각 플랫폼의 네이티브 맵 컨트롤을 통해 랜더링될 CustomPin 개체의 컬렉션을 나타내는 CustomPins 속성을 노출합니다. CustomPin 클래스는 다음 코드 예제에 나와 있습니다.

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

이 클래스는 CustomPinPin 클래스의 속성으로 상속하고 NameUrl 속성을 추가하는 것으로 정의합니다.

사용자 지정 맵 사용

CustomMap 컨트롤은 해당 위치의 네임스페이스를 선언하고 사용자 지정 맵 컨트롤의 네임스페이스 접두사를 사용하여 .NET Standard 라이브러리 프로젝트의 XAML에서 참조할 수 있습니다. 다음 코드 예제에서는 CustomMap 컨트롤을 XAML 페이지에서 사용하는 방법을 보여줍니다.

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

local 네임스페이스 접두사는 원하는 이름으로 지정할 수 있습니다. 그러나 clr-namespaceassembly 값은 사용자 지정 맵의 세부 정보와 일치해야 합니다. 네임스페이스가 선언되면 사용자 지정 맵을 참조하는 데 사용됩니다.

다음 코드 예제에서는 C# 페이지에서 CustomMap 컨트롤을 사용하는 방법을 보여줍니다.

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

CustomMap 인스턴스는 각 플랫폼에 네이티브 맵을 표시하는 데 사용됩니다. MapType 속성은 MapType 열거형에 정의된 가능한 값을 사용하여 Map의 표시 스타일을 설정합니다.

맵의 위치 및 맵에 포함된 핀은 다음 코드 예제와 같이 초기화됩니다.

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

이 초기화에서는 사용자 지정 핀을 추가하고 PositionDistance에서 MapSpan을 생성하여 맵의 위치와 확대/축소 수준을 변경하는 MoveToRegion 메서드를 사용하여 맵 보기를 배치합니다.

이제 각 애플리케이션 프로젝트에 사용자 지정 렌더러를 추가하여 네이티브 맵 컨트롤을 사용자 지정할 수 있습니다.

각 플랫폼에서 사용자 지정 렌더러 만들기

사용자 지정 렌더러 클래스를 만드는 프로세스는 다음과 같습니다.

  1. 사용자 지정 맵을 렌더링하는 MapRenderer 클래스의 서브클래스를 만듭니다.
  2. 사용자 지정 맵을 렌더링하고 이를 사용자 지정하기 위한 논리를 작성하는 OnElementChanged 메서드를 재정의합니다. 이 메서드는 해당 Xamarin.Forms 사용자 지정 맵이 생성될 때 호출됩니다.
  3. 사용자 지정 렌더러 클래스에 ExportRenderer 특성을 추가하여 Xamarin.Forms 사용자 지정 맵을 렌더링하는 데 사용하도록 지정합니다. 이 특성은 사용자 지정 렌더러를 Xamarin.Forms에 등록하는 데 사용됩니다.

참고 항목

각 플랫폼 프로젝트에서 사용자 지정 렌더러를 제공하는 것은 선택 사항입니다. 사용자 지정 렌더러가 등록되지 않은 경우 컨트롤의 기본 클래스에 대한 기본 렌더러가 사용됩니다.

다음 다이어그램은 샘플 애플리케이션에서 각 프로젝트의 책임과 이들 간의 관계를 보여줍니다.

CustomMap Custom Renderer Project Responsibilities

CustomMap 컨트롤은 각 플랫폼의 MapRenderer 클래스에서 파생되는 플랫폼별 렌더러 클래스에 의해 렌더링됩니다. 그러면 다음 스크린샷과 같이 각 CustomMap 컨트롤이 플랫폼별 컨트롤로 렌더링됩니다.

CustomMap on each Platform

MapRenderer 클래스는 해당 네이티브 컨트롤을 렌더링하기 위해 Xamarin.Forms 사용자 지정 맵이 생성될 때 호출되는 OnElementChanged 메서드를 노출합니다. 이 메서드는 OldElementNewElement 속성이 포함된 ElementChangedEventArgs 매개 변수를 가져옵니다. 이러한 속성은 렌더러가 ‘연결된’ Xamarin.Forms 요소와 렌더러가 ‘연결되는’ Xamarin.Forms 요소를 각각 나타냅니다. 샘플 애플리케이션에서 OldElement 속성은 null이고, NewElement 속성은 CustomMap 인스턴스에 대한 참조를 포함합니다.

각 플랫폼별 렌더러 클래스에서 OnElementChanged 메서드의 재정의된 버전은 네이티브 컨트롤 사용자 지정을 수행하는 위치입니다. 플랫폼에서 사용되는 네이티브 컨트롤에 대한 형식화된 참조는 Control 속성을 통해 액세스할 수 있습니다. 또한 렌더링되는 Xamarin.Forms 컨트롤에 대한 참조는 Element 속성을 통해 얻을 수 있습니다.

OnElementChanged 메서드에서 이벤트 처리기를 구독할 때는 다음 코드 예제와 같이 주의해야 합니다.

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

네이티브 컨트롤을 구성하고 사용자 지정 렌더러가 새 Xamarin.Forms 요소에 연결된 경우에만 이벤트 처리기를 구독해야 합니다. 마찬가지로, 구독된 모든 이벤트 처리기는 렌더러가 연결된 요소가 변경될 때만 구독을 취소해야 합니다. 이러한 방식을 채택하면 메모리 누수가 없는 사용자 지정 렌더러를 만들 수 있습니다.

각 사용자 지정 렌더러 클래스는 렌더러를 Xamarin.Forms에 등록하는 ExportRenderer 특성으로 데코레이트됩니다. 이 특성은 렌더링되는 Xamarin.Forms 사용자 지정 컨트롤의 형식 이름과 사용자 지정 렌더러의 형식 이름이라는 두 가지 매개 변수를 사용합니다. 특성의 assembly 접두사는 특성이 전체 어셈블리에 적용되도록 지정합니다.

다음 섹션에서는 각 플랫폼별 사용자 지정 렌더러 클래스의 구현을 설명합니다.

iOS에서 사용자 지정 렌더러 만들기

다음 스크린샷은 사용자 지정 전후의 맵을 보여줍니다.

Screenshots show a mobile device with an ordinary pin and an annotated pin.

iOS에서 핀을 주석이라고 하며, 사용자 지정 이미지 또는 다양한 색의 시스템 정의 핀일 수 있습니다. 주석은 사용자가 주석을 선택하는 것에 응답하여 표시되는 설명선을 선택적으로 표시할 수 있습니다. 설명선에는 Pin 인스턴스의 LabelAddress 속성이 표시되며 선택적으로 왼쪽 및 오른쪽 액세서리 보기가 표시됩니다. 위의 스크린샷에서 왼쪽 액세서리 보기는 monkey의 이미지이며 오른쪽 액세서리 보기는 정보 단추입니다.

다음 코드 예제에서는 iOS 플랫폼용 사용자 지정 렌더러를 보여줍니다.

[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;
            }
        }
        // ...
    }
}

사용자 지정 렌더러가 새 Xamarin.Forms 요소에 연결되어 있는 경우 OnElementChanged 메서드는 MKMapView 인스턴스의 다음 구성을 수행합니다.

  • GetViewForAnnotation 속성은 GetViewForAnnotation 메서드로 설정됩니다. 이 메서드는 주석의 위치가 맵에 표시될 때 호출되고 표시 전에 주석을 사용자 지정하는 데 사용됩니다.
  • CalloutAccessoryControlTapped, DidSelectAnnotationViewDidDeselectAnnotationView 이벤트에 대한 이벤트 처리기가 등록됩니다. 이러한 이벤트는 사용자가 설명선에 오른쪽 액세서리를 태핑하고 사용자가 주석을 각각 선택선택 취소할 때 해제됩니다. 이벤트는 렌더러가 변경 내용에 연결된 요소에서만 등록이 취소됩니다.

주석 표시

GetViewForAnnotation 메서드는 주석의 위치가 맵에 표시될 때 호출되고 표시 전에 주석을 사용자 지정하는 데 사용됩니다. 주석에는 두 부분이 있습니다.

  • MkAnnotation – 주석의 제목, 부제목 및 위치를 포함합니다.
  • MkAnnotationView – 주석을 나타내는 이미지 및 사용자가 주석을 태핑할 때 표시되는 설명선(필요에 따라)을 포함합니다.

GetViewForAnnotation 메서드는 주석 데이터를 포함하는 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 클래스는 CustomPin 인스턴스의 동일한 속성에 해당하는 NameUrl 속성으로 MKAnnotationView 클래스를 확장합니다. 주석nullCustomMKAnnotationView 다음과 같은 경우 새 인스턴스가 만들어집니다.
    • CustomMKAnnotationView.Image 속성이 맵에서 주석을 나타내는 이미지로 설정됩니다.
    • CustomMKAnnotationView.CalloutOffset 속성은 설명선이 주석 위 중앙에 위치하도록 지정하는 CGPoint로 설정됩니다.
    • CustomMKAnnotationView.LeftCalloutAccessoryView 속성은 주석 제목 및 주소 왼쪽에 표시되는 monkey 이미지로 설정됩니다.
    • CustomMKAnnotationView.RightCalloutAccessoryView 속성은 주석 제목 및 주소 오른쪽에 표시되는 정보 단추로 설정됩니다.
    • CustomMKAnnotationView.Name 속성은 GetCustomPin 메서드에서 반환된 CustomPin.Name 속성으로 설정됩니다. 이렇게 하면 주석을 식별할 수 있으므로, 원하는 경우 설명선을 추가로 사용자 지정할 수 있습니다.
    • CustomMKAnnotationView.Url 속성은 GetCustomPin 메서드에서 반환된 CustomPin.Url 속성으로 설정됩니다. 사용자가 오른쪽 설명선 액세서리 보기에 표시된 단추를 누를 때 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);
    }
}

이 메서드는 선택한 주석의 Name 속성이 Xamarin으로 설정된 경우 Xamarin 로고 이미지를 포함하는 UIView 인스턴스를 추가하여 기존 설명선(왼쪽 및 오른쪽 액세서리 보기 포함)을 확장합니다. 이렇게 하면 여러 주석에 대해 다양한 설명선을 표시할 수 있는 시나리오를 허용합니다. UIView 인스턴스는 기존 설명선 위 가운데에 표시됩니다.

오른쪽 설명선 액세서리 보기 태핑

사용자가 오른쪽 설명선 액세서리 보기의 정보 단추를 누르면 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 속성에 저장된 주소로 이동합니다. .NET Standard 라이브러리 프로젝트에서 CustomPin 컬렉션을 만들 때 주소가 정의되었습니다.

주석 취소

주석이 표시되고 사용자가 맵을 누르면 DidDeselectAnnotationView 이벤트가 발생하고, 차례로 OnDidDeselectAnnotationView 메서드가 실행됩니다.

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

이 메서드를 사용하면 기존 설명선을 선택하지 않은 경우 설명선의 확장된 부분(Xamarin 로고 이미지)도 표시되는 것을 중지하고 해당 리소스가 해제됩니다.

MKMapView 인스턴스 사용자 지정에 대한 자세한 내용은 iOS Maps를 참조하세요.

Android에서 사용자 지정 렌더러 만들기

다음 스크린샷은 사용자 지정 전후의 맵을 보여줍니다.

Screenshots show a mobile device with an ordinary marker and a customized marker.

Android에서 핀을 표식이라고 하며, 사용자 지정 이미지 또는 다양한 색의 시스템 정의 표식일 수 있습니다. 표식은 사용자가 표식을 탭핑하는 것에 응답하여 나타내는 정보 창을 표시할 수 있습니다. 정보 창에는 Pin 인스턴스의 LabelAddress 속성이 표시되고 다른 콘텐츠를 포함하도록 지정할 수 있습니다. 그러나 한 번에 하나의 정보 창만 표시할 수 있습니다.

다음 코드 예제에서는 Android 플랫폼용 사용자 지정 렌더러를 보여줍니다.

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

사용자 지정 렌더러가 새 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;
}

이 메서드는 각 Pin 인스턴스에 대해 새 MarkerOption 인스턴스를 만듭니다. 표식의 위치, 레이블 및 주소를 설정한 후 해당 아이콘이 SetIcon 메서드로 설정됩니다. 이 메서드는 아이콘을 렌더링하는 데 필요한 데이터가 포함된 BitmapDescriptor개체를 사용하고 BitmapDescriptorFactory 클래스는 BitmapDescriptor의 생성을 간소화하는 도우미 메서드를 제공합니다. BitmapDescriptorFactory 클래스를 사용하여 표식을 사용자 지정하는 방법에 대한 자세한 내용은 표식 사용자 지정을 참조하세요.

참고 항목

필요한 경우 맵 렌더러에서 GetMarkerForPin 메서드를 호출하여 Pin에서 Marker를 검색할 수 있습니다.

정보 창 사용자 지정

사용자가 표식을 누를 때 GetInfoWindow 메서드가 null을 반환하면 GetInfoContents 메서드가 실행됩니다. 다음 코드 예제는 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<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;
}

이 메서드는 정보 창의 메서드를 포함하는 View를 반환합니다. 이는 다음과 같이 수행됩니다.

  • LayoutInflater 인스턴스가 검색됩니다. 이는 레이아웃 XML 파일을 해당 View로 인스턴스화하는 데 사용됩니다.
  • 정보 창에 대한 사용자 지정 핀 데이터를 반환하기 위해 GetCustomPin 메서드가 호출됩니다.
  • CustomPin.Name 속성이 Xamarin와 같은 경우 XamarinMapInfoWindow 레이아웃이 확장됩니다. 그렇지 않으면 MapInfoWindow 레이아웃이 확장됩니다. 이렇게 하면 여러 표식에 대해 다양한 정보 창 레이아웃을 표시할 수 있는 시나리오를 허용합니다.
  • InfoWindowTitleInfoWindowSubtitle 리소스는 확장된 레이아웃에서 검색되고, 리소스가 null이 아닌 경우 Text 속성이 Marker 인스턴스에서 해당 데이터로 설정됩니다.
  • 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);
    }
}

이 메서드는 웹 브라우저를 열고 Marker에 대해 검색된 CustomPin 인스턴스의 Url 속성에 저장된 주소로 이동합니다. .NET Standard 라이브러리 프로젝트에서 CustomPin 컬렉션을 만들 때 주소가 정의되었습니다.

MapView 인스턴스 사용자 지정에 대한 자세한 내용은 Maps API를 참조하세요.

유니버설 Windows 플랫폼에서 사용자 지정 렌더러 만들기

다음 스크린샷은 사용자 지정 전후의 맵을 보여줍니다.

Screenshots show a mobile device with an ordinary map icon and a customized map icon.

UWP에서 핀은 맵 아이콘이라고 하며, 사용자 지정 이미지 또는 시스템 정의 기본 이미지일 수 있습니다. 맵 아이콘은 사용자가 맵 아이콘을 탭핑하는 것에 응답하여 나타내는 UserControl을 표시할 수 있습니다. UserControlPin 인스턴스의 LabelAddress 속성을 포함한 모든 내용을 표시할 수 있습니다.

다음 코드 예제는 UWP 사용자 지정 렌더러를 보여줍니다.

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

사용자 지정 렌더러가 새 Xamarin.Forms 요소에 연결되어 있는 경우 OnElementChanged 메서드가 다음 작업을 수행합니다.

  • MapElementClick 이벤트에 대한 이벤트 처리기를 등록하기 전에 맵에서 기존 사용자 인터페이스 요소를 제거하기 위해 MapControl.Children 컬렉션을 지웁니다. 이 이벤트는 사용자가 MapControl에서 MapElement를 탭하거나 클릭할 때 발생하며, 렌더러가 변경 내용에 연결된 경우에만 구독 취소됩니다.
  • 컬렉션의 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 인스턴스가 생성됩니다. 이 클래스는 사용자 정의 컨트롤입니다.
  • MapControl에서 XamarinMapOverlay 인스턴스를 표시할 지리적 위치는 Geopoint 인스턴스로 생성됩니다.
  • XamarinMapOverlay 인스턴스가 MapControl.Children 컬렉션에 추가되었습니다. 이 컬렉션에는 맵에 표시될 XAML 사용자 인터페이스 요소가 포함되어 있습니다.
  • 맵에서 XamarinMapOverlay 인스턴스의 지리적 위치는 SetLocation 메서드를 호출하여 설정됩니다.
  • 지정된 위치에 해당하는 XamarinMapOverlay 인스턴스의 상대 위치는 SetNormalizedAnchorPoint 메서드를 호출하여 설정됩니다. 이렇게 하면 XamarinMapOverlay 인스턴스가 항상 올바른 위치에 표시되는 맵 결과의 확대/축소 수준 변경이 보장됩니다.

또는 핀에 대한 정보가 이미 맵에 표시되어 있는 경우 맵을 누르면 MapControl.Children 컬렉션에서 XamarinMapOverlay 인스턴스가 제거됩니다.

정보 단추 누르기

사용자가 XamarinMapOverlay 사용자 정의 컨트롤에서 정보 단추를 누르면 Tapped 이벤트가 발생하며, 차례로 OnInfoButtonTapped 메서드가 실행됩니다.

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

이 메서드는 웹 브라우저를 열고 CustomPin 인스턴스의 Url 속성에 저장된 주소로 이동합니다. .NET Standard 라이브러리 프로젝트에서 CustomPin 컬렉션을 만들 때 주소가 정의되었습니다.

MapControl 인스턴스 사용자 지정에 대한 자세한 내용은 MSDN의 맵 및 위치 개요를 참조하세요.