Flutter

【Flutter】Google Mapsの現在地ボタンを自作してみた

Flutter
この記事は約12分で読めます。

はじめに

株式会社インプルの渡部です。

FlutterでGoogle Mapsを表示するとデフォルトの現在地ボタンでは実際のGoogle Mapsアプリとアイコンが違ったり位置を変えられなかったりと少し不便なので今回自作してみました。

実装

実装手順について簡単にまとめていこうと思います。

  1. マップの表示
  2. ボタンの作成
  3. マップとボタンを重ね合わせる
  4. 現在地を取得できるようにする
  5. マップを動かして現在地を画面中央に表示する
  6. 完成した全体のコード

大まかにこの流れで実装を行います。

それでは、早速コードを見ていきましょう。

マップの表示

まずはマップを作成します。

Flutterでマップを表示させるにはGoogle Maps Platformとgoogle_maps_flutterパッケージ等の設定が必要です。

公式サイトに設定方法があるので設定が済んでいない方はこちらを参考にまず設定を行ってください。

設定が完了したら実装を進めていきます。

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

class MapApp extends StatefulWidget {
  const MapApp({super.key});

  @override
  State<MapApp> createState() => _MapAppState();
}

class _MapAppState extends State<MapApp> {
  late GoogleMapController mapController;

  final LatLng _initialPosition = const LatLng(35.681236, 139.767125);

  void _onMapCreated(GoogleMapController controller) {
    mapController = controller;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(body: _googleMap());
  }

  Widget _googleMap() {
    return GoogleMap(
      onMapCreated: _onMapCreated,
      initialCameraPosition: CameraPosition(
        target: _initialPosition,
        zoom: 14.0,
      ),
      myLocationEnabled: true,
    );
  }
}

ボタンの作成

実際のGoogle Mapsアプリに合わせた丸いボタンとnear_meアイコンを使って実装していきます。

Widget _goToCurrentPositionButon() {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        minimumSize: const Size(55, 55),
        backgroundColor: Colors.white,
        foregroundColor: Colors.black,
        shape: const CircleBorder(),
      ),
      onPressed: () {},
      child: const Icon(Icons.near_me_outlined),
    );
  }

マップとボタンを重ね合わせる

Google Mapsとボタンが完成したのでScaffoldのbodyを書き換えてGoogleMap上にボタンを表示するようにしてみましょう。

Widgetを重ね合わせるStack WidgetとStack Widgte内のWidgetを自在に配置できるPositioned Widgetを使用して2つのWidgetを重ね合わせます。

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          _googleMap(),
          Positioned(
            right: 10,
            bottom: 70,
            child: _goToCurrentPositionButon(),
          )
        ],
      ),
    );
  }

現在地を取得できるようにする

アプリの見た目が完成したので次はスマホの現在地を取得できるようにします。

位置情報を取得するためにgeolocatorパッケージをインストールします。

flutter pub add geolocator

インストールしたら自身のエミュレータがAndroidかiosかによって設定が必要なのでこちらを参考にファイルの設定を行ってください。設定が完了したら現在地を取得するための実装を進めていきます。

さっそく位置情報を取得したいのですが勝手に位置情報を取得するわけにも行かないのでアプリからユーザーに位置情報を取得しても良いか許可を取る必要があります。よく見る位置情報取得許可画面をまずは実装していきます。この部分はアプリ起動時に一度だけ表示すれば良いのでWidgetの初期化時に呼ばれるinitState内に実装します。

  void _onMapCreated(GoogleMapController controller) {
    mapController = controller;
  }
  @override
  void initState() {
    super.initState();
    _initializeMap();
  }

  void _initializeMap() async {
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      await Geolocator.requestPermission();
    }
  }

ここまで出来たら一度ctrl+Cでエミュレータを落として再度flutter runでアプリを起動してみましょう。アプリが起動すると位置情報取得許可の画面が出てくるはずです。

