Flutter地理位置选择插件google_maps_location_picker的使用

Flutter地理位置选择插件google_maps_location_picker的使用

预览

注意:此预览展示了新功能:限制选择区域为圆形。此功能可以禁用。

支持

如果该包对您有用或节省了您的时间,请不要犹豫买杯咖啡支持原作者! 更多咖啡意味着未来能做出更多有用的项目。

获取开始

  1. https://cloud.google.com/maps-platform/获取API密钥。
  2. 在Google开发者控制台中启用Google地图SDK。
    • 打开Google开发者控制台
    • 选择您要启用Google Maps的项目。
    • 选择导航菜单并选择“Google Maps”。
    • 选择“APIs”。
    • 要为Android启用Google Maps,请在“附加API”部分选择“Maps SDK for Android”,然后选择“启用”。
    • 要为iOS启用Google Maps,请在“附加API”部分选择“Maps SDK for iOS”,然后选择“启用”。
    • 同时启用“Geocoding API”、“Geolocation API”和“Places API”。
    • 确保已启用的API位于“已启用API”部分。
    • 为所有API生成API密钥。

您还可以在此处找到详细的入门指南: https://developers.google.com/maps/gmp-get-started

Android

在应用程序清单文件android/app/src/main/AndroidManifest.xml中指定您的API密钥:

<manifest ...
  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR ANDROID KEY HERE"/>

注意:从版本3.0.0开始,geolocator插件切换到AndroidX版本的Android支持库。这意味着您需要确保您的Android项目也升级以支持AndroidX。详细说明可以在这里找到。

简要说明:

  1. 将以下内容添加到您的gradle.properties文件中:
android.useAndroidX=true
android.enableJetifier=true
  1. 确保在您的android/app/build.gradle文件中将compileSdkVersion设置为28:
android {
 compileSdkVersion 28

 ...
}
  1. 确保将所有android.依赖项替换为其AndroidX等效项(完整的列表可以在这里找到)。

iOS

在应用程序代理ios/Runner/AppDelegate.m中指定您的API密钥:

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
#import "GoogleMaps/GoogleMaps.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GMSServices provideAPIKey:@"YOUR IOS KEY HERE"];
  [GeneratedPluginRegistrant registerWithRegistry:self];
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end

或者在Swift代码中,在应用程序代理ios/Runner/AppDelegate.swift中指定您的API密钥:

import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    GMSServices.provideAPIKey("YOUR IOS KEY HERE")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

在iOS上,您需要向您的Info.plist文件(位于ios/Runner下)添加以下条目以访问设备的位置。

简单打开您的Info.plist文件并添加以下内容:

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location when open.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>This app needs access to location when in the background.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs access to location when open and in the background.</string>

此外,您可以添加“后台模式”功能到您的XCode项目(项目 > 目标 > Runner > 签名和能力 > "+"按钮 > “后台模式”),并在添加后选中“位置更新”。

通过在应用的Info.plist文件中添加一个布尔属性来选择嵌入视图预览,键名为io.flutter.embedded_views_preview,值为YES

<key>io.flutter.embedded_views_preview</key>
<true/>

如果您想在模拟器上运行您的应用,请确保在模拟器菜单中设置一个位置:

使用

首先,建议在Android上启用混合组合以避免地图重绘时出现闪烁问题:

void main() {
  if(Platform.isAndroid) {
    AndroidGoogleMapsFlutter.useAndroidViewSurface = true;
  }
  return runApp(MyApp());
}

现在,您可以使用LocationPicker通过Navigator推送至新页面,或者作为任何小部件的子级使用。当用户在地图上选择一个地点时,它会返回带有PickResult类型的onPlacePicked结果。

import 'package:flutter/material.dart';
import 'package:google_maps_place_picker_mb/google_maps_location_picker.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'dart:io' show Platform;

// ...

Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => LocationPicker(
          apiKey: Platform.isAndroid
              ? "YOUR ANDROID API KEY"
              : "YOUR IOS API KEY"
          onPlacePicked: (result) { 
            print(result.address); 
            Navigator.of(context).pop();
          },
          initialPosition: HomePage.kInitialPosition,
          useCurrentLocation: true,
          resizeToAvoidBottomInset: false, // 只在页面模式下工作,减少闪烁,移除可能会导致错误偏移
        ),
      ),
    );

PickResult

参数 类型 描述
placeId String 唯一标识一个地点的文本标识符。要检索有关该地点的信息,请将此标识符传递给Places API请求中的placeId字段。更多信息见PlaceId
geometry Geometry 包含有关结果的一般信息,通常包括地点的位置(地理编码)及其(可选)定义其大致覆盖区域的视口。
formattedAddress String 这个地方的人类可读地址。通常,这个地址等同于“邮政地址”。
types List<String> 包含描述给定结果的功能类型数组。参见支持的类型。XML响应包括多个

