Flutter集成高德地图

Flutter集成高德地图

  • 引言
    • 高德开放平台
      • 创建应用
      • 获取 Android 安全码
      • 获取包名
      • 配置 key 和权限
    • 项目内集成
      • 安装包
      • dynamic_page.dart 父页面
      • map_choice.dart子页面
    • FAQ
      • 64k引用限制
    • 运行结果

引言

Flutter 集成高德地图,部分资源来源于网络,技术在不断更新,网上很多方法都过时了,筛选最优解决办法做一次总结。

高德开放平台

创建应用

在高德开放平台创建一个应用 。

获取 Android 安全码

我是Ubuntu20.04开发环境,进入目录

scrutiny@scrutiny-CN15S:~/.android$ cd ~/.android/

如果有 debug.keystone 输入以下命令查看调试安全码

keytool -list -v -keystore debug.keystore

Flutter集成高德地图_第1张图片
如果没有输入以下命令生成 debug.keystore 后再查看

keytool -list -v -keystore "~/.android/debug.keystore" -alias androiddebugkey -storepass android -keypass android

获取包名

package name 在 android/app/build.gradle下查看,然后完成创建应用。
Flutter集成高德地图_第2张图片

配置 key 和权限

编辑 android/app/src/main/AndroidManifest.xml 文件

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.oblivion">
    <application
        android:name="io.flutter.app.FlutterApplication"
        //略···
        <activity
           	//略···
        </activity>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
        //第一步 name 是固定的,value 填高德地图提供的key
        <meta-data
            android:name="com.amap.api.v2.apikey"
            android:value="1391d54211e5425ad433c85187188e3e"/>
    </application>
    //第二步 在此处添加开放权限
    <!--允许程序打开网络套接字-->
    <uses-permission android:name="android.permission.INTERNET" />
    <!--允许程序设置内置sd卡的写权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <!--允许程序获取网络状态-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!--允许程序访问WiFi网络信息-->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <!--允许程序读写手机状态和身份-->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <!--允许程序访问CellID或WiFi热点来获取粗略的位置-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>

项目内集成

安装包

pubspec.yaml 新增配置,然后执行 flutter pub get 获取依赖包。

  # https://pub.flutter-io.cn/packages/amap_all_fluttify
  amap_all_fluttify: 0.15.1
  # https://github.com/Baseflow/flutter-permission-handler
  permission_handler: ^5.0.0+hotfix.9
  # https://github.com/PonnamKarthik/FlutterToast
  fluttertoast: ^4.0.0

dynamic_page.dart 父页面

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:oblivion/pages/dynamic/map_choice.dart';

class DynamicPage extends StatefulWidget {
  @override
  _DynamicPageState createState() => _DynamicPageState();
}

class _DynamicPageState extends State<DynamicPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: MapChoicePoint((point) {
        debugPrint(point.toString());
      })),
    );
  }
}

map_choice.dart子页面

import 'package:flutter/material.dart';
import 'package:amap_all_fluttify/amap_all_fluttify.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:fluttertoast/fluttertoast.dart';
/**
 * 地图选择点控件
 */
class MapChoicePoint extends StatefulWidget {
  /**
   * 选择点后回调事件
   */
  final Function onChoicePoint;
  MapChoicePoint(this.onChoicePoint);
  @override
  _MapChoicePointState createState() => _MapChoicePointState();
}

