像 Zomato 或 Swiggy 一样在 Flutter 中实现 google 地图

Implementing google map in Flutter just like Zomato or Swiggy

我正在尝试在我的 flutter 应用程序中实现一个地图,其中包含一个指向地图中心的标记,当我们停止拖动地图时,我们会得到中心位置的经纬度。 受到 Zomato 和 Swiggy 地图的启发 UI . 如何实现这个??

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:geolocator/geolocator.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

void main() => runApp(const InitApp());

class InitApp extends StatelessWidget {
  const InitApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: RenderMap(),
    );
  }
}

class RenderMap extends StatefulWidget {
  @override
  State<RenderMap> createState() => _RenderMapState();
}

class _RenderMapState extends State<RenderMap> {
  late Position _currentLocation;
  late GoogleMapController _controller;
  bool loadingMap = false;
  bool init = true;

  getCurrentLoc() async {
    bool serviceEnabled;
    LocationPermission permission;
    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return Future.error('Location services are disabled.');
    }
    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return Future.error('Location permissions are denied');
      }
    }
    if (permission == LocationPermission.deniedForever) {
      return Future.error(
          'Location permissions are permanently denied, we cannot request permissions.');
    }
    _currentLocation = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);
    setState(() {});
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    getCurrentLoc();
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    _controller.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
          body: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        child: Stack(
          alignment: Alignment.center,
          children: [
            (loadingMap)
                ? const Center(
                    child: CircularProgressIndicator(),
                  )
                : GoogleMap(
                    zoomControlsEnabled: false,
                    myLocationButtonEnabled: true,
                    indoorViewEnabled: false,
                    onMapCreated: (controller) {
                      _controller = controller;
                    },
                    initialCameraPosition: CameraPosition(
                      target: LatLng(_currentLocation.latitude,
                          _currentLocation.longitude),
                      zoom: 16,
                    ),
                    mapType: MapType.normal,
                  ),
            Image.asset(
              'assets/pin.png',
              height: 35,
              fit: BoxFit.cover,
            ),
          ],
        ),
      )),
    );
  }
}

这是源代码,我正在使用堆栈小部件将地图和标记放在中心,现在我想知道图钉指向的位置的经纬度。由于图钉位于中心,我只需要 cameraPosition 的 latlong 即可。

import 'dart:async'; 的 dart 中有一个名为 Streams 的 Api 它与某些状态管理库在幕后使用的 API 相同。

我们将使用 StreamControllerStreamBuilder 来完成我们的工作。

  1. 创建类型为 LatLng

    的流控制器

    StreamController<LatLng> streamController = StreamController();

  2. 然后在 GoogleMap 小部件内的 onCameraMove 函数中添加 LatLng streamController

    onCameraMove: (CameraPosition pos) {
        streamController.add(pos.target);
    }
  1. 然后使用 StreamBuilder
  2. 使用流
StreamBuilder<LatLng>(
  stream: streamController.stream,
  builder: (context, snapshot) {
   if (snapshot.hasData) {
    return Column(
    children: [
      Text('${snapshot.data!.latitude}'),
      Text('${snapshot.data!.longitude}'),
                                        ],
     );
    } else {
   return const CircularProgressIndicator();
    }
    },
    )
  1. 处理/关闭它,以避免任何类型的内存泄漏
  @override
  void dispose() {
    streamController.close();
    super.dispose();
  }

完整代码:希望这有效

import 'dart:async';
import 'package:geocoding/geocoding.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class NewAddressPage extends StatefulWidget {
  @override
  State<NewAddressPage> createState() => _NewAddressPageState();
}

class _NewAddressPageState extends State<NewAddressPage> {
  late GoogleMapController _controller;
  LatLng? markerPos;
  LatLng? initPos;
  Set<Marker> markers = {};
  TextEditingController? searchPlaceController;
  bool loadingMap = false;
  bool init = true;
  bool loadingAddressDetails = false;
  String addressTitle = '';
  String locality = '';
  String city = '';
  String state = '';
  String pincode = '';

  StreamController<LatLng> streamController = StreamController();

  void fetchAddressDetail(LatLng location) async {
    List<Placemark> placemarks =
        await placemarkFromCoordinates(location.latitude, location.longitude);
    addressTitle = placemarks[0].name!;
    locality = placemarks[0].locality!;
    city = placemarks[0].subLocality!;
    pincode = placemarks[0].postalCode!;
    state = placemarks[0].administrativeArea!;
  }