LocationPicker

参数 类型 描述
apiKey String (必填)您的谷歌地图API密钥
onPlacePicked Callback(PickResult) 当用户选择一个地点并决定使用它时调用。如果手动构建selectedPlaceWidgetBuilder,则不会调用此回调,因为您将覆盖默认的“选择这里”按钮。
initialPosition LatLng (必填)创建谷歌地图时初始中心位置。如果useCurrentLocation设置为true,则尝试使用GeoLocator获取设备的当前位置。
useCurrentLocation bool 是否使用设备的当前位置作为初始中心位置。当设置为true且用户允许收集他们的位置时,它将用于替代initialPosition。如果被拒绝,则使用initialPosition
desiredLocationAccuracy LocationAccuracy 获取当前位置的精度。默认为高精度。
hintText String? 搜索栏的提示文本。默认为“搜索此处”。
searchingText String? 搜索执行时显示的文本。默认为“正在搜索…”。
selectText String? 显示在允许选择焦点地址的按钮上的文本。默认仅显示图标。
outsideOfPickAreaText String? 当焦点地址超出选择区域时显示在禁用按钮上的文本。默认仅显示图标。
proxyBaseUrl String 用于谷歌地图API调用的基础URL。如果使用代理,可以设置baseUrl。在代理设置apiKey的情况下,不需要apiKey。
httpClient Client 用于谷歌地图API调用的客户端。如果使用需要身份验证或自定义配置的代理URL。
autoCompleteDebounceInMilliseconds int 自动完成输入的防抖计时器。默认为500
cameraMoveDebounceInMilliseconds int 拖动地图进行搜索时的防抖计时器。默认为750
intialMapType MapType 谷歌地图的地图类型。默认为正常。
enableMapTypeButton bool 是否显示更改地图类型的按钮
enableMyLocationButton bool 是否显示我的位置按钮
usePinPointingSearch bool 默认为true。这将允许用户拖动地图并获取指针指向的地方的信息。
usePlaceDetailSearch bool 默认为false。将其设置为true将从拖动地图搜索中获取详细结果,但将使用+1个Places详情API请求。
onAutoCompleteFailed Callback(String) 自动完成搜索失败时调用
onGeocodingSearchFailed Callback(String) 通过拖动地图搜索失败时调用
onMapCreated MapCreatedCallback 地图创建时返回谷歌地图控制器
selectedPlaceWidgetBuilder WidgetBuilder 下面的部分中指定
pinBuilder WidgetBuilder 下面的部分中指定
introPanelWidgetBuilder WidgetBuilder 下面的部分中指定
autocompleteOffset num 输入项中最后一个字符的位置,服务用于匹配预测
autocompleteRadius num 返回地点结果的距离(以米为单位)。设置时,自动完成的结果将按最佳结果在半径内排序,并按半径外最近的排序。设置为1将按距离排序所有结果。
autocompleteLanguage String 结果应以哪种语言返回的语言代码
autocompleteComponents List<Components> 你希望限制结果的地点分组。目前,你可以使用组件按最多5个国家过滤结果。
autocompleteTypes List<String> 返回的地点结果的类型。参见Place Types
strictbounds bool 仅返回严格位于由location和radius定义的区域内的地点。
region String 区域——国家代码顶级域名(ccTLD)的两个字符值。大多数ccTLD代码与ISO 3166-1代码相同,但有一些例外。此参数仅影响而不完全限制搜索结果。如果在指定区域内存在更相关的结果,它们可能会被包含。当使用此参数时,对于指定区域内的结果,结果中省略国家名称。
pickArea CircleArea 定义地址可以选择的区域圆。可以按您喜欢的颜色填充。
selectInitialPosition bool 是否在初始地图加载时显示选定的地点。默认为false。
resizeToAvoidBottomInset bool 键盘显示时调整地图大小。只能在全屏地图中禁用。默认为true。
initialSearchString String 设置自动完成搜索的初始搜索字符串
searchForInitialValue bool 是否在启动时自动搜索初始值
myLocationButtonCooldown int 'myLocationButton’的冷却时间(秒)。默认为10秒。
forceSearchOnZoomChanged bool 是否允许即使缩放发生变化时也进行地点搜索。默认为false。
automaticallyImplyAppBarLeading bool 默认情况下,顶部有一个返回按钮。设置为false将移除返回按钮。
autocompleteOnTrailingWhitespace bool 是否允许在搜索末尾的空白处运行自动完成。默认为false。
hidePlaceDetailsWhenDraggingPin bool 拖动标记时是否隐藏地点详情。默认为true。
ignoreLocationPermissionErrors bool 是否忽略位置权限错误。默认为false。
onTapBack Function(PlaceProvider)? 按返回按钮离开谷歌选择器时调用。
zoomGesturesEnabled bool 禁用捏合缩放手势,这不控制放大缩小按钮的显示。
zoomControlsEnabled bool 在屏幕右下角显示放大缩小按钮,这不控制捏合缩放手势。
onCameraMoveStarted Function(PlaceProvider)? 相机开始移动时调用。
onCameraMove CameraPositionCallback? 相机继续移动时重复调用。
onCameraIdle Function(PlaceProvider)? 相机停止移动时调用。
onMapTypeChanged Callback(MapType) 用户更改地图类型时调用。
  • MB 版本专属,目前

