Flutter Google Maps:无需InfoWindow为Polyline添加静态旁侧标签
在Flutter Google Maps中为Polyline添加静态旁侧标签的实现方法
下面提供两种高效的实现方案,均无需依赖InfoWindow,可直接在地图上静态展示文本标签:
方法1:自定义文本Marker(无交互)
通过生成带文本的自定义图片作为Marker图标,将其放置在Polyline的关键位置(如中点、端点),并禁用交互属性,避免干扰地图操作。
代码示例
import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'dart:typed_data'; import 'package:flutter/rendering.dart'; // 生成带文本的Marker图标 Future<BitmapDescriptor> createTextMarker(String text) async { final ui.Image image = await _generateTextImage(text); final ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); final Uint8List pngBytes = byteData!.buffer.asUint8List(); return BitmapDescriptor.fromBytes(pngBytes); } Future<ui.Image> _generateTextImage(String text) async { final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); final TextPainter textPainter = TextPainter( text: TextSpan( text: text, style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold), ), textDirection: TextDirection.ltr, ); textPainter.layout(); // 绘制白色背景矩形 canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH(0, 0, textPainter.width + 10, textPainter.height + 8), Radius.circular(4), ), Paint()..color = Colors.white, ); // 绘制文本 textPainter.paint(canvas, Offset(5, 4)); return await recorder.endRecording().toImage( (textPainter.width + 10).ceil(), (textPainter.height + 8).ceil(), ); } class MapScreen extends StatefulWidget { @override _MapScreenState createState() => _MapScreenState(); } class _MapScreenState extends State<MapScreen> { late GoogleMapController mapController; final Set<Polyline> _polylines = {}; final Set<Marker> _markers = {}; @override void initState() { super.initState(); _initPolylineAndLabel(); } void _initPolylineAndLabel() async { // 定义Polyline坐标 final List<LatLng> routePoints = [ LatLng(37.7749, -122.4194), LatLng(37.7895, -122.4010), ]; // 添加Polyline _polylines.add(Polyline( polylineId: PolylineId('bus_route'), points: routePoints, width: 5, color: Colors.blue, )); // 计算Polyline中点坐标 final LatLng midPoint = LatLng( (routePoints[0].latitude + routePoints[1].latitude) / 2, (routePoints[0].longitude + routePoints[1].longitude) / 2, ); // 创建文本Marker并添加到地图 final BitmapDescriptor textMarker = await createTextMarker('公交线路A'); _markers.add(Marker( markerId: MarkerId('route_label'), position: midPoint, icon: textMarker, draggable: false, clickable: false, // 禁用点击交互 zIndex: 1, // 确保标签显示在Polyline上方 )); setState(() {}); } @override Widget build(BuildContext context) { return Scaffold( body: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(37.7749, -122.4194), zoom: 14), onMapCreated: (controller) => mapController = controller, polylines: _polylines, markers: _markers, ), ); } }
方法2:使用OverlayEntry绘制悬浮文本
通过Flutter的Overlay机制,在地图上方绘制文本标签,实时监听地图相机移动事件,更新标签的屏幕位置,适合需要随地图缩放保持文本大小的场景。
代码示例
import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; class MapScreen extends StatefulWidget { @override _MapScreenState createState() => _MapScreenState(); } class _MapScreenState extends State<MapScreen> { late GoogleMapController mapController; final Set<Polyline> _polylines = {}; OverlayEntry? _labelOverlay; final List<LatLng> _routePoints = [ LatLng(37.7749, -122.4194), LatLng(37.7895, -122.4010), ]; @override void initState() { super.initState(); _initPolyline(); } void _initPolyline() { _polylines.add(Polyline( polylineId: PolylineId('bus_route'), points: _routePoints, width: 5, color: Colors.blue, )); setState(() {}); } void _initLabelOverlay() async { // 获取Polyline中点的屏幕坐标 final LatLng midPoint = LatLng( (_routePoints[0].latitude + _routePoints[1].latitude) / 2, (_routePoints[0].longitude + _routePoints[1].longitude) / 2, ); final ScreenCoordinate screenCoord = await mapController.getScreenCoordinate(midPoint); // 创建悬浮文本Overlay _labelOverlay = OverlayEntry( builder: (context) => Positioned( left: screenCoord.x.toDouble() + 10, // 偏移避免与线条重叠 top: screenCoord.y.toDouble() - 20, child: Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 2)], ), child: Text( '公交线路A', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), ), ), ); Overlay.of(context).insert(_labelOverlay!); } void _updateLabelPosition() async { if (_labelOverlay == null) return; final LatLng midPoint = LatLng( (_routePoints[0].latitude + _routePoints[1].latitude) / 2, (_routePoints[0].longitude + _routePoints[1].longitude) / 2, ); final ScreenCoordinate screenCoord = await mapController.getScreenCoordinate(midPoint); // 更新Overlay位置 _labelOverlay!.markNeedsBuild(); _labelOverlay = _labelOverlay!.copyWith( builder: (context) => Positioned( left: screenCoord.x.toDouble() + 10, top: screenCoord.y.toDouble() - 20, child: Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(4), boxShadow: [BoxShadow(color: Colors.black26, blurRadius: 2)], ), child: Text( '公交线路A', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), ), ), ), ); } @override void dispose() { _labelOverlay?.remove(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: GoogleMap( initialCameraPosition: CameraPosition(target: LatLng(37.7749, -122.4194), zoom: 14), onMapCreated: (controller) { mapController = controller; _initLabelOverlay(); }, onCameraMove: (_) => _updateLabelPosition(), polylines: _polylines, ), ); } }
方案选择建议
- 若标签无需随地图缩放调整大小,方法1的自定义Marker实现更简单,性能更稳定。
- 若需要标签保持固定大小、不随地图缩放变化,方法2的Overlay方案更合适。
内容的提问来源于stack exchange,提问作者caio2810
相关产品推荐
相关产品推荐