class _MapChoicePointState extends State<MapChoicePoint>
    with SingleTickerProviderStateMixin {
  //----属性----

  //地图控制器
  AmapController _amapController;
  //选择的点
  Marker _markerSelect;
  //搜索出来之后选择的点
  Marker _markerSeached;
  //所在城市
  String city;
  //搜索框文字控制器
  TextEditingController _serachController;
  //自定义marker点图标图片路径
  //Uri _imgUri = Uri.parse('images/position.png');

  //----方法----

  /**
   * 获取权限
   */
  Future<bool> _requestPermission() async {
    final permissions = await await Permission.location.status;
    if (permissions.isUndetermined) {
      return true;
    } else {
      Fluttertoast.showToast(
          msg: "需要定位权限",
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0
      );
      return false;
    }
  }

  /**
   * 根据搜索条件选出想要的点
   */
  Future _openModalBottomSheet() async {
    //收起键盘
    FocusScope.of(context).requestFocus(FocusNode());
    //根据关键字及城市进行搜索
    final poiList = await AmapSearch.searchKeyword(
      _serachController.text,
      city: city,
    );
    List<Map> points = [];
    //便利拼接信息
    for (var item in poiList) {
      points.add({
        'title': await item.title,
        'address': await item.adName + await item.address,
        'position': await item.latLng,
      });
    }
    //弹出底部对话框并等待选择
    final option = await showModalBottomSheet(
        context: context,
        builder: (BuildContext context) {
          return points.length > 0
              ? ListView.builder(
            itemCount: points.length,
            itemBuilder: (BuildContext itemContext, int i) {
              return ListTile(
                title: Text(points[i]['title']),
                subtitle: Text(points[i]['address']),
                onTap: () {
                  Navigator.pop(context, points[i]);
                },
              );
            },
          )
              : Container(
              alignment: Alignment.center,
              padding: EdgeInsets.all(40),
              child: Text('暂无数据'));
        });

    if (option != null) {
      LatLng selectlatlng = option['position'];
      //将地图中心点移动到选择的点
      _amapController.setCenterCoordinate(selectlatlng);
      //删除原来地图上搜索出来的点
      if (_markerSeached != null) {
        _markerSeached.remove();
      }
      //将搜索出来的点显示在界面上 --此处不能使用自定义图标的marker,使用会报错,至今也没有解决
      _markerSeached = await _amapController.addMarker(MarkerOption(
        latLng: selectlatlng,
      ));
    }
  }

  @override
  void initState() {
    super.initState();
    _serachController = TextEditingController();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: AlignmentDirectional.topCenter,
      children: <Widget>[
        AmapView(
          // 地图类型 (可选)
          mapType: MapType.Standard,
          // 是否显示缩放控件 (可选)
          showZoomControl: true,
          // 是否显示指南针控件 (可选)
          showCompass: true,
          // 是否显示比例尺控件 (可选)
          showScaleControl: true,
          // 是否使能缩放手势 (可选)
          zoomGesturesEnabled: true,
          // 是否使能滚动手势 (可选)
          scrollGesturesEnabled: true,
          // 是否使能旋转手势 (可选)
          rotateGestureEnabled: true,
          // 是否使能倾斜手势 (可选)
          tiltGestureEnabled: true,
          // 缩放级别 (可选)
          zoomLevel: 16,
          // 中心点坐标 (可选)
          // centerCoordinate: LatLng(39, 116),
          // 标记 (可选)
          markers: <MarkerOption>[],
          // 标识点击回调 (可选)
          onMarkerClicked: (Marker marker) async {
            if (_markerSeached == null) {
              return;
            }
            //获取点击点的位置
            var location = await marker.location;
            var lon = location.longitude;
            var lat = location.latitude;
            //获取搜索点的位置
            var slocation = await _markerSeached.location;
            var slon = slocation.longitude;
            var slat = slocation.latitude;
            //比较位置
            if (lon == slon && lat == slat) {
              //移除原来的点
              if (_markerSeached != null) {
                _markerSeached.remove();
              }
              if (_markerSelect != null) {
                _markerSelect.remove();
              }
              //画上新的点
              _markerSelect = await _amapController.addMarker(MarkerOption(
                  latLng: location,
                  //iconUri: _imgUri,
                  imageConfig: createLocalImageConfiguration(context),
                  width: 64,
                  height: 64,
                  anchorV: 0.7,
                  anchorU: 0.5));
            }
          },
          // 地图点击回调 (可选)
          onMapClicked: (LatLng coord) async {

            if (_amapController != null) {
              //移除原来的点
              if (_markerSelect != null) {
                _markerSelect.remove();
              }
              if (_markerSeached != null) {
                _markerSeached.remove();
              }
              //画上新的点
              _markerSelect = await _amapController.addMarker(MarkerOption(
                  latLng: coord,
                  //iconUri: _imgUri,
                  imageConfig: createLocalImageConfiguration(context),
                  width: 64,
                  height: 64,
                  anchorV: 0.7,
                  anchorU: 0.5));
              widget.onChoicePoint(coord);
            }
          },
          onMapMoveStart: (MapMove move) {},
          // 地图创建完成回调 (可选)
          onMapCreated: (controller) async {
            _amapController = controller;
            //申请权限
            if (await _requestPermission()) {
              //获取所在城市
              final location = await AmapLocation.fetchLocation();
              city = await location.city;
              //显示自己的定位
              await controller.showMyLocation(MyLocationOption(show: true));
              // await initSerach();
            }
          },
        ),
        Container(
          margin: EdgeInsets.all(20),
          width: MediaQuery.of(context).size.width,
          height: 46,
          decoration: BoxDecoration(color: Colors.white),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              Container(
                padding: EdgeInsets.all(8),
                width: MediaQuery.of(context).size.width - 20 - 80,
                child: TextField(
                  controller: _serachController,
                  decoration: InputDecoration(border: InputBorder.none),
                  inputFormatters: <TextInputFormatter>[
                    LengthLimitingTextInputFormatter(10) //限制长度
                  ],
                ),
              ),
              IconButton(
                  icon: Icon(Icons.search), onPressed: _openModalBottomSheet)
            ],
          ),
        )
      ],
    );
  }
}

FAQ

64k引用限制

报错: Error:The number of method references in a .dex file cannot exceed 64K.
解决方法不唯一,我选择的是multidex 配置,在我们配置应用程序的构建过程中,生成多个DEX文件。

android/app/build.gradle 文件最下方 defaultConfig 中添加 multiDexEnabled true。

defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.example.oblivion"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        # 追加
        multiDexEnabled true
    }

android/app/build.gradle 文件最下方 dependencies 中添加如下代码:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    # 追加
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'com.android.support:multidex:1.0.3'
}

运行结果

Flutter集成高德地图_第3张图片

你可能感兴趣的:(Flutter软件开发)