Xamarin Forms 自定义地图 Pin 图 [英] Xamarin Forms Custom Map Pin

查看:22
本文介绍了Xamarin Forms 自定义地图 Pin 图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我正在开发的其中一个应用程序中,我需要使用自定义地图图钉,并且我遵循了 Xamarin 上的指南 https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/map/customized-pin/ 以及借用他们的示例代码来尝试制作我自己的示例.

它在一定程度上可以使信息窗口实际更新为自定义布局,但地图图钉永远不会改变.

我的 CustomMapRenderer:

使用系统;使用 System.Collections.Generic;使用 System.ComponentModel;使用 Android.Content;使用 Android.Gms.Maps;使用 Android.Gms.Maps.Model;使用 Android.Widget;使用 Xamarin.Forms;使用 Xamarin.Forms.Maps;使用 Xamarin.Forms.Maps.Android;使用 WorkingWithMaps.Droid.Renderers;使用 WorkingWithMaps;[组装:ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]命名空间 WorkingWithMaps.Droid.Renderers{公共类 CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter, IOnMapReadyCallback{谷歌地图;列表自定义引脚;bool isDrawn;protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs e){base.OnElementChanged(e);if (e.OldElement != null){map.InfoWindowClick -= OnInfoWindowClick;}if (e.NewElement != null){var formsMap = (CustomMap)e.NewElement;customPins = formsMap.CustomPins;((MapView)Control).GetMapAsync(this);}}void IOnMapReadyCallback.OnMapReady(GoogleMap googleMap){地图 = 谷歌地图;map.SetInfoWindowAdapter(this);map.InfoWindowClick += OnInfoWindowClick;this.NativeMap = googleMap;}protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e){base.OnElementPropertyChanged(sender, e);if (e.PropertyName.Equals("VisibleRegion") && !isDrawn){地图.清除();foreach(customPins 中的 var pin){var 标记 = 新的 MarkerOptions();标记.SetPosition(新的LatLng(pin.Pin.Position.Latitude,pin.Pin.Position.Longitude));标记.SetTitle(pin.Pin.Label);标记.SetSnippet(pin.Pin.Address);marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));map.AddMarker(marker);}isDrawn = true;}}protected override void OnLayout(bool changed, int l, int t, int r, int b){base.OnLayout(改变,l,t,r,b);如果(改变){isDrawn = 假;}}void OnInfoWindowClick(对象发送者,GoogleMap.InfoWindowClickEventArgs e){var customPin = GetCustomPin(e.Marker);if (customPin == null){throw new Exception("找不到自定义引脚");}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);}}public Android.Views.View GetInfoContents(Marker 标记){var inflater = Android.App.Application.Context.GetSystemService(Context.LayoutInflaterService) as Android.Views.LayoutInflater;如果(充气器!= null){Android.Views.View 视图;var customPin = GetCustomPin(marker);if (customPin == null){throw new Exception("找不到自定义引脚");}if (customPin.Id == "Xamarin"){view = inflater.Inflate(Resource.Layout.XamarinMapInfoWindow, null);}别的{view = inflater.Inflate(Resource.Layout.MapInfoWindow, null);}var infoTitle = view.FindViewById(Resource.Id.InfoWindowTitle);var infoSubtitle = view.FindViewById(Resource.Id.InfoWindowSubtitle);如果(信息标题!= null){infoTitle.Text = 标记.标题;}如果(信息字幕!= null){infoSubtitle.Text = marker.Snippet;}返回视图;}返回空;}公共 Android.Views.View GetInfoWindow(Marker 标记){返回空;}CustomPin GetCustomPin(标记注解){var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);foreach(customPins 中的 var pin){如果(pin.Pin.Position == 位置){回位针;}}返回空;}}}

和我的地图页面,也大量借鉴了 Xamarin 的使用地图指南

