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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 11:35:07