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が動けるようになりました。
ここまで読んでいただき、ありがとうございました!