using Plugin.Geolocator;使用系统;使用 System.Collections.Generic;使用 System.Diagnostics;使用 Xamarin.Forms;使用 Xamarin.Forms.Maps;使用 Xamarin.Forms.Xaml;命名空间 WorkingWithMaps{[XamlCompilation(XamlCompilationOptions.Compile)]公共部分类 MainPage : ContentPage{自定义地图;地理编码器 geoCoder;字符串导航添加;公共主页(){初始化组件();var maplocator = CrossGeolocator.Current;maplocator.DesiredAccuracy = 1;geoCoder = new Geocoder();地图 = 新的自定义地图{高度请求 = 100,宽度请求 = 960,VerticalOptions = LayoutOptions.FillAndExpand,IsShowingUser = true};map.MapType = MapType.Street;map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(55.237208, 10.479160), Distance.FromMeters(500)));map.IsShowingUser = true;var street = new Button { Text = "Street" };var hybrid = new Button { Text = "Hybrid" };var卫星=新按钮{文本=卫星"};street.Clicked += HandleClickedAsync;hybrid.Clicked += HandleClickedAsync;//satellite.Clicked += OnReverseGeocodeButtonClicked;var 段 = 新的 StackLayout{间距 = 30,Horizo​​ntalOptions = LayoutOptions.CenterAndExpand,方向 = StackOrientation.Horizo​​ntal,儿童 = {街道,混合,卫星}};内容 = 新的 StackLayout{Horizo​​ntalOptions = LayoutOptions.Center,儿童 = { 地图,段 }};Device.BeginInvokeOnMainThread(async () =>{尝试{//var currentpos = await maplocator.GetPositionAsync(1000);//map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(currentpos.Latitude, currentpos.Longitude), Distance.FromMeters(500)));如果 (!maplocator.IsListening){等待 maplocator.StartListeningAsync(1000, 50, true);}}捕获(异常前){Debug.WriteLine("Fail" + ex);}});var pin = 新的 CustomPin{Pin = 新 Pin{类型 = PinType.Place,位置 = 新位置(55.240121, 10.469895),标签 = "测试引脚"}};map.CustomPins = 新列表{ 别针 };map.Pins.Add(pin.Pin);map.PropertyChanged += (sender, e) =>{Debug.WriteLine(e.PropertyName + "刚刚改了!");if (e.PropertyName == "VisibleRegion" && map.VisibleRegion != null)计算边界坐标(map.VisibleRegion);};maplocator.PositionChanged += (sender, e) =>{var 位置 = e.Position;map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(position.Latitude, position.Longitude), Distance.FromKilometers(2)));};}///<总结>//////</总结>///<param name="sender"></param>///<param name="e"></param>//async void OnReverseGeocodeButtonClicked(object sender, EventArgs e)//{//var possibleAddresses = await geoCoder.GetAddressesForPositionAsync(pin.Position);//navAdd += possibleAddresses.ElementAt(0) + "
";//切换 (Device.OS)//{//案例 TargetPlatform.iOS://Device.OpenUri(new Uri(string.Format("http://maps.apple.com/?q={0}", WebUtility.UrlEncode(navAdd))));//           休息;//case TargetPlatform.Android://Device.OpenUri(new Uri(string.Format("geo:0,0?q={0}", WebUtility.UrlEncode(navAdd))));//           休息;//case TargetPlatform.Windows://case TargetPlatform.WinPhone://Device.OpenUri(new Uri(string.Format("bingmaps:?where={0}", Uri.EscapeDataString(navAdd))));//           休息;//}//}void HandleClickedAsync(对象发送者,EventArgs e){var b = 发件人为按钮;开关(b.Text){案例街道":map.MapType = MapType.Street;休息;案例混合":map.MapType = MapType.Hybrid;休息;案例卫星":map.MapType = MapType.Satellite;休息;}}静态无效计算边界坐标(MapSpan 区域){var center = region.Center;var halfheightDegrees = region.LatitudeDegrees/2;var halfwidthDegrees = region.LongitudeDegrees/2;var left = center.Longitude - halfwidthDegrees;var right = center.Longitude + halfwidthDegrees;var top = center.Latitude + halfheightDegrees;var bottom = center.Latitude - halfheightDegrees;if (left <-180) left = 180 + (180 + left);如果 (右 > 180) 右 = (右 - 180) - 180;Debug.WriteLine("边界框:");Debug.WriteLine(" " + top);Debug.WriteLine(" " + left + " " + right);Debug.WriteLine(" " + 底部);}}}

除了上述问题之外,该实现还导致 IsShowingUser = True 不再像

那样起作用

var currentpos = await maplocator.GetPositionAsync(1000);

抛出异常.

Github 存储库:

In one of the apps I'm working on I require the use of custom map pins and I've followed the guide on Xamarin https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/map/customized-pin/ as well as borrowed their sample code to try and make my own example.

It works to a degree in such that the info window is actually updated to the custom layout but the map pin never changes.

My CustomMapRenderer:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using Android.Content;
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using Xamarin.Forms.Maps.Android;
using WorkingWithMaps.Droid.Renderers;
using WorkingWithMaps;

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace WorkingWithMaps.Droid.Renderers
{
    public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter, IOnMapReadyCallback
    {
        GoogleMap map;
        List<CustomPin> customPins;
        bool isDrawn;



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

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

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

        void IOnMapReadyCallback.OnMapReady(GoogleMap googleMap)
        {

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


        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName.Equals("VisibleRegion") && !isDrawn)
            {
                map.Clear();

                foreach (var pin in customPins)
                {
                    var marker = new MarkerOptions();
                    marker.SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude));
                    marker.SetTitle(pin.Pin.Label);
                    marker.SetSnippet(pin.Pin.Address);
                    marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));

                    map.AddMarker(marker);
                }
                isDrawn = true;
            }
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);

            if (changed)
            {
                isDrawn = false;
            }
        }

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

        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.Id == "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;
        }

        public Android.Views.View GetInfoWindow(Marker marker)
        {
            return null;
        }

        CustomPin GetCustomPin(Marker annotation)
        {
            var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
            foreach (var pin in customPins)
            {
                if (pin.Pin.Position == position)
                {
                    return pin;
                }
            }
            return null;
        }
    }
}

