Xamarin.Forms.GoogleMaps实现类似Uber同时展示多个地图信息窗口求助
解决方案
谷歌地图原生默认限制同一时间仅能展示一个信息窗口,该逻辑属于底层限制,无法通过上层配置直接覆盖,因此推荐采用「自定义悬浮视图绑定图钉位置」的方案实现多信息窗口同时展示的需求,具体实现逻辑如下:
安卓端自定义渲染修改说明
你可以基于现有自定义渲染代码调整,核心思路是完全弃用谷歌地图原生的InfoWindow组件,自己在地图上层添加独立的自定义视图,跟随地图相机移动实时更新位置,具体修改后的代码如下:
[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))] namespace CoTripApp.Droid.CustomRennders { public class CustomMapRenderer : MapRenderer { // 存储Pin与对应自定义信息窗口的绑定关系 private readonly Dictionary<Pin, Android.Views.View> _pinInfoWindowMapping = new(); // 承载所有自定义信息窗口的父容器 private FrameLayout _infoWindowContainer; protected override void OnElementChanged(ElementChangedEventArgs<Map> e) { base.OnElementChanged(e); if (e.OldElement != null && NativeMap != null) { NativeMap.CameraChange -= OnCameraChanged; } if (e.NewElement != null) { // 初始化信息窗口承载容器,叠加在地图上层 _infoWindowContainer = new FrameLayout(Context); AddView(_infoWindowContainer); } } protected override void OnMapReady(GoogleMap nativeMap, Xamarin.Forms.GoogleMaps.Map map) { base.OnMapReady(nativeMap, map); // 监听地图相机移动、缩放事件,实时更新信息窗口位置 NativeMap.CameraChange += OnCameraChanged; // 批量生成所有Pin对应的信息窗口 GenerateAllInfoWindows(); } protected override void OnMarkerCreated(Pin pin, Marker marker) { marker.Position = new LatLng(pin.Position.Latitude, pin.Position.Longitude); marker.Title = pin.Label; marker.Snippet = pin.Address; marker.SetIcon(pin.Type.Equals(PinType.Generic) ? BitmapDescriptorFactory.FromResource(Resource.Drawable.circle) : BitmapDescriptorFactory.FromResource(Resource.Drawable.circle_closed)); // 不再调用原生的ShowInfoWindow方法 } /// <summary> /// 为所有Pin生成对应的自定义信息窗口 /// </summary> private void GenerateAllInfoWindows() { _infoWindowContainer.RemoveAllViews(); _pinInfoWindowMapping.Clear(); var inflater = Context.GetSystemService(Context.LayoutInflaterService) as LayoutInflater; if (inflater == null) return; foreach (var pin in Element.Pins) { // 复用你已有的自定义信息窗口布局 var infoWindow = inflater.Inflate(Resource.Layout.CustomMapInfoWindow, null); infoWindow.FindViewById<TextView>(Resource.Id.waypointNo).Text = pin.Label; infoWindow.FindViewById<TextView>(Resource.Id.address).Text = pin.Address; // 预先测量视图大小,避免定位偏移 infoWindow.Measure(MeasureSpec.MakeMeasureSpec(0, MeasureSpec.Unspecified), MeasureSpec.MakeMeasureSpec(0, MeasureSpec.Unspecified)); infoWindow.Layout(0, 0, infoWindow.MeasuredWidth, infoWindow.MeasuredHeight); _pinInfoWindowMapping.Add(pin, infoWindow); _infoWindowContainer.AddView(infoWindow); } // 首次加载后先更新一次位置 UpdateAllInfoWindowPosition(); } private void OnCameraChanged(object sender, GoogleMap.CameraChangeEventArgs e) { UpdateAllInfoWindowPosition(); } /// <summary> /// 计算每个信息窗口的屏幕坐标,更新位置使其始终绑定在对应Pin的上方 /// </summary> private void UpdateAllInfoWindowPosition() { if (NativeMap == null || _pinInfoWindowMapping.Count == 0) return; foreach (var (pin, infoWindow) in _pinInfoWindowMapping) { var geoPoint = new LatLng(pin.Position.Latitude, pin.Position.Longitude); // 将地理坐标转换为当前地图的屏幕像素坐标 var screenPoint = NativeMap.Projection.ToScreenLocation(geoPoint); // 调整位置,使信息窗口显示在图钉上方,可根据实际布局调整偏移量 var offsetX = screenPoint.X - infoWindow.MeasuredWidth / 2; var offsetY = screenPoint.Y - infoWindow.MeasuredHeight - 20; // 20为图钉与信息窗口的间距 infoWindow.SetX(offsetX); infoWindow.SetY(offsetY); } } protected override void Dispose(bool disposing) { if (disposing && NativeMap != null) { NativeMap.CameraChange -= OnCameraChanged; _pinInfoWindowMapping?.Clear(); } base.Dispose(disposing); } } }
补充说明
- 可根据你的UI设计调整
UpdateAllInfoWindowPosition方法中的偏移量,避免信息窗口遮挡图钉 - 如果业务中有动态新增/删除Pin的需求,调用
GenerateAllInfoWindows方法刷新所有信息窗口即可 - 自定义信息窗口支持直接添加点击、长按等交互事件,比原生InfoWindow的扩展性更强
内容的提问来源于stack exchange,提问作者Sechaba Motaung
相关产品推荐
相关产品推荐