  getCurrentLoc() async {
    bool serviceEnabled;
    LocationPermission permission;
    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return Future.error('Location services are disabled.');
    }
    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return Future.error('Location permissions are denied');
      }
    }
    if (permission == LocationPermission.deniedForever) {
      return Future.error(
          'Location permissions are permanently denied, we cannot request permissions.');
    }
    Position position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high);
    initPos = LatLng(position.latitude, position.longitude);
    streamController.add(initPos as LatLng);
    setState(() {
      loadingMap = false;
    });
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    loadingMap = true;
    getCurrentLoc();
    searchPlaceController = TextEditingController();
  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose();
    streamController.close();

  }

  renderMap() {
    return SizedBox(
      height: MediaQuery.of(context).size.height,
      width: MediaQuery.of(context).size.width,
      child: (loadingMap)
          ? const Center(
              child: CircularProgressIndicator(),
            )
          : GoogleMap(
              zoomControlsEnabled: false,
              myLocationEnabled: true,
              buildingsEnabled: true,
              indoorViewEnabled: false,
              onMapCreated: (controller) {
                _controller = controller;
                setState(() {
                  fetchAddressDetail(initPos!);
                });
              },
              onCameraMove: (CameraPosition pos) {
                streamController.add(pos.target);
              },
              initialCameraPosition: CameraPosition(
                target: initPos!,
                zoom: 14.4746,
              ),
              mapType: MapType.normal,
            ),
    );
  }

  backButton() {
    return IconButton(
      onPressed: () {
        Navigator.pop(context);
      },
      color: Colors.black87,
      icon: const Icon(Icons.arrow_back),
    );
  }

  searchBox() {
    return Container(
      height: MediaQuery.of(context).size.height,
      width: MediaQuery.of(context).size.height,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15),
        border: Border.all(color: Colors.black87, width: 0.1),
        color: Colors.white,
      ),
      padding: const EdgeInsets.all(10),
      child: Center(
        child: TextFormField(
          controller: searchPlaceController,
          onChanged: (value) async {
            // ignore
          },
          decoration: const InputDecoration(
              contentPadding: EdgeInsets.all(8),
              border: InputBorder.none,
              hintText: "Search Places...",
              labelStyle: TextStyle(color: Colors.black87)),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
          appBar: AppBar(
            title: searchBox(),
          ),
          body: SizedBox(
            child: Stack(
              alignment: Alignment.center,
              children: [
                renderMap(),
                Positioned(
                    top: MediaQuery.of(context).size.height * 0.4,
                    child: Image.asset(
                      'assets/pin.png',
                      height: 30,
                      fit: BoxFit.cover,
                    )),
                Positioned(
                    bottom: 0,
                    child: Container(
                      height: MediaQuery.of(context).size.height * 0.2,
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(15),
                      decoration: const BoxDecoration(
                          color: Colors.white,
                          borderRadius: BorderRadius.only(
                              topRight: Radius.circular(10),
                              topLeft: Radius.circular(10))),
                      child: Column(
                        mainAxisAlignment: MainAxisAlignment.start,
                        children: [
                          Row(
                            mainAxisAlignment: MainAxisAlignment.start,
                            children: [
                              Icon(
                                Icons.location_on,
                                color: Colors.red[200],
                                size: MediaQuery.of(context).size.width * 0.08,
                              ),
                              const Padding(padding: EdgeInsets.all(2)),
                              Column(
                                mainAxisAlignment: MainAxisAlignment.start,
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    addressTitle,
                                    style: const TextStyle(
                                        overflow: TextOverflow.ellipsis,
                                        fontWeight: FontWeight.bold,
                                        fontSize: 10,
                                        color: Colors.black87),
                                  ),
                                  const Padding(padding: EdgeInsets.all(2)),
                                  Text(
                                    city,
                                    style: const TextStyle(
                                      fontSize: 6,
                                      color: Colors.black54,
                                    ),
                                  )
                                ],
                              )
                            ],
                          ),
                          const Padding(padding: EdgeInsets.all(10)),
                          Expanded(
                            child: InkWell(
                              onTap: () {
                                showModalBottomSheet(
                                    shape: const RoundedRectangleBorder(
                                        borderRadius: BorderRadius.vertical(
                                            top: Radius.circular(10.0))),
                                    backgroundColor: Colors.white,
                                    context: context,
                                    isScrollControlled: true,
                                    builder: (context) => const Text('ignore'));
                              },
                              child: Container(
                                decoration: BoxDecoration(
                                    color: Colors.red[400],
                                    borderRadius: BorderRadius.circular(5)),
                                height:
                                    MediaQuery.of(context).size.height * 0.07,
                                width: MediaQuery.of(context).size.width * 0.8,
                                child: Center(
                                    child: StreamBuilder<LatLng>(
                                  stream: streamController.stream,
                                  builder: (context, snapshot) {
                                    if (snapshot.hasData) {
                                      return Column(
                                        children: [
                                          Text('${snapshot.data!.latitude}'),
                                          Text('${snapshot.data!.longitude}'),
                                        ],
                                      );
                                    } else {
                                      return const CircularProgressIndicator();
                                    }
                                  },
                                )

                                    // Text(
                                    //   'Confirm Address',
                                    //   style: TextStyle(
                                    //     color: Colors.white,
                                    //     fontSize: 15,
                                    //   ),
                                    // ),
                                    ),
                              ),
                            ),
                          )
                        ],
                      ),
                    ))
              ],
            ),
          )),
    );
  }
}