我正试图在我的应用程序中集成谷歌地图,并为其提供放置标记的来源和目的地。从过去几天开始,它运行正常,一点错误都没有。今天突然出现了这个错误,地图无法工作。尽管它没有在屏幕上显示谷歌地图,只是在空白的白色屏幕上。
class MapView extends StatefulWidget {
@override
_MapViewState createState() => _MapViewState();
}
class _MapViewState extends State<MapView> {
CameraPosition _initialLocation = CameraPosition(target: LatLng(0.0, 0.0));
**late GoogleMapController mapController ;**
late Position _currentPosition;
String _currentAddress = '';
final startAddressController = TextEditingController();
final destinationAddressController = TextEditingController();
final startAddressFocusNode = FocusNode();
final destinationAddressFocusNode = FocusNode();
String _startAddress = '';
String _destinationAddress = '';
double? _placeDistance;
Set<Marker> markers = {};
late PolylinePoints polylinePoints;
List<LatLng> polylineCoordinates = [];
Map<PolylineId, Polyline> polylines = {};
final _scaffoldKey = GlobalKey<ScaffoldState>();
Widget _textField({
required TextEditingController controller,
required FocusNode focusNode,
required String label,
required String hint,
required double width,
required Icon prefixIcon,
Widget? suffixIcon,
required Function(String) locationCallback,
}) {
return Container(
width: width * 0.8,
child: TextField(
onChanged: (value) {
locationCallback(value);
},
controller: controller,
focusNode: focusNode,
decoration: new InputDecoration(
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
labelText: label,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
borderSide: BorderSide(
color: Colors.grey.shade400,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
borderSide: BorderSide(
color: Colors.blue.shade300,
width: 2,
),
),
contentPadding: EdgeInsets.all(15),
hintText: hint,
),
),
);
}
// Method for retrieving the current location
_getCurrentLocation() async {
await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
.then((Position position) async {
setState(() {
_currentPosition = position;
print('CURRENT POS: $_currentPosition');
**mapController.animateCamera(**
CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(position.latitude, position.longitude),
zoom: 18.0,
),
),
);
});
await _getAddress();
}).catchError((e) {
print(e);
});
}
下一部分:
_getAddress() async {
try {
List<Placemark> p = await placemarkFromCoordinates(
_currentPosition.latitude, _currentPosition.longitude);
Placemark place = p[0];
setState(() {
_currentAddress =
"${place.name}, ${place.locality}, ${place.postalCode}, ${place.country}";
startAddressController.text = _currentAddress;
_startAddress = _currentAddress;
});
} catch (e) {
print(e);
}
}
// Method for calculating the distance between two places
Future<bool> _calculateDistance() async {
try {
// Retrieving placemarks from addresses
List<Location> startPlacemark = await locationFromAddress(_startAddress);
List<Location> destinationPlacemark =
await locationFromAddress(_destinationAddress);
// Use the retrieved coordinates of the current position,
// instead of the address if the start position is user's
// current position, as it results in better accuracy.
double startLatitude = _startAddress == _currentAddress
? _currentPosition.latitude
: startPlacemark[0].latitude;
double startLongitude = _startAddress == _currentAddress
? _currentPosition.longitude
: startPlacemark[0].longitude;
double destinationLatitude = destinationPlacemark[0].latitude;
double destinationLongitude = destinationPlacemark[0].longitude;
String startCoordinatesString = '($startLatitude, $startLongitude)';
String destinationCoordinatesString =
'($destinationLatitude, $destinationLongitude)';
// Start Location Marker
Marker startMarker = Marker(
markerId: MarkerId(startCoordinatesString),
position: LatLng(startLatitude, startLongitude),
infoWindow: InfoWindow(
title: 'Start $startCoordinatesString',
snippet: _startAddress,
),
icon: BitmapDescriptor.defaultMarker,
);
// Destination Location Marker
Marker destinationMarker = Marker(
markerId: MarkerId(destinationCoordinatesString),
position: LatLng(destinationLatitude, destinationLongitude),
infoWindow: InfoWindow(
title: 'Destination $destinationCoordinatesString',
snippet: _destinationAddress,
),
icon: BitmapDescriptor.defaultMarker,
);
// Adding the markers to the list
markers.add(startMarker);
markers.add(destinationMarker);
print(
'START COORDINATES: ($startLatitude, $startLongitude)',
);
print(
'DESTINATION COORDINATES: ($destinationLatitude, $destinationLongitude)',
);
// Calculating to check that the position relative
// to the frame, and pan & zoom the camera accordingly.
double miny = (startLatitude <= destinationLatitude)
? startLatitude
: destinationLatitude;
double minx = (startLongitude <= destinationLongitude)
? startLongitude
: destinationLongitude;
double maxy = (startLatitude <= destinationLatitude)
? destinationLatitude
: startLatitude;
double maxx = (startLongitude <= destinationLongitude)
? destinationLongitude
: startLongitude;
double southWestLatitude = miny;
double southWestLongitude = minx;
double northEastLatitude = maxy;
double northEastLongitude = maxx;
// Accommodate the two locations within the
// camera view of the map
mapController.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
northeast: LatLng(northEastLatitude, northEastLongitude),
southwest: LatLng(southWestLatitude, southWestLongitude),
),
100.0,
),
);
// Calculating the distance between the start and the end positions
// with a straight path, without considering any route
// double distanceInMeters = await Geolocator.bearingBetween(
// startLatitude,
// startLongitude,
// destinationLatitude,
// destinationLongitude,
// );
await _createPolylines(startLatitude, startLongitude, destinationLatitude,
destinationLongitude);
double totalDistance = 0.0;
// Calculating the total distance by adding the distance
// between small segments
for (int i = 0; i < polylineCoordinates.length - 1; i++) {
totalDistance += _coordinateDistance(
polylineCoordinates[i].latitude,
polylineCoordinates[i].longitude,
polylineCoordinates[i + 1].latitude,
polylineCoordinates[i + 1].longitude,
);
}
setState(() {
_placeDistance = totalDistance;
print('DISTANCE: $_placeDistance km');
});
return true;
} catch (e) {
print(e);
}
return false;
}
// Formula for calculating distance between two coordinates
// https://stackoverflow.com/a/54138876/11910277
double _coordinateDistance(lat1, lon1, lat2, lon2) {
var p = 0.017453292519943295;
var c = cos;
var a = 0.5 -
c((lat2 - lat1) * p) / 2 +
c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2;
return 12742 * asin(sqrt(a));
}
// Create the polylines for showing the route between two places
_createPolylines(
double startLatitude,
double startLongitude,
double destinationLatitude,
double destinationLongitude,
) async {
polylinePoints = PolylinePoints();
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
Secrets.API_KEY, // Google Maps API Key
PointLatLng(startLatitude, startLongitude),
PointLatLng(destinationLatitude, destinationLongitude),
travelMode: TravelMode.driving,
);
result.points.forEach((PointLatLng point) {
polylineCoordinates.add(LatLng(point.latitude, point.longitude));
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: Text(
'Poly here'),
),
);
});
PolylineId id = PolylineId('poly');
Polyline polyline = Polyline(
polylineId: id,
color: Colors.red,
points: polylineCoordinates,
width: 3,
);
polylines[id] = polyline;
setState(() {});
}
@override
void initState() {
super.initState();
_getCurrentLocation();
_calculateDistance();
}
@override
void dispose() {
mapController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
return Container(
height: height,
width: width,
child: Scaffold(
key: _scaffoldKey,
body: Stack(
children: <Widget>[
// Map View
GoogleMap(
markers: Set<Marker>.from(markers),
initialCameraPosition: _initialLocation,
myLocationEnabled: true,
myLocationButtonEnabled: false,
mapType: MapType.normal,
zoomGesturesEnabled: true,
zoomControlsEnabled: false,
polylines: Set<Polyline>.of(polylines.values),
onMapCreated: (GoogleMapController controller) {
setState(() {
mapCreated = true;
});
mapController = controller;
},
),
// Show zoom buttons
SafeArea(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipOval(
child: Material(
color: Colors.blue.shade100, // button color
child: InkWell(
splashColor: Colors.blue, // inkwell color
child: SizedBox(
width: 50,
height: 50,
child: Icon(Icons.add),
),
onTap: () {
mapController.animateCamera(
CameraUpdate.zoomIn(),
);
},
),
),
),
创建GoogleMap
小部件时,必须存储GoogleMapController
,如下所示:
GoogleMap(
onMapCreated: (GoogleMapController controller) {
// here save the value
mapController = controller;
}
在尝试访问mapController
(例如使用FutureBuilder
(之前,您必须确保此onMapCreated
已运行,否则您将不断收到此错误消息。另一个选项是删除late
关键字,允许成员为null,并在使用前检查null值。
此外,不要忘记正确处理控制器:
@override
void dispose() {
mapController.dispose();
super.dispose();
}
在您的代码中,您可以从initState
调用_getCurrentLocation
async
函数。这通常不是一个好方法,因为initState
不能是async
函数,所以在第一次构建小部件之前,没有办法使用await
来确保这些函数已经完成。
但在这种情况下,最大的问题是只有在创建了GoogleMap
小部件之后,您才会有一个有效的GoogleMapController
。这就是onMapCreated
的主要作用,在创建映射后获得GoogleMapController
。
由于在_getCurrentLocation
中您尝试访问mapController
,所以它还不可用,因为它可能会在创建GoogleMap
之前执行。
现在,_getCurrentLocation
使用await
来获取当前位置,一旦完成,它就会尝试调用mapController.animateCamera
。这就是为什么你可以更早地体验到它的作用。这是一个典型的赛车问题。如果Geolocator.getCurrentPosition
花费了足够的时间,则可能已经构建了GoogleMap
,因此您有一个有效的GoogleMapController
。但你不能确定,正如你目前的错误所表明的那样,这取决于获得当前职位需要多长时间。
解决方案是什么?您必须重新组织您的代码,以便只有在创建了GoogleMap
之后才能使用mapController
。例如,将_getCurrentLocation
函数调用从initState
移动到onMapCreated
,这样可以确保您有一个有效的映射控制器。但是,在mapController
可用之前,请确保没有任何代码依赖于它。
以下是一些代码,展示了如何获得这项工作:
class MyPageHomeMap extends StatefulWidget {
const MyPageHomeMap({Key? key}) : super(key: key);
@override
State<MyPageHomeMap> createState() => _MyPageHomeMapState();
}
class _MyPageHomeMapState extends State<MyPageHomeMap> {
bool _mapCreated = false;
late GoogleMapController mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
GoogleMap(
initialCameraPosition: <something>,
onMapCreated: _onMapCreated),
if (!_mapCreated) const Center(child: CircularProgressIndicator())
],
),
);
}
_onMapCreated(GoogleMapController controller) {
mapController = controller;
setState(() {
_mapCreated = true;
});
// get current position here and use mapController when it is completed
}
}