iOS Developer Learning Flutter. Lesson27 Map + Location

Andorid/Flutter的Dictionary也叫Map

Today Preview

使用Flutter版Google Mapslocation
(定位还有一套geolocator好像也很厉害, 不过两套都是Flutter Favorites)

1. 设定

要先去GCP生一把Google Maps API key

1.1 在上面的输入框打Map, 可以找到下列服务(不用搜寻的根本找不到入口在哪= =)

选择Maps SDK for iOS并启用

1.2 因为我们是双平台, 所以还要启用Android的

然後因为已经启用iOS了, 所以画面会变成这样

1.3 两边都启用後, 从API这边进入

1.4 就可以拿到key了

1.5 拿到钥匙之後要插在这里你在乱想什麽

iOS Runner的AppDelegate
一样要import GoogleMaps
然後GMSServices.provideAPIKey

import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GMSServices.provideAPIKey("你钥匙掉了")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Android这边请

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

如果需要不同平台用不同的key
可以申请两只
然後点进去设定

1.6 权限不足

如果切换帐号会看到这个报错
重新选取专案就好了
https://ithelp.ithome.com.tw/upload/images/20201013/20117052p6JT08cHJC.png

1.7 如果要定位, 一样要申请权限

iOS:

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location when open.</string>

Android:
如果是Flutter1.12之前才需要特别处理,看这里

2. 程序

2.1 显示地图

就是一个叫做GoogleMap的Widget
可以设定图层类型与起始点
根据Fullter惯例
一样是使用controller去控制widget
不过这边用了一个没见过的东西Completer
暂时没研究

Completer<GoogleMapController> _controller = Completer();

GoogleMap(
    mapType: MapType.terrain,
    initialCameraPosition: _kGooglePlex,
    myLocationEnabled: _enableMyLocation,
    myLocationButtonEnabled: true,
    markers: Set<Marker>.of(_markerMap.values),
    onMapCreated: (GoogleMapController controller) {
      _controller.complete(controller);
    },
  )

2.2 my location

可以看到在init里面有两个参数都是跟my location有关的
myLocationButtonEnabled就是右下角的定位按钮(预设为ture)
点了之後的逻辑套件已经处理好
iOS版的Google Maps应该没有这麽方便⚠️⚠️⚠️

myLocationEnabled就是在地图上要不要显示蓝点点(预设false)
若为true, 会要求权限
但是要求的时机也太丑了吧...就卡在这边

用了addPostFrameCallback
好啦是有好一点...

抱歉小弟学艺不精
暂时先用delayed
initState两秒後再要求(经实测XD, 1秒不要求, 3秒太久)

2.3 移动镜头

刚刚配对的controller就可以在这时使用

  static final CameraPosition _kGooglePlex = CameraPosition(
    target: LatLng(37.42796133580664, -122.085749655962),
    zoom: 14.4746,
  );

  final GoogleMapController controller = await _controller.future;
  controller.animateCamera(CameraUpdate.newCameraPosition(_kGooglePlex));

2.4 添加大头针

因为是要请圣兽守护在自身四方(这人写扣写到头壳坏掉)
所以要先取得目前位置

  void _getCurrentLocation() async {
    var location = new Location();
    try {
      _currentLocation = await location.getLocation();
      print(_currentLocation);
    } on Exception {
      _currentLocation = null;
    }

    _addMarker(MarkerType.top);
    _addMarker(MarkerType.bottom);
    _addMarker(MarkerType.left);
    _addMarker(MarkerType.right);
  }

然後建立Marker, 加进GoogleMap的markers参数里
注意两点:

  1. GoogleMap的markers参数是Set型别
  2. markerId必须唯一, 若重复只会出现一个
  enum MarkerType {top, bottom, left, right}
  Map<MarkerId, Marker> _markerMap = <MarkerId, Marker>{};
  
  void _addMarker(MarkerType type) {

    final MarkerId markerId = MarkerId("IDLF_$type");

    final offset = 0.002;
    LatLng latLng;
    String snippet;
    switch (type){
      case MarkerType.top:
        latLng = LatLng(_currentLocation.latitude + 0.001, _currentLocation.longitude);
        snippet = "北~玄武";
        break;
      case MarkerType.bottom:
        latLng = LatLng(_currentLocation.latitude - 0.003, _currentLocation.longitude);
        snippet = "南~朱雀";
        break;
      case MarkerType.left:
        latLng = LatLng(_currentLocation.latitude, _currentLocation.longitude - offset);
        snippet = "左~青龙";
        break;
      case MarkerType.right:
        latLng = LatLng(_currentLocation.latitude, _currentLocation.longitude + offset);
        snippet = "右~白虎";
        break;
    }

    final Marker marker = Marker(
      markerId: markerId,
      infoWindow: InfoWindow(title: "Hello~", snippet: snippet),
      position: latLng,
    );

    if (!mounted) return;

    setState(() {
      _markerMap[markerId] = marker;
    });
  }
  

