Mobility TechnologiesでFlutterエンジニアとして働いているTomiと申します。
タクシーアプリ「GO」ではGoogle MapにMarker(Taxi)を動かす場合が多くあります。Flutterではどのように動かせるのかを調査しましたので、この内容を共有します。
目標
Google Map上のMarkerを移動するとき、ネイティブ(Android, iOS)側では表示しているMarkerを参照して、そのpositionを少しつづ変更することで対応できます。
しかしながら、Flutterでは表示しているMarkerを参照することはできません。
本記事では、FlutterでMarkerの移動をどのように実現するのかを解説します。
事前準備
Google Mapを表示
ライブラリインストール
Google Mapを表示するために、google_maps_flutterライブラリ「22年11月現在v2.2.1
」を使います。
$ flutter pub add google_maps_flutter
上記のコマンドを実行してライブラリをインストールします。
そして各プラットフォーム側で設定が必要です。
Android設定方法
Flutterプロジェクトを立ち上げるとAndroidのminSdkVersion
は16
に設定されていますが、Google Mapでは20
以上求めており、20
以上で設定する必要がります。
// android/app/build.gradle android { defaultConfig { // ...省略 minSdkVersion 20 // ...省略 } }
そしてAndroidManifest.xmlにGoogle Map Api Keyを設定したら完了です。
<!-- android/app/src/main/AndroidManifest.xml --> <application> ...省略 <meta-data android:name="com.google.android.geo.API_KEY" android:value="####"/> </application>
iOS設定方法
import UIKit import Flutter import GoogleMaps // NEW[1] @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GMSServices.provideAPIKey("YOUR KEY HERE") // NEW[2] GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
上記のNEW[1]
にimport文を追加してNEW[2]
にGoogle Map Api Keyを入れてください。
FlutterにGoogleMap Widgetを表示する
// lib/main.dart import 'package:google_maps_flutter/google_maps_flutter.dart'; ...省略 GoogleMap( mapType: MapType.normal, initialCameraPosition: const CameraPosition( target: LatLng(35.68165450744266, 139.76708188461404), // TokyoStation zoom: 14, ), );
initialCameraPosition
: 最初にカメラで表示される位置mapType
:マップの表現タイプ
結果
Flutterアプリを起動するとGoogle Mapが表示していることを確認出来ます。
Markerを表示
GoogleMap( mapType: MapType.normal, initialCameraPosition: _tokyoStation, markers: <Marker>{ const Marker( markerId: MarkerId('driverMarker'), position: LatLng(35.68128517123827, 139.76714151602386), ) }, ),
GoogleMap
Widgetのmarkers
パラメーターにMarkerを入れると表示されます。
markerId
:markers
の中でユニークなidを設定する必要があります。position
: Markerを配置する位置情報です。
Markerを動かす
FlutterでMarkerを動かせるためには少しづつ位置を修正したMarkerを入れたGoogleMapをリビルドします。
今回はTweenアニメーションを使って実装しました。
Marker? _marker; Future<void> _run() async { final animationController = AnimationController( duration: const Duration(seconds: 5), vsync: this, ); Tween<double> tween = Tween(begin: 0, end: 1); _animation = tween.animate(animationController) ..addListener(() async { final v = _animation!.value; double lng = v * _endLng + (1 - v) * _startLng; double lat = v * _endLat + (1 - v) * _startLat; setState(() { _marker = Marker( markerId: const MarkerId('driverMarker'), position: LatLng(lat, lng), ); }); }); animationController.forward(); }
上記の_run
関数を実行するとアニメーションが動いて_marker
変数を少しつづ目的地まで位置情報を更新します。
@override Widget build(BuildContext context) { return Scaffold( body: GoogleMap( mapType: MapType.normal, initialCameraPosition: _tokyoStation, markers: <Marker>{if (_marker != null) _marker!}, ), floatingActionButton: FloatingActionButton( onPressed: _run, child: const Text('Start'), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ); }
この_marker
をGoogleMapのパラメーターで入れておくとMarkerが動くことを確認できます。
しかしながら、上記のコードはMarkerが動く際に、全てのGoogleMapのビルド関数でリビルドが走ってしまうため、性能的に良くないです。この問題を解決するためにリビルドするWidgetを絞る必要がありましてStreamBuilder
を使って制限しました。
final _markerStreamController = StreamController<Marker>(); StreamSink<Marker> get _markerSink => _markerStreamController.sink; Stream<Marker> get _markerStream => _markerStreamController.stream; @override Widget build(BuildContext context) { return Scaffold( body: StreamBuilder<Marker>( stream: _markerStream, builder: (context, snapshot) { final maker = snapshot.data; return GoogleMap( mapType: MapType.normal, initialCameraPosition: _tokyoStation, markers: <Marker>{if (maker != null) maker}, ); }, ), floatingActionButton: FloatingActionButton( onPressed: _run, child: const Text('Start'), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, ); } Future<void> _run() async { final animationController = AnimationController( duration: const Duration(seconds: 5), vsync: this, ); Tween<double> tween = Tween(begin: 0, end: 1); _animation = tween.animate(animationController) ..addListener(() async { final v = _animation!.value; double lng = v * _endLng + (1 - v) * _startLng; double lat = v * _endLat + (1 - v) * _startLat; _markerSink.add( Marker( markerId: const MarkerId('driverMarker'), position: LatLng(lat, lng), ), ); }); animationController.forward(); }
StreamController
とStreamBuilder
を使って該当する部分のみリビルドさせるように修正しました。
これでStart
ボタンを押したらMarkerが動けるようになりました。
ここまで読んでいただき、ありがとうございました!