位置情報取得を許可したら次は現在地を取得してみましょう。

実装したボタンのonPressed内に現在地を取得する実装をします。

onPressed: () async {
        Position currentPosition = await Geolocator.getCurrentPosition(
            desiredAccuracy: LocationAccuracy.high);
        print(currentPosition.latitude);
        print(currentPosition.longitude);
      },

これでボタンをタップしたらターミナルに経度と緯度が表示されるはずです。経度と緯度の表示が確認出来たら確認用のprintは削除して構いません。

Unhandled Exception: MissingPluginException(No implementation found for method getCurrentPosition on channel flutter.baseflow.com/geolocator_apple)

このようなエラーが出たら位置情報取得が許可されていない可能性があります。位置情報を取得することを許可することで解決するかと思いますので許可をしましょう。このエラーメッセージはiosのものですがandroidでも似たようなエラーメッセージが出るかと思います。

マップを動かして現在地を画面中央に表示する

GoogleMapControllerのanimateCameraを使用することで任意の経緯・緯度に移動することが出来ます。先程のonPressed内で現在地の経緯・緯度は取得できているので取得した値を渡すことで現在地が画面中央に表示されます。

mapController.animateCamera(
  CameraUpdate.newCameraPosition(
   CameraPosition(
    target:
     LatLng(currentPosition.latitude, currentPosition.longitude),
     zoom: 14
   ),
  ),
);

実行すると既存のボタンと同じ動きをしていることが確認出来ると思います。

既存の現在地ボタンを削除したいことはmyLocationButtonEnabled: falseを追加すれば削除することが出来ます。

  Widget _googleMap() {
    return GoogleMap(
      onMapCreated: _onMapCreated,
      initialCameraPosition: CameraPosition(
        target: _initialPosition,
        zoom: 14.0,
      ),
      myLocationEnabled: true,
      myLocationButtonEnabled: false,
    );
  }

完成した全体のコード

最後に全体のコードを添付しておきますのでエラーで実行出来ないときはコピペして動くかを確認してみてください。

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class MapApp extends StatefulWidget {
  const MapApp({super.key});

  @override
  State<MapApp> createState() => _MapAppState();
}

class _MapAppState extends State<MapApp> {
  late GoogleMapController mapController;

  final LatLng _initialPosition = const LatLng(35.681236, 139.767125);

  void _onMapCreated(GoogleMapController controller) {
    mapController = controller;
  }

  @override
  void initState() {
    super.initState();
    _initializeMap();
  }

  void _initializeMap() async {
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      await Geolocator.requestPermission();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          _googleMap(),
          Positioned(
            right: 10,
            bottom: 70,
            child: _goToCurrentPositionButon(),
          )
        ],
      ),
    );
  }

  Widget _googleMap() {
    return GoogleMap(
      onMapCreated: _onMapCreated,
      initialCameraPosition: CameraPosition(
        target: _initialPosition,
        zoom: 14.0,
      ),
      myLocationEnabled: true,
      myLocationButtonEnabled: false,
    );
  }

  Widget _goToCurrentPositionButon() {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        minimumSize: const Size(55, 55),
        backgroundColor: Colors.white,
        foregroundColor: Colors.black,
        shape: const CircleBorder(),
      ),
      onPressed: () async {
        Position currentPosition = await Geolocator.getCurrentPosition(
            desiredAccuracy: LocationAccuracy.high);
        mapController.animateCamera(
          CameraUpdate.newCameraPosition(
            CameraPosition(
                target:
                    LatLng(currentPosition.latitude, currentPosition.longitude),
                zoom: 14),
          ),
        );
      },
      child: const Icon(Icons.near_me_outlined),
    );
  }
}

まとめ

いかがでしたでしょうか?今回はGoogle Mapsの現在地ボタンを自作してみました。既存のボタンでは不便に思うこともあると思うのでよかったら参考にしてもらえればと思います。