自定义选择地点的可视化

默认情况下,当用户通过自动完成搜索或拖动地图选择一个地点时,我们会在屏幕底部显示信息(FloatingCard)。

然而,如果您不喜欢这种UI/UX,只需使用selectedPlaceWidgetBuilder覆盖构建器。FloatingCard小部件可以重复使用,悬浮在屏幕上,或者根据您的需求构建全新的小部件。它堆叠在地图之上,因此您可能想要使用Positioned小部件。

注意,使用此自定义将不会调用[onPlacePicked]回调,因为它将覆盖浮动卡片上的默认“选择这里”按钮。

...
LocationPicker(apiKey: APIKeys.apiKey,
            ...
            selectedPlaceWidgetBuilder: (_, selectedPlace, state, isSearchBarFocused) {
              return isSearchBarFocused
                  ? Container()
                  // 使用FloatingCard或创建自己的小部件。
                  : FloatingCard(
                      bottomPosition: 0.0,    // MediaQuery.of(context)会导致重建。请参阅MediaQuery文档以了解更多信息。
                      leftPosition: 0.0,
                      rightPosition: 0.0,
                      width: 500,
                      borderRadius: BorderRadius.circular(12.0),
                      child: state == SearchingState.Searching ? 
                                      Center(child: CircularProgressIndicator()) : 
                                      RaisedButton(onPressed: () { print("do something with [selectedPlace] data"); },),
                   );
            },
            ...
          ),
...
参数 类型 描述
context BuildContext Flutter的构建上下文值
selectedPlace PickResult 用户选择地点的结果数据
state SearchingState 搜索操作的状态(空闲、搜索中)
isSearchBarFocused bool 搜索栏是否当前聚焦,键盘是否显示

自定义标记

默认情况下,标记图标提供了简单的拾取动画,当移动时。

但是,您也可以使用pinBuilder创建自己的标记小部件。

LocationPicker(apiKey: APIKeys.apiKey,
            ...
            pinBuilder: (context, state) {
                  if (state == PinState.Idle) {
                    return Icon(Icons.favorite_border);
                  } else {
                    return AnimatedIcon(.....);
                  }
                },
            ...                        
          ),
...
参数 类型 描述
context BuildContext Flutter的构建上下文值
state PinState 标记状态。(准备中;地图加载时,空闲,拖动)

添加自定义介绍模态框

默认情况下,地图会立即显示。您可能希望在首次启动应用程序时显示一个带有说明的漂亮定制覆盖模态框。在这种情况下,这个自定义小部件构建器是有意义的,因为它为您提供了最大的自由度,以避免人们认为他们打开了Google Maps。

LocationPicker(apiKey: APIKeys.apiKey,
            ...
            introModalWidgetBuilder: (context,  close) {
              return Positioned(
                top: MediaQuery.of(context).size.height * 0.35,
                right: MediaQuery.of(context).size.width * 0.15,
                left: MediaQuery.of(context).size.width * 0.15,
                child: Container(
                  width: MediaQuery.of(context).size.width * 0.7,
                  child: Material(
                    type: MaterialType.canvas,
                    color: Theme.of(context).cardColor,
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(12.0), 
                    ),
                    elevation: 4.0,
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(12.0),
                      child: Container(
                        padding: EdgeInsets.all(8.0),
                        child: Column(
                          children: [
                            SizedBox.fromSize(size: new Size(0, 10)),
                            Text("Please select your preferred address.",
                              style: TextStyle(
                                fontWeight: FontWeight.bold,
                              )
                            ),
                            SizedBox.fromSize(size: new Size(0, 10)),
                            SizedBox.fromSize(
                              size: Size(MediaQuery.of(context).size.width * 0.6, 56), // 按钮宽度和高度
                              child: ClipRRect(
                                borderRadius: BorderRadius.circular(10.0),
                                child: Material(
                                  child: InkWell(
                                    overlayColor: MaterialStateColor.resolveWith(
                                      (states) => Colors.blueAccent
                                    ),
                                    onTap: close,
                                    child: Row(
                                      mainAxisAlignment: MainAxisAlignment.center,
                                      children: [
                                        Icon(Icons.check_sharp, color: Colors.blueAccent),
                                        SizedBox.fromSize(size: new Size(10, 0)),
                                        Text("OK",
                                          style: TextStyle(
                                            color: Colors.blueAccent
                                          )
                                        )
                                      ],
                                    ) 
                                  ),
                                ),
                              ),
                            )
                          ]
                        )
                      ),
                    ),
                  ),
                )
              );
            },
            ...                        
          ),
