Xamarin.Forms/Xamarin.Forms.Maps.Android/MapRenderer.cs

456 строки
11 KiB
C#
Исходник Обычный вид История

2016-03-22 23:02:25 +03:00
using System;
using System.Collections;
2016-03-22 23:02:25 +03:00
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
[All] Page embedding (#1124) * Fix broken SetTextAppearance call when targeting below API 23 * Enable embedding XF apps in Android * VS didn't save the project file * Allow retrieving rendered XF VisualElements for use in UWP apps * Prevent XF from slaughtering the menu bar in embedded scenarios * ? * Post-rebase fixups * Create embedding test project for Android * Create iOS test bed project * Attempting to get functional embedded project * Android and iOS functional testbed projects * Basic embedding app on UWP * First pass at handling navigation to Forms page from UWP * Fleshed out example, added parameter checks, sealed wrapper class * Create test page and load it from UWP * Make DisplayAlert/DisplayActionSheet independent of Platform instance on UWP * Set up test page for alerts/actionsheets on Android * Move DisplayAlert/ActionSheet code to a helper class so embedded Forms can use it * VS didn't save my changes * Add test page for alerts and action sheets * Get FrameworkElement from renderer * Page -> ContentPage * Can now display the webview embedded page * Example working on iOS * Repro of crash * Remove the IStartActivityForResult dependency from FormsWebChromeClient * Created repro * Remove need for Forms.Context to create renderers * Remove Forms.Context dependencies in ScrollViewRenderer * Remove Forms.Context dependencies in FrameRenderer * Remove Forms.Context dependencies * Remove Context/Forms.Context dependencies in Drawable subclasses * Remove some more Forms.Context dependencies * CellAdapter no longer dependent on Forms.Context * Obsolete ToAndroid using Forms.Context * Remove Forms.Context dependencies in ResourceManager * Remove need for Forms.Context for SupportsProgress * Remove Forms.Context dependency for setting titlebar visibility * Remove Forms.Context dependencies in GetAccentColor * Some comments about caching * Remove Forms.Context dependencies for AndroidPlatformServices and ResourceProvider * Remove Forms.Context dependencies in Maps * Disabled warnings for Forms.Context in CustomRenderers * Filter logs so we don't get those annoying "parked" messages * Filter alerts/actionsheets/activityindicator by context * Clean up constructor debugging messages * Remove old TODOs * Set up test page for Device.Openuri * Make AndroidDeviceInfo more resilient to multiple activity scenarios * Use parameter instead of member * Add missing parameterless constructors for embedded fragment wrappers * Fix multiple popup subscriptions for embedded context * Remove UpdateGlobalContext and Page Context attached property * Obsolete static reference to Context * Warnings as errors in embedded test bed projects * Comment cleanup * Clean up unused code * Obsolete old constructor for DefaultRenderer * Make sure embedded fragment wrappers handle disposing the platform * Revert to old DependencyService registration for ResourceProvider; use ApplicationContext for ResourceProvider; Comments for everything happing in Forms.SetupInit * Remove old TODO * Make PopupManager take the correct type instead of casting a bunch * Update docs * Add missing nuspec entry for FormsEmbeddedPageWrapper * Post-rebase cleanup * Update docs * Disable XF target validation so package restore works * Restore InputTransparent handling lost in rebase * Restore parameter lost during rebase * Finalize the list of subscriptions to avoid 'modified collection' errors * Avoid double-fetching Context * Fix "with you package" typo
2017-10-09 20:51:55 +03:00
using Android.Content;
2016-03-22 23:02:25 +03:00
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Android.OS;
using Java.Lang;
using Xamarin.Forms.Internals;
2016-03-22 23:02:25 +03:00
using Xamarin.Forms.Platform.Android;
using Math = System.Math;
namespace Xamarin.Forms.Maps.Android
{
2018-05-15 18:16:52 +03:00
public class MapRenderer : ViewRenderer<Map, MapView>, GoogleMap.IOnCameraMoveListener, IOnMapReadyCallback
2016-03-22 23:02:25 +03:00
{
const string MoveMessageName = "MapMoveToRegion";
2016-03-22 23:02:25 +03:00
static Bundle s_bundle;
bool _disposed;
bool _init = true;
2016-03-22 23:02:25 +03:00
List<Marker> _markers;
[All] Page embedding (#1124) * Fix broken SetTextAppearance call when targeting below API 23 * Enable embedding XF apps in Android * VS didn't save the project file * Allow retrieving rendered XF VisualElements for use in UWP apps * Prevent XF from slaughtering the menu bar in embedded scenarios * ? * Post-rebase fixups * Create embedding test project for Android * Create iOS test bed project * Attempting to get functional embedded project * Android and iOS functional testbed projects * Basic embedding app on UWP * First pass at handling navigation to Forms page from UWP * Fleshed out example, added parameter checks, sealed wrapper class * Create test page and load it from UWP * Make DisplayAlert/DisplayActionSheet independent of Platform instance on UWP * Set up test page for alerts/actionsheets on Android * Move DisplayAlert/ActionSheet code to a helper class so embedded Forms can use it * VS didn't save my changes * Add test page for alerts and action sheets * Get FrameworkElement from renderer * Page -> ContentPage * Can now display the webview embedded page * Example working on iOS * Repro of crash * Remove the IStartActivityForResult dependency from FormsWebChromeClient * Created repro * Remove need for Forms.Context to create renderers * Remove Forms.Context dependencies in ScrollViewRenderer * Remove Forms.Context dependencies in FrameRenderer * Remove Forms.Context dependencies * Remove Context/Forms.Context dependencies in Drawable subclasses * Remove some more Forms.Context dependencies * CellAdapter no longer dependent on Forms.Context * Obsolete ToAndroid using Forms.Context * Remove Forms.Context dependencies in ResourceManager * Remove need for Forms.Context for SupportsProgress * Remove Forms.Context dependency for setting titlebar visibility * Remove Forms.Context dependencies in GetAccentColor * Some comments about caching * Remove Forms.Context dependencies for AndroidPlatformServices and ResourceProvider * Remove Forms.Context dependencies in Maps * Disabled warnings for Forms.Context in CustomRenderers * Filter logs so we don't get those annoying "parked" messages * Filter alerts/actionsheets/activityindicator by context * Clean up constructor debugging messages * Remove old TODOs * Set up test page for Device.Openuri * Make AndroidDeviceInfo more resilient to multiple activity scenarios * Use parameter instead of member * Add missing parameterless constructors for embedded fragment wrappers * Fix multiple popup subscriptions for embedded context * Remove UpdateGlobalContext and Page Context attached property * Obsolete static reference to Context * Warnings as errors in embedded test bed projects * Comment cleanup * Clean up unused code * Obsolete old constructor for DefaultRenderer * Make sure embedded fragment wrappers handle disposing the platform * Revert to old DependencyService registration for ResourceProvider; use ApplicationContext for ResourceProvider; Comments for everything happing in Forms.SetupInit * Remove old TODO * Make PopupManager take the correct type instead of casting a bunch * Update docs * Add missing nuspec entry for FormsEmbeddedPageWrapper * Post-rebase cleanup * Update docs * Disable XF target validation so package restore works * Restore InputTransparent handling lost in rebase * Restore parameter lost during rebase * Finalize the list of subscriptions to avoid 'modified collection' errors * Avoid double-fetching Context * Fix "with you package" typo
2017-10-09 20:51:55 +03:00
public MapRenderer(Context context) : base(context)
{
AutoPackage = false;
}
2017-10-20 23:02:16 +03:00
[Obsolete("This constructor is obsolete as of version 2.5. Please use MapRenderer(Context) instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public MapRenderer()
{
AutoPackage = false;
}
protected Map Map => Element;
2016-03-22 23:02:25 +03:00
protected GoogleMap NativeMap;
internal static Bundle Bundle
{
set { s_bundle = value; }
}
2016-03-22 23:02:25 +03:00
public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
2016-03-22 23:02:25 +03:00
{
return new SizeRequest(new Size(Context.ToPixels(40), Context.ToPixels(40)));
2016-03-22 23:02:25 +03:00
}
protected override MapView CreateNativeControl()
{
return new MapView(Context);
}
protected override void Dispose(bool disposing)
2016-03-22 23:02:25 +03:00
{
if (_disposed)
{
return;
}
2016-03-22 23:02:25 +03:00
_disposed = true;
if (disposing)
{
if (Element != null)
{
MessagingCenter.Unsubscribe<Map, MapSpan>(this, MoveMessageName);
((ObservableCollection<Pin>)Element.Pins).CollectionChanged -= OnCollectionChanged;
foreach (Pin pin in Element.Pins)
{
pin.PropertyChanged -= PinOnPropertyChanged;
}
}
2016-03-22 23:02:25 +03:00
if (NativeMap != null)
{
NativeMap.MyLocationEnabled = false;
NativeMap.SetOnCameraMoveListener(null);
NativeMap.InfoWindowClick -= MapOnMarkerClick;
NativeMap.MapClick -= OnMapClick;
NativeMap.Dispose();
NativeMap = null;
}
Control?.OnDestroy();
}
base.Dispose(disposing);
}
protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
MapView oldMapView = Control;
2016-03-22 23:02:25 +03:00
MapView mapView = CreateNativeControl();
mapView.OnCreate(s_bundle);
mapView.OnResume();
SetNativeControl(mapView);
if (e.OldElement != null)
{
Map oldMapModel = e.OldElement;
2016-03-22 23:02:25 +03:00
((ObservableCollection<Pin>)oldMapModel.Pins).CollectionChanged -= OnCollectionChanged;
foreach (Pin pin in oldMapModel.Pins)
{
pin.PropertyChanged -= PinOnPropertyChanged;
}
MessagingCenter.Unsubscribe<Map, MapSpan>(this, MoveMessageName);
2016-03-22 23:02:25 +03:00
if (NativeMap != null)
{
NativeMap.SetOnCameraMoveListener(null);
2016-03-22 23:02:25 +03:00
NativeMap.InfoWindowClick -= MapOnMarkerClick;
NativeMap.MapClick -= OnMapClick;
NativeMap = null;
2016-03-22 23:02:25 +03:00
}
oldMapView.Dispose();
}
Control.GetMapAsync(this);
MessagingCenter.Subscribe<Map, MapSpan>(this, MoveMessageName, OnMoveToRegionMessage, Map);
2016-03-22 23:02:25 +03:00
var incc = Map.Pins as INotifyCollectionChanged;
if (incc != null)
{
2016-03-22 23:02:25 +03:00
incc.CollectionChanged += OnCollectionChanged;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
2016-03-22 23:02:25 +03:00
{
base.OnElementPropertyChanged(sender, e);
2016-03-22 23:02:25 +03:00
if (e.PropertyName == Map.MapTypeProperty.PropertyName)
{
2016-03-22 23:02:25 +03:00
SetMapType();
return;
}
GoogleMap gmap = NativeMap;
2016-03-22 23:02:25 +03:00
if (gmap == null)
{
2016-03-22 23:02:25 +03:00
return;
}
2016-03-22 23:02:25 +03:00
if (e.PropertyName == Map.IsShowingUserProperty.PropertyName)
{
2016-03-22 23:02:25 +03:00
gmap.MyLocationEnabled = gmap.UiSettings.MyLocationButtonEnabled = Map.IsShowingUser;
}
2016-03-22 23:02:25 +03:00
else if (e.PropertyName == Map.HasScrollEnabledProperty.PropertyName)
{
2016-03-22 23:02:25 +03:00
gmap.UiSettings.ScrollGesturesEnabled = Map.HasScrollEnabled;
}
else if (e.PropertyName == Map.HasZoomEnabledProperty.PropertyName)
{
2016-03-22 23:02:25 +03:00
gmap.UiSettings.ZoomControlsEnabled = Map.HasZoomEnabled;
gmap.UiSettings.ZoomGesturesEnabled = Map.HasZoomEnabled;
}
}
protected override void OnLayout(bool changed, int l, int t, int r, int b)
2016-03-22 23:02:25 +03:00
{
base.OnLayout(changed, l, t, r, b);
2016-03-22 23:02:25 +03:00
if (_init)
{
if (NativeMap != null)
{
MoveToRegion(Element.LastMoveToRegion, false);
OnCollectionChanged(Element.Pins, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
_init = false;
}
}
else if (changed)
{
if (NativeMap != null)
{
UpdateVisibleRegion(NativeMap.CameraPosition.Target);
}
MoveToRegion(Element.LastMoveToRegion, false);
2016-03-22 23:02:25 +03:00
}
}
2018-05-15 18:16:52 +03:00
protected virtual void OnMapReady(GoogleMap map)
{
if (map == null)
{
return;
}
2018-05-15 18:16:52 +03:00
map.SetOnCameraMoveListener(this);
map.InfoWindowClick += MapOnMarkerClick;
map.MapClick += OnMapClick;
2018-05-15 18:16:52 +03:00
map.UiSettings.ZoomControlsEnabled = Map.HasZoomEnabled;
map.UiSettings.ZoomGesturesEnabled = Map.HasZoomEnabled;
map.UiSettings.ScrollGesturesEnabled = Map.HasScrollEnabled;
map.MyLocationEnabled = map.UiSettings.MyLocationButtonEnabled = Map.IsShowingUser;
SetMapType();
}
2018-05-15 18:16:52 +03:00
protected virtual MarkerOptions CreateMarker(Pin pin)
{
var opts = new MarkerOptions();
opts.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
opts.SetTitle(pin.Label);
opts.SetSnippet(pin.Address);
2018-05-15 18:16:52 +03:00
return opts;
}
2016-03-22 23:02:25 +03:00
void AddPins(IList pins)
2016-03-22 23:02:25 +03:00
{
GoogleMap map = NativeMap;
2016-03-22 23:02:25 +03:00
if (map == null)
{
2016-03-22 23:02:25 +03:00
return;
}
2016-03-22 23:02:25 +03:00
if (_markers == null)
{
_markers = new List<Marker>();
}
2016-03-22 23:02:25 +03:00
_markers.AddRange(pins.Cast<Pin>().Select(p =>
{
Pin pin = p;
var opts = CreateMarker(pin);
var marker = map.AddMarker(opts);
2016-03-22 23:02:25 +03:00
pin.PropertyChanged += PinOnPropertyChanged;
2016-03-22 23:02:25 +03:00
// associate pin with marker for later lookup in event handlers
2019-03-27 21:38:38 +03:00
pin.MarkerId = marker.Id;
2016-03-22 23:02:25 +03:00
return marker;
}));
}
void PinOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
Pin pin = (Pin)sender;
Marker marker = GetMarkerForPin(pin);
if (marker == null)
{
return;
}
if (e.PropertyName == Pin.LabelProperty.PropertyName)
{
marker.Title = pin.Label;
}
else if (e.PropertyName == Pin.AddressProperty.PropertyName)
{
marker.Snippet = pin.Address;
}
else if (e.PropertyName == Pin.PositionProperty.PropertyName)
{
marker.Position = new LatLng(pin.Position.Latitude, pin.Position.Longitude);
}
}
protected Marker GetMarkerForPin(Pin pin)
{
2019-03-27 21:38:38 +03:00
return _markers?.Find(m => m.Id == (string)pin.MarkerId);
}
void MapOnMarkerClick(object sender, GoogleMap.InfoWindowClickEventArgs eventArgs)
2016-03-22 23:02:25 +03:00
{
// clicked marker
var marker = eventArgs.Marker;
// lookup pin
Pin targetPin = null;
for (var i = 0; i < Map.Pins.Count; i++)
{
Pin pin = Map.Pins[i];
2019-03-27 21:38:38 +03:00
if ((string)pin.MarkerId != marker.Id)
{
2016-03-22 23:02:25 +03:00
continue;
}
2016-03-22 23:02:25 +03:00
targetPin = pin;
break;
}
2018-05-15 18:16:52 +03:00
// only consider event handled if a handler is present.
2016-03-22 23:02:25 +03:00
// Else allow default behavior of displaying an info window.
targetPin?.SendTap();
2016-03-22 23:02:25 +03:00
}
void OnMapClick(object sender, GoogleMap.MapClickEventArgs e)
{
Map.SendMapClicked(new Position(e.Point.Latitude, e.Point.Longitude));
}
void MoveToRegion(MapSpan span, bool animate)
2016-03-22 23:02:25 +03:00
{
GoogleMap map = NativeMap;
if (map == null)
{
return;
}
span = span.ClampLatitude(85, -85);
var ne = new LatLng(span.Center.Latitude + span.LatitudeDegrees / 2,
span.Center.Longitude + span.LongitudeDegrees / 2);
var sw = new LatLng(span.Center.Latitude - span.LatitudeDegrees / 2,
span.Center.Longitude - span.LongitudeDegrees / 2);
CameraUpdate update = CameraUpdateFactory.NewLatLngBounds(new LatLngBounds(sw, ne), 0);
try
{
if (animate)
{
map.AnimateCamera(update);
}
else
{
map.MoveCamera(update);
2016-03-22 23:02:25 +03:00
}
}
catch (IllegalStateException exc)
{
System.Diagnostics.Debug.WriteLine("MoveToRegion exception: " + exc);
Log.Warning("Xamarin.Forms MapRenderer", $"MoveToRegion exception: {exc}");
}
}
2016-03-22 23:02:25 +03:00
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
switch (notifyCollectionChangedEventArgs.Action)
{
case NotifyCollectionChangedAction.Add:
AddPins(notifyCollectionChangedEventArgs.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
RemovePins(notifyCollectionChangedEventArgs.OldItems);
break;
case NotifyCollectionChangedAction.Replace:
RemovePins(notifyCollectionChangedEventArgs.OldItems);
AddPins(notifyCollectionChangedEventArgs.NewItems);
break;
case NotifyCollectionChangedAction.Reset:
_markers?.ForEach(m => m.Remove());
_markers = null;
AddPins((IList)Element.Pins);
break;
case NotifyCollectionChangedAction.Move:
//do nothing
break;
}
}
void OnMoveToRegionMessage(Map s, MapSpan a)
{
MoveToRegion(a, true);
}
void RemovePins(IList pins)
{
GoogleMap map = NativeMap;
if (map == null)
{
return;
}
if (_markers == null)
{
return;
}
foreach (Pin p in pins)
{
p.PropertyChanged -= PinOnPropertyChanged;
var marker = GetMarkerForPin(p);
if (marker == null)
{
continue;
}
marker.Remove();
_markers.Remove(marker);
}
}
void SetMapType()
{
GoogleMap map = NativeMap;
if (map == null)
{
return;
2016-03-22 23:02:25 +03:00
}
switch (Map.MapType)
{
case MapType.Street:
map.MapType = GoogleMap.MapTypeNormal;
break;
case MapType.Satellite:
map.MapType = GoogleMap.MapTypeSatellite;
break;
case MapType.Hybrid:
map.MapType = GoogleMap.MapTypeHybrid;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
void UpdateVisibleRegion(LatLng pos)
{
GoogleMap map = NativeMap;
if (map == null)
{
return;
}
Projection projection = map.Projection;
int width = Control.Width;
int height = Control.Height;
LatLng ul = projection.FromScreenLocation(new global::Android.Graphics.Point(0, 0));
LatLng ur = projection.FromScreenLocation(new global::Android.Graphics.Point(width, 0));
LatLng ll = projection.FromScreenLocation(new global::Android.Graphics.Point(0, height));
LatLng lr = projection.FromScreenLocation(new global::Android.Graphics.Point(width, height));
double dlat = Math.Max(Math.Abs(ul.Latitude - lr.Latitude), Math.Abs(ur.Latitude - ll.Latitude));
double dlong = Math.Max(Math.Abs(ul.Longitude - lr.Longitude), Math.Abs(ur.Longitude - ll.Longitude));
Element.SetVisibleRegion(new MapSpan(new Position(pos.Latitude, pos.Longitude), dlat, dlong));
2016-03-22 23:02:25 +03:00
}
void IOnMapReadyCallback.OnMapReady(GoogleMap map)
{
NativeMap = map;
OnMapReady(map);
}
void GoogleMap.IOnCameraMoveListener.OnCameraMove()
{
UpdateVisibleRegion(NativeMap.CameraPosition.Target);
}
2016-03-22 23:02:25 +03:00
}
}