Xamarin.Forms Maps中如何配置XAML实现Polygon数据绑定
Xamarin.Forms Maps 多边形数据绑定实现方案
原生Xamarin.Forms的Map.MapElements属性未提供可绑定的ItemsSource能力,无法直接像Pin绑定那样对接ViewModel的ObservableCollection,需要通过自定义可绑定附加属性实现绑定适配,具体步骤如下:
步骤1:定义ViewModel侧数据模型
先定义多边形对应的数据模型,实现INotifyPropertyChanged接口,属性和硬编码时的Polygon属性一一对应:
public class MapPolygonModel : INotifyPropertyChanged { private Color _strokeColor; private float _strokeWidth; private Color _fillColor; private ObservableCollection<Position> _geoPath; public Color StrokeColor { get => _strokeColor; set { _strokeColor = value; OnPropertyChanged(); } } public float StrokeWidth { get => _strokeWidth; set { _strokeWidth = value; OnPropertyChanged(); } } public Color FillColor { get => _fillColor; set { _fillColor = value; OnPropertyChanged(); } } public ObservableCollection<Position> GeoPath { get => _geoPath; set { _geoPath = value; OnPropertyChanged(); } } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
在ViewModel中初始化多边形集合:
public ObservableCollection<MapPolygonModel> MyPolygons { get; set; } = new ObservableCollection<MapPolygonModel>(); // 初始化时往MyPolygons中添加对应多边形数据即可,点位按顺序存入GeoPath
步骤2:实现Map绑定适配的附加属性
新建静态类MapBindableProperties,编写两个附加属性:PolygonItemsSource(绑定ViewModel的多边形集合)、PolygonItemTemplate(定义多边形的DataTemplate,和Pin的ItemTemplate逻辑一致),同时处理集合变更同步逻辑:
public static class MapBindableProperties { public static readonly BindableProperty PolygonItemsSourceProperty = BindableProperty.CreateAttached( "PolygonItemsSource", typeof(IEnumerable), typeof(MapBindableProperties), null, propertyChanged: OnPolygonItemsSourceChanged); public static readonly BindableProperty PolygonItemTemplateProperty = BindableProperty.CreateAttached( "PolygonItemTemplate", typeof(DataTemplate), typeof(MapBindableProperties), null); public static void SetPolygonItemsSource(BindableObject view, IEnumerable value) { view.SetValue(PolygonItemsSourceProperty, value); } public static IEnumerable GetPolygonItemsSource(BindableObject view) { return (IEnumerable)view.GetValue(PolygonItemsSourceProperty); } public static void SetPolygonItemTemplate(BindableObject view, DataTemplate value) { view.SetValue(PolygonItemTemplateProperty, value); } public static DataTemplate GetPolygonItemTemplate(BindableObject view) { return (DataTemplate)view.GetValue(PolygonItemTemplateProperty); } private static void OnPolygonItemsSourceChanged(BindableObject bindable, object oldValue, object newValue) { if (!(bindable is Map map)) return; map.MapElements.Clear(); if (oldValue is INotifyCollectionChanged oldCollection) { oldCollection.CollectionChanged -= (s, e) => SyncPolygonElements(map, e); } if (newValue is IEnumerable<MapPolygonModel> polygons) { foreach (var polygon in polygons) { AddPolygonToMap(map, polygon); } } if (newValue is INotifyCollectionChanged newCollection) { newCollection.CollectionChanged += (s, e) => SyncPolygonElements(map, e); } } private static void SyncPolygonElements(Map map, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Add) { foreach (MapPolygonModel item in e.NewItems) { AddPolygonToMap(map, item); } } else if (e.Action == NotifyCollectionChangedAction.Remove) { foreach (MapPolygonModel item in e.OldItems) { var removeElement = map.MapElements.FirstOrDefault(el => el.BindingContext == item); if (removeElement != null) map.MapElements.Remove(removeElement); } } else if (e.Action == NotifyCollectionChangedAction.Reset) { map.MapElements.Clear(); } } private static void AddPolygonToMap(Map map, MapPolygonModel polygonModel) { var template = GetPolygonItemTemplate(map); Polygon polygon; if (template != null) { polygon = (Polygon)template.CreateContent(); polygon.BindingContext = polygonModel; } else { polygon = new Polygon { BindingContext = polygonModel, StrokeColor = polygonModel.StrokeColor, StrokeWidth = polygonModel.StrokeWidth, FillColor = polygonModel.FillColor, Geopath = polygonModel.GeoPath }; } map.MapElements.Add(polygon); } }
步骤3:XAML配置绑定
首先在XAML头部引入附加属性所在的命名空间,替换原来硬编码的Map.MapElements节点,保留原有Pin绑定逻辑不变:
<!-- 头部引入命名空间,替换为自己附加属性类的clr-namespace --> <ContentPage xmlns:local="clr-namespace:YourAppNamespace"> <StackLayout> <maps:Map MapType="{Binding CurrentMapType, Mode=TwoWay}" ItemsSource="{Binding MyPins}" HasZoomEnabled="True" x:Name="map" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" <!-- 绑定ViewModel的多边形集合 --> local:MapBindableProperties.PolygonItemsSource="{Binding MyPolygons}"> <x:Arguments> <maps:MapSpan> <x:Arguments> <maps:Position> <x:Arguments> <x:Double>-41.6</x:Double> <x:Double>173.0</x:Double> </x:Arguments> </maps:Position> <x:Double>13</x:Double> <x:Double>13</x:Double> </x:Arguments> </maps:MapSpan> </x:Arguments> <!-- 原有Pin模板保留 --> <maps:Map.ItemTemplate> <DataTemplate> <maps:Pin Position="{Binding Position}" Address="{Binding Address}" Label="{Binding Description}" /> </DataTemplate> </maps:Map.ItemTemplate> <!-- 定义多边形的DataTemplate,和Pin模板写法一致 --> <local:MapBindableProperties.PolygonItemTemplate> <DataTemplate> <maps:Polygon StrokeColor="{Binding StrokeColor}" StrokeWidth="{Binding StrokeWidth}" FillColor="{Binding FillColor}" Geopath="{Binding GeoPath}"/> </DataTemplate> </local:MapBindableProperties.PolygonItemTemplate> </maps:Map> </StackLayout> </ContentPage>
注意事项
- 如果需要同时绑定Polyline,只需要在附加属性里增加对应的数据模型判断、Polyline模板和同步逻辑即可,实现逻辑和Polygon完全一致
- 存入GeoPath的Position点位顺序必须和硬编码时的顺序一致,否则会出现多边形形状错位、填充异常的问题
- 如果需要支持多边形属性运行时动态更新,在
AddPolygonToMap方法中给polygonModel的PropertyChanged事件加监听,对应更新Polygon实例的属性即可 - 若不需要自定义模板,也可以省略
PolygonItemTemplate配置,附加属性会自动根据模型属性生成Polygon实例
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