...
参数 类型 描述
context BuildContext Flutter的构建上下文值
close Function 用于关闭模态框的函数,从而从树中删除小部件及其覆盖层。

更改默认FloatingCard的颜色

虽然您可以构建自己的预测小部件,但您仍然可以通过主题数据更改默认小部件的样式,如下所示:

// 浅色主题
final ThemeData lightTheme = ThemeData.light().copyWith(
  // FloatingCard的背景颜色
  cardColor: Colors.white,
);

// 深色主题
final ThemeData darkTheme = ThemeData.dark().copyWith(
  // FloatingCard的背景颜色
  cardColor: Colors.grey,
);

更多关于Flutter地理位置选择插件google_maps_location_picker的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter地理位置选择插件google_maps_location_picker的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter应用中使用google_maps_location_picker插件的示例代码。这个插件允许用户通过Google Maps界面选择一个地理位置。

首先,确保你已经在pubspec.yaml文件中添加了google_maps_location_picker依赖:

dependencies:
  flutter:
    sdk: flutter
  google_maps_location_picker: ^x.y.z  # 请替换为最新版本号

然后,运行flutter pub get来安装依赖。

接下来,在你的Flutter应用中,你需要进行以下步骤:

  1. 配置Android和iOS的Google Maps API密钥

    • 对于Android,在android/app/src/main/AndroidManifest.xml中添加你的Google Maps API密钥。
    • 对于iOS,在ios/Runner/Info.plist中添加相应的配置,并确保在Apple Developer账户中配置了相应的API密钥。
  2. 使用GoogleMapsLocationPicker小部件

下面是一个完整的示例代码,展示了如何在Flutter应用中使用google_maps_location_picker插件:

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_location_picker/google_maps_location_picker.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  LatLng _selectedLocation!;
  bool _loading = false;

  final LatLng _initialCameraPosition = LatLng(37.7749, -122.4194);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Google Maps Location Picker Example'),
      ),
      body: Center(
        child: _loading
            ? CircularProgressIndicator()
            : Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'Selected Location:',
                    style: TextStyle(fontSize: 18),
                  ),
                  SizedBox(height: 10),
                  _selectedLocation != null
                      ? Text(
                          'Latitude: ${_selectedLocation.latitude}, Longitude: ${_selectedLocation.longitude}',
                          style: TextStyle(fontSize: 16),
                        )
                      : Text('No location selected'),
                  SizedBox(height: 20),
                  ElevatedButton(
                    onPressed: _pickLocation,
                    child: Text('Pick Location'),
                  ),
                ],
              ),
      ),
    );
  }

  Future<void> _pickLocation() async {
    setState(() {
      _loading = true;
    });

    final result = await LocationPicker(
      apiKey: 'YOUR_GOOGLE_MAPS_API_KEY', // 替换为你的Google Maps API密钥
      useCurrentLocation: true,
      initialSelection: _initialCameraPosition,
      selectionColor: Colors.blue.withOpacity(0.5),
    ).show(context);

    setState(() {
      _loading = false;
    });

    if (result != null) {
      setState(() {
        _selectedLocation = result;
      });
    }
  }
}

在这个示例中:

  • LocationPicker小部件被用来打开Google Maps界面让用户选择一个位置。
  • apiKey参数需要替换为你自己的Google Maps API密钥。
  • useCurrentLocation设置为true表示在地图上显示当前位置。
  • initialSelection设置了地图的初始视角。
  • selectionColor设置了选中位置时的标记颜色。

请确保你已经正确配置了Google Maps API密钥,并且在你的项目中启用了相应的API服务。如果你遇到任何问题,请检查你的API密钥和配置是否正确。

回到顶部