Xamarin Forms Maps Circle自定义渲染器

时间:2018-02-19 14:52:25

标签: ios iphone xamarin xamarin.forms

在iOS上遇到自定义地图/渲染器的两个问题。

演示视频:https://ufile.io/pscn3

我有一个自定义地图类,其中的圆圈放在地图上。 滑块控件从可绑定属性调整圆的大小。

  1. 当滑块值更改时,圆的半径属性将使用所选值进行更新。但正如您所看到的,它并未在地图上更新半径,而是将圆圈移动到曲线内的新位置。

  2. 当圆圈移动到x像素之外时,它会消失或在可见边界外被切除。

  3. 这些是正在使用的类:

    Page.xaml:

    <?xml version="1.0" encoding="UTF-8"?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:vm="clr-namespace:CompanyName.Data.ViewModels.MapWithCircleSlider;assembly=CompanyName"
        xmlns:local="clr-namespace:CompanyName.UI;assembly=CompanyName"
        x:Class="CompanyName.UI.Pages.MapWithCircleSlider"
        Title="{Binding Title}">
    
        <ContentPage.BindingContext>
        <vm:MapWithCircleSliderViewModel></vm:MapWithCircleSliderViewModel>
        </ContentPage.BindingContext>
    
        <ContentPage.Content>
            <ScrollView>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <local:CircleMap Grid.Row="0" CircleRadius="{Binding CircleRadius}" Latitude="{Binding Latitude}" Longitude="{Binding Longitude}" MapRadius="{Binding MapRadius}" IsShowingUser="true" HasZoomEnabled="true" />
                    <!--<Image Grid.Row="0" HorizontalOptions="Center" VerticalOptions="Center" Source="ic_place_green_48dp.png" />-->
                    <StackLayout Grid.Row="1" Padding="32,16">
                        <Entry VerticalOptions="Start" Placeholder="navn *" Text="{Binding Name}">
                            <Entry.Style>
                                <OnPlatform x:TypeArguments="Style">
                                    <On Platform="iOS" Value="{x:Static local:Styling.IosEntryStyle}" />
                                </OnPlatform>
                            </Entry.Style>
                        </Entry>
                        <Label VerticalOptions="Center" HorizontalOptions="Center" Text="{Binding CircleRadius, StringFormat='{0}m'}" />
                        <Slider VerticalOptions="End" Maximum="{Binding Maximum}" Minimum="{Binding Minimum}" Value="{Binding CircleRadius}" />
                        <!-- NB: Maximum must be set before Minimum, ref: https://bugzilla.xamarin.com/show_bug.cgi?id=23665 -->
                    </StackLayout>
                </Grid>
            </ScrollView>
        </ContentPage.Content>
    
    </ContentPage>
    

    Pages ViewModel:

    using System;
    using CompanyName.ViewModels;
    
    namespace CompanyName.Data.ViewModels.MapWithCircleSlider
    {
        public class MapWithCircleSliderViewModel : ViewModelBase
        {
    
            private string name;
            private int circleRadius;
            private float latitude;
            private float longitude;
            private int mapRadius;
    
            public MapWithCircleSliderViewModel()
            {
                Name = "Labs";
                CircleRadius = 200;
                MapRadius = 200;
                Latitude = 58.9698634f;
                Longitude = 5.7331874f;
            }
    
            public int Maximum => 1000;
            public int Minimum => 100;
            public string Id { get; set; }
            public bool IsEditMode { get; set; }
            public string Title { get; set; }
    
            public string Name
            {
                get => name;
                set
                {
                    if (name == value) return;
    
                    name = value;
                    OnPropertyChanged("Name");
                }
            }
    
            public int CircleRadius
            {
                get => circleRadius;
                set
                {
                    if (circleRadius == value) return;
    
                    circleRadius = value;
                    OnPropertyChanged("CircleRadius");
                }
            }
    
            public float Latitude
            {
                get => latitude;
                set
                {
                    if (Math.Abs(latitude - value) < float.Epsilon) return;
    
                    latitude = value;
                    OnPropertyChanged("Latitude");
                }
            }
    
            public float Longitude
            {
                get => longitude;
                set
                {
                    if (Math.Abs(longitude - value) < float.Epsilon) return;
    
                    longitude = value;
                    OnPropertyChanged("Longitude");
                }
            }
    
            public int MapRadius
            {
                get => mapRadius;
                set
                {
                    if (mapRadius == value) return;
    
                    mapRadius = value;
                    OnPropertyChanged("MapRadius");
                }
            }
        }
    }
    

    CircleMap.cs

    using System.Diagnostics;
    using Xamarin.Forms;
    using Xamarin.Forms.Maps;
    
    namespace CompanyName.UI
    {
        public class CircleMap : Map
        {
            private const int DefaultCircleRadius = 100;
            private const float DefaultLatitude = 58.8523208f;
            private const float DefaultLongitude = 5.7326743f;
            private const int DefaultMapRadius = 150;
    
            public static readonly BindableProperty CircleRadiusProperty = BindableProperty.Create("CircleRadius", typeof(int), typeof(CircleMap), DefaultCircleRadius, BindingMode.TwoWay, propertyChanged: OnCircleRadiusPropertyChanged);
            public static readonly BindableProperty LatitudeProperty = BindableProperty.Create("Latitude", typeof(float), typeof(CircleMap), DefaultLatitude, BindingMode.TwoWay, propertyChanged: OnLatitudePropertyChanged);
            public static readonly BindableProperty LongitudeProperty = BindableProperty.Create("Longitude", typeof(float), typeof(CircleMap), DefaultLongitude, BindingMode.TwoWay, propertyChanged: OnLongitudePropertyChanged);
            public static readonly BindableProperty MapRadiusProperty = BindableProperty.Create("MapRadius", typeof(int), typeof(CircleMap), DefaultMapRadius, BindingMode.TwoWay, propertyChanged: OnMapRadiusPropertyChanged);
    
            public CircleMap() : base(MapSpan.FromCenterAndRadius(new Position(DefaultLatitude, DefaultLongitude), Distance.FromMeters(DefaultMapRadius))) { }
    
            public int CircleRadius
            {
                get => (int)GetValue(CircleRadiusProperty);
                set => SetValue(CircleRadiusProperty, value);
            }
    
            public float Latitude
            {
                get => (float)GetValue(LatitudeProperty);
                set => SetValue(LatitudeProperty, value);
            }
    
            public float Longitude
            {
                get => (float)GetValue(LongitudeProperty);
                set => SetValue(LongitudeProperty, value);
            }
    
            public int MapRadius
            {
                get => (int)GetValue(MapRadiusProperty);
                set => SetValue(MapRadiusProperty, value);
            }
    
            private static void OnCircleRadiusPropertyChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var circleMap = (CircleMap)bindable;
                circleMap.CircleRadius = (int)newValue;
            }
    
            private static void OnLatitudePropertyChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var circleMap = (CircleMap)bindable;
                circleMap.Latitude = (float)newValue;
    
                MoveToRegion(circleMap);
            }
    
            private static void OnLongitudePropertyChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var circleMap = (CircleMap)bindable;
                circleMap.Longitude = (float)newValue;
    
                MoveToRegion(circleMap);
            }
    
            private static void OnMapRadiusPropertyChanged(BindableObject bindable, object oldValue, object newValue)
            {
                var circleMap = (CircleMap)bindable;
                circleMap.MapRadius = (int)newValue;
    
                MoveToRegion(circleMap);
            }
    
            private static void MoveToRegion(CircleMap circleMap)
            {
                circleMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(circleMap.Latitude, circleMap.Longitude), Distance.FromMeters(circleMap.MapRadius)));
            }
        }
    }
    

    CustomMapRenderer.cs(iOS):

    using CompanyName.UI;
    using MapKit;
    using ObjCRuntime;
    using System;
    using System.ComponentModel;
    using System.Linq;
    using Xamarin.Forms;
    using Xamarin.Forms.Maps.iOS;
    using Xamarin.Forms.Platform.iOS;
    using CompanyName.iOS.Renderers.CustomRenderer;
    using CompanyName.Utilities;
    
    [assembly: ExportRenderer(typeof(CircleMap), typeof(CustomMapRenderer))]
    namespace CompanyName.iOS.Renderers.CustomRenderer
    {
        /// <remarks>
        /// https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/map/circle-map-overlay/#Creating_the_Custom_Renderer_on_iOS
        /// </remarks>
        public class CustomMapRenderer : MapRenderer
        {
            private CircleMap circleMap;
            private MKCircleRenderer circleRenderer;
            private MKMapView NativeMap => Control as MKMapView;
    
            protected override void OnElementChanged(ElementChangedEventArgs<View> e)
            {
                try
                {
                    base.OnElementChanged(e);
    
                    if (e.OldElement != null)
                    {
                        if (Control is MKMapView nativeMap)
                        {
                            nativeMap.RemoveOverlays(nativeMap.Overlays);
                            nativeMap.OverlayRenderer = null;
                            circleRenderer = null;
                        }
                    }
    
                    if (e.NewElement != null)
                    {
                        circleMap = (CircleMap)e.NewElement;
                        NativeMap.OverlayRenderer = GetOverlayRenderer;
    
                        AddOverlay();
                    }
                }
                catch (Exception ex)
                {
                    //Logger.LogException(ex, GetType().Name);
                }
            }
    
            protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                base.OnElementPropertyChanged(sender, e);
    
                if (sender == null) return;
                circleMap = (CircleMap)sender;
    
                if (e.PropertyName == "VisibleRegion") OnVisibleRegionChanged();
                if (e.PropertyName == CircleMap.CircleRadiusProperty.PropertyName) RedrawOverlay();
            }
    
            private MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
            {
                if (circleRenderer == null && !Equals(overlayWrapper, null))
                {
                    var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
                    circleRenderer = new MKCircleRenderer(overlay as MKCircle)
                    {
                        Alpha = 0.15f,
                        FillColor = CompanyName.Constants.Colors.Skobeloff500.ToUIColor(),
                        LineWidth = 1,
                        StrokeColor = CompanyName.Constants.Colors.Skobeloff500.ToUIColor()
                    };
                }
                return circleRenderer;
            }
    
            private void OnVisibleRegionChanged()
            {
                SetNewCoordinates();
                RedrawOverlay();
            }
    
            private void SetNewCoordinates()
            {
                circleMap.Latitude = (float)circleMap.VisibleRegion.Center.Latitude;
                circleMap.Longitude = (float)circleMap.VisibleRegion.Center.Longitude;
                circleMap.MapRadius = (int)circleMap.VisibleRegion.Radius.Meters;
            }
    
            private void RedrawOverlay()
            {
                RemoveOverlays();
                AddOverlay();
            }
    
            private void RemoveOverlays()
            {
                if (NativeMap?.Overlays == null) return;
                if (NativeMap.Overlays.Any()) NativeMap.RemoveOverlays(NativeMap.Overlays);
            }
    
            private void AddOverlay()
            {
                var circleOverlay = MKCircle.Circle(new CoreLocation.CLLocationCoordinate2D(circleMap.Latitude, circleMap.Longitude), circleMap.CircleRadius);
                NativeMap.AddOverlay(circleOverlay);
            }
        }
    }
    

    非常感谢任何反馈/建议!

1 个答案:

答案 0 :(得分:1)

您可以尝试刷新circleRenderer以达到以下效果:

private void RemoveOverlays()
{
    if (NativeMap?.Overlays == null) return;
    if (NativeMap.Overlays.Any())
    {
        NativeMap.RemoveOverlays(NativeMap.Overlays);

        circleRenderer = null;
        NativeMap.OverlayRenderer = GetOverlayRenderer;
    }
}