and my map page, also heavily borrowed from Xamarin's working with maps guide

using Plugin.Geolocator;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
using Xamarin.Forms.Xaml;

namespace WorkingWithMaps
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MainPage : ContentPage
    {
        CustomMap map;
        Geocoder geoCoder;
        String navAdd;

        public MainPage()
        {
            InitializeComponent();

            var maplocator = CrossGeolocator.Current;
            maplocator.DesiredAccuracy = 1;
            geoCoder = new Geocoder();

            map = new CustomMap
            {
                HeightRequest = 100,
                WidthRequest = 960,
                VerticalOptions = LayoutOptions.FillAndExpand,
                IsShowingUser = true
            };

            map.MapType = MapType.Street;
            map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(55.237208, 10.479160), Distance.FromMeters(500)));
            map.IsShowingUser = true;

            var street = new Button { Text = "Street" };
            var hybrid = new Button { Text = "Hybrid" };
            var satellite = new Button { Text = "Satellite" };
            street.Clicked += HandleClickedAsync;
            hybrid.Clicked += HandleClickedAsync;
            //satellite.Clicked += OnReverseGeocodeButtonClicked;
            var segments = new StackLayout
            {
                Spacing = 30,
                HorizontalOptions = LayoutOptions.CenterAndExpand,
                Orientation = StackOrientation.Horizontal,
                Children = { street, hybrid, satellite }
            };

            Content = new StackLayout
            {
                HorizontalOptions = LayoutOptions.Center,
                Children = { map, segments }
            };

            Device.BeginInvokeOnMainThread(async () =>
            {
                try
                {

                    //var currentpos = await maplocator.GetPositionAsync(1000);
                    //map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(currentpos.Latitude, currentpos.Longitude), Distance.FromMeters(500)));

                    if (!maplocator.IsListening)
                    {
                        await maplocator.StartListeningAsync(1000, 50, true);
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("Fail" + ex);
                }
            });

            var pin = new CustomPin
            {
                Pin = new Pin
                {
                    Type = PinType.Place,
                    Position = new Position(55.240121, 10.469895),
                    Label = "Testing Pins"
                }
            };

            map.CustomPins = new List<CustomPin> { pin };
            map.Pins.Add(pin.Pin);

            map.PropertyChanged += (sender, e) =>
            {
                Debug.WriteLine(e.PropertyName + " just changed!");
                if (e.PropertyName == "VisibleRegion" && map.VisibleRegion != null)
                    CalculateBoundingCoordinates(map.VisibleRegion);
            };

            maplocator.PositionChanged += (sender, e) =>
            {
                var position = e.Position;

                map.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(position.Latitude, position.Longitude), Distance.FromKilometers(2)));
            };
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        //async void OnReverseGeocodeButtonClicked(object sender, EventArgs e)
        //{
        //    var possibleAddresses = await geoCoder.GetAddressesForPositionAsync(pin.Position);
        //    navAdd += possibleAddresses.ElementAt(0) + "
