You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 08:00:04