Flutter集成高德地圖

引言

Flutter 集成高德地圖,部分資源來源於網絡,技術在不斷更新,網上很多方法都過時了,篩選最優解決辦法做一次總結。

高德開放平臺

創建應用

高德開放平臺創建一個應用 。

獲取 Android 安全碼

我是Ubuntu20.04開發環境,進入目錄

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

如果有 debug.keystone 輸入以下命令查看調試安全碼

keytool -list -v -keystore debug.keystore

在這裏插入圖片描述
如果沒有輸入以下命令生成 debug.keystore 後再查看

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

獲取包名

package name 在 android/app/build.gradle下查看,然後完成創建應用。
在這裏插入圖片描述

配置 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'
}

運行結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章