像 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 相同。
我们将使用 StreamController
和 StreamBuilder
来完成我们的工作。
创建类型为 LatLng
的流控制器
StreamController<LatLng> streamController = StreamController();
然后在 GoogleMap
小部件内的 onCameraMove
函数中添加 LatLng
streamController
onCameraMove: (CameraPosition pos) {
streamController.add(pos.target);
}
- 然后使用
StreamBuilder
使用流
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();
}
},
)
- 处理/关闭它,以避免任何类型的内存泄漏
@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,
// ),
// ),
),
),
),
)
],
),
))
],
),
)),
);
}
}
我正在尝试在我的 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 相同。
我们将使用 StreamController
和 StreamBuilder
来完成我们的工作。
创建类型为
的流控制器LatLng
StreamController<LatLng> streamController = StreamController();
然后在
GoogleMap
小部件内的onCameraMove
函数中添加LatLng
streamController
onCameraMove: (CameraPosition pos) {
streamController.add(pos.target);
}
- 然后使用
StreamBuilder
使用流
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();
}
},
)
- 处理/关闭它,以避免任何类型的内存泄漏
@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,
// ),
// ),
),
),
),
)
],
),
))
],
),
)),
);
}
}