iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 28
2
Mobile Development

iOS Developer Learning Flutter系列 第 28

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

下集預告:打包上架


上一篇
iOS Developer Learning Flutter. Lesson26 Biometric
下一篇
iOS Developer Learning Flutter. Lesson28 打包上架
系列文
iOS Developer Learning Flutter30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言