3. 其他

pub上面的范例就这样...有点崩溃

後来发现是在这边
然後套件里面也有@@

4. Code

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:icofont_flutter/icofont_flutter.dart';
import 'package:location/location.dart';

enum MarkerType {top, bottom, left, right}

class LessonPageMap extends StatefulWidget {
  @override
  _LessonPageMapState createState() => _LessonPageMapState();
}

class _LessonPageMapState extends State<LessonPageMap> {

  static final CameraPosition _kGooglePlex = CameraPosition(
    target: LatLng(37.42796133580664, -122.085749655962),
    zoom: 14.4746,
  );

  Completer<GoogleMapController> _controller = Completer();
  bool _enableMyLocation = false;

  Map<MarkerId, Marker> _markerMap = <MarkerId, Marker>{};
  LocationData _currentLocation;

  void _getCurrentLocation() async {
    var location = new Location();
    try {
      _currentLocation = await location.getLocation();
      print(_currentLocation);
    } on Exception {
      _currentLocation = null;
    }

    _addMarker(MarkerType.top);
    _addMarker(MarkerType.bottom);
    _addMarker(MarkerType.left);
    _addMarker(MarkerType.right);
  }

  void _addMarker(MarkerType type) {

    final MarkerId markerId = MarkerId("IDLF_$type");

    final offset = 0.002;
    LatLng latLng;
    String snippet;
    switch (type){
      case MarkerType.top:
        latLng = LatLng(_currentLocation.latitude + 0.001, _currentLocation.longitude);
        snippet = "北~玄武";
        break;
      case MarkerType.bottom:
        latLng = LatLng(_currentLocation.latitude - 0.003, _currentLocation.longitude);
        snippet = "南~朱雀";
        break;
      case MarkerType.left:
        latLng = LatLng(_currentLocation.latitude, _currentLocation.longitude - offset);
        snippet = "左~青龙";
        break;
      case MarkerType.right:
        latLng = LatLng(_currentLocation.latitude, _currentLocation.longitude + offset);
        snippet = "右~白虎";
        break;
    }

    final Marker marker = Marker(
      markerId: markerId,
      infoWindow: InfoWindow(title: "Hello~", snippet: snippet),
      position: latLng,
    );

    if (!mounted) return;

    setState(() {
      _markerMap[markerId] = marker;
    });
  }

  @override
  void initState() {
    super.initState();
    print("init");
    print(DateTime.now());

      Future.delayed(Duration(seconds: 2)).then((value) {
      print("delayed");
      print(DateTime.now());
      setState(() {
        _enableMyLocation = true;
        _getCurrentLocation();
      });
    });
  }

  Future<void> _goToGoogle() async {
    final GoogleMapController controller = await _controller.future;
    controller.animateCamera(CameraUpdate.newCameraPosition(_kGooglePlex));
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(title: Text("Map")),
      body: GoogleMap(
        mapType: MapType.terrain,
        initialCameraPosition: _kGooglePlex,
        myLocationEnabled: _enableMyLocation,
        myLocationButtonEnabled: true,
        markers: Set<Marker>.of(_markerMap.values),
        onMapCreated: (GoogleMapController controller) {
          _controller.complete(controller);
        },
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.startFloat,
      floatingActionButton: FloatingActionButton(
        child: Icon(IcoFontIcons.brandGoogle),
        onPressed: _goToGoogle,
      ),
    );
  }
}

本集内容Android版请见:iOS Developer Learning Android. Lesson 22

下集预告:打包上架


<<:  第 29 天 - RxSwiftExt

>>:  菜鸡用 Phaser 拾起童年游戏 30

在全域宣告的let

//宣告全域变数 var v = 'global' let l = 'global' //建立fun...

【day20】创建对象列表(上)

我们今天先来介绍如何拿到资料,我们既然已经有看到了我们的个别聊天画面了,那当然我们需要有地方可以看...

Day 9. 新手也能懂的物件导向

新手在学写程序时一定常常看到物件、类别、介面、抽象、继承...奇怪的外星语,可能知道跟物件导向有关但...

【领域展开 30 式】 完赛习得 30 式,去年与今年的比较回顾

感谢老天又让我有机会参加铁人赛,虽然今年一样面临了新的工作环境,但还是保有挑战与坚持的心态,BTW ...

Day 31 DEV TOOL

在开发时,总缺少不了 bug 的存在, 那如何 debugger 就是所有开发者都很在意的问题了, ...