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

Flutter中为Google Maps标记的onTap添加DropdownButton功能

解决Flutter Google Maps Marker点击显示下拉菜单的问题

原代码核心问题

  1. Marker的onTap是VoidCallback,仅用于触发动作,不能直接在回调中渲染Widget,Widget必须在组件的build方法中构建。
  2. MarkerService是普通类,未继承State,因此无法调用setState,状态管理逻辑应该放在StatefulWidget的State子类中。

正确实现方案

步骤1:重构MarkerService

让服务类只负责创建Marker,将点击逻辑通过回调传递给外部的State类:

class MarkerService {
  Marker createMarkerFromPlace(Place place, VoidCallback onTap) {
    final markerId = place.name;

    return Marker(
      markerId: MarkerId(markerId),
      draggable: false,
      infoWindow: InfoWindow(title: place.name, snippet: place.vicinity),
      position: LatLng(place.geometry.location.lat, place.geometry.location.lng),
      onTap: onTap,
    );
  }
}

步骤2:在State类中管理状态与UI

在包含GoogleMap的StatefulWidget中,维护标记选中状态、下拉菜单显示状态和选中值,通过Stack将下拉菜单叠加在地图上方:

class MapScreen extends StatefulWidget {
  const MapScreen({super.key});

  @override
  State<MapScreen> createState() => _MapScreenState();
}

class _MapScreenState extends State<MapScreen> {
  final MarkerService _markerService = MarkerService();
  List<Marker> _markers = [];
  Place? _selectedPlace;
  String _dropdownValue = 'One';
  final List<String> _dropdownList = ['One', 'Two', 'Three', 'Four'];
  bool _showDropdown = false;

  // 初始化标记(示例:从Place列表生成)
  void _initMarkers(List<Place> places) {
    setState(() {
      _markers = places.map((place) => _markerService.createMarkerFromPlace(
        place,
        () => _onMarkerTap(place),
      )).toList();
    });
  }

  // 标记点击回调:更新状态显示下拉菜单
  void _onMarkerTap(Place place) {
    setState(() {
      _selectedPlace = place;
      _showDropdown = true;
      _dropdownValue = _dropdownList.first; // 每次点击重置选中项
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        GoogleMap(
          markers: Set.of(_markers),
          initialCameraPosition: const CameraPosition(
            target: LatLng(37.7749, -122.4194),
            zoom: 12,
          ),
        ),
        // 叠加在地图上方的下拉菜单
        if (_showDropdown && _selectedPlace != null)
          Positioned(
            top: 20,
            left: 20,
            right: 20,
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 16),
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(8),
                boxShadow: const [BoxShadow(blurRadius: 4, color: Colors.black26)],
              ),
              child: DropdownButton<String>(
                value: _dropdownValue,
                isExpanded: true,
                icon: const Icon(Icons.arrow_downward),
                elevation: 16,
                style: const TextStyle(color: Colors.deepPurple),
                underline: Container(
                  height: 2,
                  color: Colors.deepPurpleAccent,
                ),
                onChanged: (String? value) {
                  setState(() {
                    _dropdownValue = value!;
                  });
                  // 这里添加选中下拉项后的业务逻辑
                },
                items: _dropdownList.map<DropdownMenuItem<String>>((String value) {
                  return DropdownMenuItem<String>(
                    value: value,
                    child: Text(value),
                  );
                }).toList(),
              ),
            ),
          ),
      ],
    );
  }
}

可选方案:用弹窗展示下拉菜单

如果希望点击标记后弹出对话框显示下拉菜单,修改_onMarkerTap方法即可:

void _onMarkerTap(Place place) {
  setState(() {
    _selectedPlace = place;
    _dropdownValue = _dropdownList.first;
  });
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: Text(place.name),
      content: DropdownButton<String>(
        value: _dropdownValue,
        isExpanded: true,
        icon: const Icon(Icons.arrow_downward),
        elevation: 16,
        style: const TextStyle(color: Colors.deepPurple),
        underline: Container(
          height: 2,
          color: Colors.deepPurpleAccent,
        ),
        onChanged: (String? value) {
          setState(() {
            _dropdownValue = value!;
          });
        },
        items: _dropdownList.map<DropdownMenuItem<String>>((String value) {
          return DropdownMenuItem<String>(
            value: value,
            child: Text(value),
          );
        }).toList(),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('确定'),
        ),
      ],
    ),
  );
}

内容的提问来源于stack exchange,提问作者Hrishikesh Garud

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 11:20:29