";

        //    switch (Device.OS)
        //    {
        //        case TargetPlatform.iOS:
        //            Device.OpenUri(new Uri(string.Format("http://maps.apple.com/?q={0}", WebUtility.UrlEncode(navAdd))));
        //            break;
        //        case TargetPlatform.Android:
        //            Device.OpenUri(new Uri(string.Format("geo:0,0?q={0}", WebUtility.UrlEncode(navAdd))));
        //            break;
        //        case TargetPlatform.Windows:
        //        case TargetPlatform.WinPhone:
        //            Device.OpenUri(new Uri(string.Format("bingmaps:?where={0}", Uri.EscapeDataString(navAdd))));
        //            break;
        //    }
        //}


        void HandleClickedAsync(object sender, EventArgs e)
        {
            var b = sender as Button;
            switch (b.Text)
            {
                case "Street":
                    map.MapType = MapType.Street;
                    break;
                case "Hybrid":
                    map.MapType = MapType.Hybrid;
                    break;
                case "Satellite":
                    map.MapType = MapType.Satellite;
                    break;
            }
        }

        static void CalculateBoundingCoordinates(MapSpan region)
        {
            var center = region.Center;
            var halfheightDegrees = region.LatitudeDegrees / 2;
            var halfwidthDegrees = region.LongitudeDegrees / 2;

            var left = center.Longitude - halfwidthDegrees;
            var right = center.Longitude + halfwidthDegrees;
            var top = center.Latitude + halfheightDegrees;
            var bottom = center.Latitude - halfheightDegrees;

            if (left < -180) left = 180 + (180 + left);
            if (right > 180) right = (right - 180) - 180;

            Debug.WriteLine("Bounding box:");
            Debug.WriteLine("                    " + top);
            Debug.WriteLine("  " + left + "                " + right);
            Debug.WriteLine("                    " + bottom);
        }

    }
}

On top of the mentioned issue the implementation has also caused IsShowingUser = True to no longer function as well as

var currentpos = await maplocator.GetPositionAsync(1000);

to throw an exception.

Github repository: https://github.com/Mortp/CustomMapPinsXamarin

解决方案

First of all I would like to provide 2 links that helped me to understand the problem. Thank you guys. Xamarin.Forms.Maps 2.3.4 custom MapRenderer disables everything and https://forums.xamarin.com/discussion/92565/android-ionmapreadycallback-forms-2-3-4

Latest Xamarin Maps broke OnElementPropertyChanged with VisibleRegion. They defined that MapRenderer now implements IOnMapReadyCallback and that broke somehow OnElementPropertyChanged (I didn't investigate how and why). As you can see in link I provided there are 2 methods you can go. To keep your renderer implementing IOnMapReadyCallback or not. When I kept IOnMapReadyCallback I started to get 2 pins - one of top another - our custom pin and regular pin. I didn't dig more how that happens and removed IOnMapReadyCallback. After that things became really simple because if you let Xamarin handle it and create NativeMap you can remove some code and make renderer simpler.

Before I post the code I also want to mention that when I fixed it the app started crashing with OutOfMemory exception and I found out that your pin image is 2000 pixels width. I changed it to 40. Below is the code:

public class CustomMapRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter//, IOnMapReadyCallback
{
    bool isDrawn;

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

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

    }


    bool isMapReady;
    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        if (!isMapReady && (NativeMap != null))
        {
            NativeMap.SetInfoWindowAdapter(this);
            NativeMap.InfoWindowClick += OnInfoWindowClick;
            isMapReady = true;
        }

        if (e.PropertyName.Equals("VisibleRegion") && !isDrawn)
        {
            NativeMap.Clear();

            foreach (var pin in ((CustomMap)Element).CustomPins)
            {
                var marker = new MarkerOptions();
                marker.SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude));
                marker.SetTitle(pin.Pin.Label);
                marker.SetSnippet(pin.Pin.Address);
                marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.pin));

                NativeMap.AddMarker(marker);
            }
            isDrawn = true;
        }
    }

    protected override void OnLayout(bool changed, int l, int t, int r, int b)
    {
        base.OnLayout(changed, l, t, r, b);

        if (changed)
        {
            isDrawn = false;
        }
    }

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

    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.Id == "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;
    }

    public Android.Views.View GetInfoWindow(Marker marker)
    {
        return null;
    }

    CustomPin GetCustomPin(Marker annotation)
    {
        var position = new Position(annotation.Position.Latitude, annotation.Position.Longitude);
        foreach (var pin in ((CustomMap)Element).CustomPins)
        {
            if (pin.Pin.Position == position)
            {
                return pin;
            }
        }
        return null;
    }


}

这篇关于Xamarin Forms 自定义地图 Pin 图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