我目前正在研究一些要包含在 Flutter 应用程序中的 Google 地图功能。我尝试了几天来获取 OpeningHoursDetail(开放/关闭、日期和时间)并显示为文本,但它没有显示任何结果或显示任何错误。有人知道如何解决这个问题并获取详细信息吗?第二个问题是搜索功能,我在输入地址或地名后无法获得他们的自动完成推荐或搜索结果,哪一部分出了问题?我已经启用 Places and Maps SDK for API,包括 Android XML 文件中的所有权限。任何人都可以指出或修改代码来为我展示解决方案,感谢这些帮助,谢谢。
代码:
import 'package:flutter/material.dart';
import 'package:flutter_google_places/flutter_google_places.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_webservice/places.dart';
import 'package:location/location.dart' as LocationManager;
const kGoogleApiKey = "REPLACE_WITH_YOUR_OWN_API_KEY";
GoogleMapsPlaces _places = GoogleMapsPlaces(apiKey: kGoogleApiKey);
class GoogleMapView extends StatefulWidget {
const GoogleMapView({super.key});
@override
State<GoogleMapView> createState() => _GoogleMapViewState();
}
class _GoogleMapViewState extends State<GoogleMapView> {
late GoogleMapController mapController;
List<PlacesSearchResult> places = [];
bool isLoading = false;
String? errorMessage;
late TextEditingController _searchController;
@override
void initState() {
super.initState();
_searchController = TextEditingController();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Nearby Places"),
actions: [
IconButton(
onPressed: _searchPlaces,
icon: const Icon(Icons.search),
),
],
),
body: Stack(
children: [
GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: const CameraPosition(
target: LatLng(0, 0), // Default: Center of the world
zoom: 12,
),
markers: _buildMarkers(),
),
if (isLoading)
const Center(
child: CircularProgressIndicator(),
),
if (errorMessage != null)
Center(
child: Text(errorMessage!),
),
],
),
);
}
Set<Marker> _buildMarkers() {
return places.map((place) {
return Marker(
markerId: MarkerId(place.placeId),
position:
LatLng(place.geometry!.location.lat, place.geometry!.location.lng),
onTap: () {
_showPlaceDetails(place);
},
);
}).toSet();
}
void _onMapCreated(GoogleMapController controller) {
setState(() {
mapController = controller;
_getCurrentLocation();
});
}
Future<void> _getCurrentLocation() async {
try {
LocationManager.LocationData? currentLocation =
await LocationManager.Location().getLocation();
LatLng userLocation =
LatLng(currentLocation.latitude!, currentLocation.longitude!);
mapController.animateCamera(CameraUpdate.newLatLngZoom(userLocation, 12));
_getNearbyPlaces(userLocation);
} catch (e) {
setState(() {
errorMessage = "Error getting current location";
});
}
}
Future<void> _getNearbyPlaces(LatLng userLocation) async {
setState(() {
isLoading = true;
errorMessage = null;
});
final location =
Location(lat: userLocation.latitude, lng: userLocation.longitude);
final result = await _places.searchNearbyWithRadius(
location,
5000, // Radius in meters, adjust as needed
type: "restaurant", // Specify the desired place type
);
setState(() {
isLoading = false;
if (result.status == "OK") {
places = result.results;
} else {
errorMessage = result.errorMessage;
}
});
}
Future<void> _searchPlaces() async {
Prediction? prediction = await PlacesAutocomplete.show(
context: context,
apiKey: kGoogleApiKey,
mode: Mode.overlay,
components: [
Component(Component.country, "MY"),
Component(Component.country, "SG")
],
);
if (prediction != null) {
PlacesDetailsResponse details =
await _places.getDetailsByPlaceId(prediction.placeId!);
PlaceDetails placeDetails = details.result;
LatLng location = LatLng(
placeDetails.geometry!.location.lat,
placeDetails.geometry!.location.lng,
);
mapController.animateCamera(CameraUpdate.newLatLngZoom(location, 15));
_getNearbyPlaces(location);
}
}
void _showPlaceDetails(PlacesSearchResult place) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 350,
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
place.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20.0,
),
),
const SizedBox(height: 8.0),
Text(place.vicinity!),
const SizedBox(height: 8.0),
if (place.openingHours != null &&
place.openingHours!.periods != null)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Operation Hours:',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4.0),
for (var period in place.openingHours!.periods)
Text(
'${_getDayString(period.open!.day)}: ${_formatTime(period.open!.time as TimeOfDay)} - ${_formatTime(period.close!.time as TimeOfDay)}',
),
],
),
],
),
);
},
);
}
String _getDayString(int day) {
switch (day) {
case DateTime.monday:
return 'Monday';
case DateTime.tuesday:
return 'Tuesday';
case DateTime.wednesday:
return 'Wednesday';
case DateTime.thursday:
return 'Thursday';
case DateTime.friday:
return 'Friday';
case DateTime.saturday:
return 'Saturday';
case DateTime.sunday:
return 'Sunday';
default:
return '';
}
}
String _formatTime(TimeOfDay time) {
return '${time.hour}:${time.minute.toString().padLeft(2, '0')}';
}
}
从您提供的代码中,我注意到您还有其他各种目标,例如获取附近的地点和处理用户的当前位置。由于您的问题具体是关于自动完成功能不起作用和显示开放时间的,因此我专注于这些部分并成功创建了一个解决这些问题的 Flutter 应用程序。
以下是代码关键部分的细分:
我的 UI 中有一个 “搜索地点” 按钮,按下该按钮会触发名为
_handlePressButton
的方法。
@override
Widget build(BuildContext context) {
return Scaffold(
key: homeScaffoldKey,
appBar: AppBar(
title: const Text("Google Maps Search"),
),
body: Stack(
children: [
GoogleMap(
initialCameraPosition: initialCameraPosition,
markers: markersList,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
),
ElevatedButton(
onPressed: _handlePressButton, child: const Text("Search Places"))
],
),
);
}
在
_handlePressButton
方法中,就像您所做的那样,使用 PlacesAutocomplete.show()
方法来显示搜索界面。一旦用户从自动完成结果中选择了一个位置,就会调用 displayPrediction
方法。
Future<void> _handlePressButton() async {
Prediction? p = await PlacesAutocomplete.show(
context: context,
apiKey: kGoogleApiKey,
onError: onError,
mode: _mode,
language: "en",
strictbounds: false,
types: [""],
decoration: InputDecoration(
hintText: "Search Places",
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide(color: Colors.green),
)),
components: [
Component(Component.country, "us"),
Component(Component.country, "ph")
]);
displayPrediction(p!, homeScaffoldKey.currentState);
}
displayPrediction
方法使用 GoogleMapsPlaces
包中的 google_maps_webservice
类来获取详细信息,例如位置的 lat、lng 和 openingHours。这些详细信息是使用 getDetailsByPlaceId
方法获取的,并将其显示在 AlertDialog
中。
我没有像您的
_getDayString
方法那样尝试手动提取每日开放时间,而是使用了 openingHours.weedayText
(这是代表一周中每一天的开放时间的字符串列表) 属性放置 API 响应。
Future<void> displayPrediction(
Prediction p, ScaffoldState? currentState) async {
GoogleMapsPlaces places = GoogleMapsPlaces(
apiKey: kGoogleApiKey,
apiHeaders: await const GoogleApiHeaders().getHeaders());
PlacesDetailsResponse detail = await places.getDetailsByPlaceId(p.placeId!);
final lat = detail.result.geometry!.location.lat;
final lng = detail.result.geometry!.location.lng;
final openingHoursResult =
detail.result.openingHours?.weekdayText.join("\n");
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(detail.result.name),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text("Address: $lat, $lng"),
),
Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Opening Hours:"),
Text(openingHoursResult ?? "Not available"),
],
),
),
],
),
);
});
如果您想尝试的话,这是我的完整代码。
main.dart
import 'package:awesome_snackbar_content/awesome_snackbar_content.dart';
import 'package:flutter/material.dart';
import 'package:flutter_google_places/flutter_google_places.dart';
import 'package:google_api_headers/google_api_headers.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_webservice/places.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const GoogleMapsSearch(title: 'Flutter Demo Home Page'),
);
}
}
class GoogleMapsSearch extends StatefulWidget {
const GoogleMapsSearch({super.key, required String title});
@override
State<GoogleMapsSearch> createState() => _GoogleMapsSearchState();
}
const kGoogleApiKey = 'YOUR_API_KEY';
final homeScaffoldKey = GlobalKey<ScaffoldState>();
class _GoogleMapsSearchState extends State<GoogleMapsSearch> {
static const CameraPosition initialCameraPosition = CameraPosition(
target: LatLng(37.42796, -122.08574),
zoom: 14.0,
);
Set<Marker> markersList = {};
late GoogleMapController mapController;
final Mode _mode = Mode.overlay;
@override
Widget build(BuildContext context) {
return Scaffold(
key: homeScaffoldKey,
appBar: AppBar(
title: const Text("Google Maps Search"),
),
body: Stack(
children: [
GoogleMap(
initialCameraPosition: initialCameraPosition,
markers: markersList,
mapType: MapType.normal,
onMapCreated: (GoogleMapController controller) {
mapController = controller;
},
),
ElevatedButton(
onPressed: _handlePressButton, child: const Text("Search Places"))
],
),
);
}
Future<void> _handlePressButton() async {
Prediction? p = await PlacesAutocomplete.show(
context: context,
apiKey: kGoogleApiKey,
onError: onError,
mode: _mode,
language: "en",
strictbounds: false,
types: [""],
decoration: InputDecoration(
hintText: "Search Places",
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide(color: Colors.green),
)),
components: [
Component(Component.country, "us"),
Component(Component.country, "ph")
]);
displayPrediction(p!, homeScaffoldKey.currentState);
}
void onError(PlacesAutocompleteResponse response) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
elevation: 0,
behavior: SnackBarBehavior.floating,
backgroundColor: Colors.transparent,
content: AwesomeSnackbarContent(
title: 'Message',
message: response.errorMessage!,
contentType: ContentType.failure,
),
));
}
Future<void> displayPrediction(
Prediction p, ScaffoldState? currentState) async {
GoogleMapsPlaces places = GoogleMapsPlaces(
apiKey: kGoogleApiKey,
apiHeaders: await const GoogleApiHeaders().getHeaders());
PlacesDetailsResponse detail = await places.getDetailsByPlaceId(p.placeId!);
final lat = detail.result.geometry!.location.lat;
final lng = detail.result.geometry!.location.lng;
final openingHoursResult =
detail.result.openingHours?.weekdayText.join("\n");
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(detail.result.name),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text("Address: $lat, $lng"),
),
Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Opening Hours:"),
Text(openingHoursResult ?? "Not available"),
],
),
),
],
),
);
});
markersList.clear();
markersList.add(Marker(
markerId: const MarkerId("0"),
position: LatLng(lat, lng),
infoWindow: InfoWindow(
title: detail.result.name,
)));
setState(() {});
mapController
.animateCamera(CameraUpdate.newLatLngZoom(LatLng(lat, lng), 14.0));
}
}