본문 바로가기

Flutter/04 Widgets

[Flutter] Widgets - Google map

이번 카테고리는 google map 을 사용하는 방법에 대해서 알아보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1

소스코드 위치 - 

 

 

화면은 아래와 같습니다.

메인화면에서 "Places Nearby" 버튼을 클릭하면 아래와 같은 화면이 나옵니다.

MapType 을 클릭하면 지도의 타입(normal, satellite, terrain, hybrid)을 순차적으로 변경됩니다.

"어디에서 볼까?" 를 클릭하면 베이커리, 레스토랑, 카페 를 선택할수 있습니다.

 

 

 

관련 패키지들은 

 

google_maps_flutter: ^2.1.8
flutter_form_builder: ^7.3.1
form_builder_validators: ^8.1.1
http: ^0.13.4

 

 

API 관련 키 취득 및 등록은 구글의 절차대로 신청해서 받으시면 됩니다.

AndroidManifest.xml 파일 설정 방법은 아래의 유투브를 참고하시면 됩니다.

 

 

constants.dart

 

const String baseUrl =
    'https://maps.googleapis.com/maps/api/place/nearbysearch/json';
const places = [
  {'id': '1', 'placeName': '베이커리'},
  {'id': '2', 'placeName': '레스토랑'},
  {'id': '3', 'placeName': '카페'}
];

 

main.dart

 

import 'dart:async';
import 'dart:convert';

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

import 'constants/constants.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Google Maps Demo',
      home: MapSample(),
    );
  }
}

class MapSample extends StatefulWidget {
  const MapSample({Key? key}) : super(key: key);

  @override
  State<MapSample> createState() => MapSampleState();
}

class MapSampleState extends State<MapSample> {
  final Completer<GoogleMapController> _controller = Completer();

  // 5가지, non, normal, satellite, terrain, hybrid 등등.
  MapType _googleMapType = MapType.normal;

  // 클릭시마다 지도 타입이 변경, 4로 나눠서 나머지 값으로 지도 타입 설정
  int _mapType = 0;
  final Set<Marker> _markers = {};

  //
  final GlobalKey<FormBuilderState> _fbKey = GlobalKey<FormBuilderState>();

  @override
  void initState() {
    super.initState();
    _markers.add(
      const Marker(
        markerId: MarkerId('myInitPosition'),
        position: LatLng(37.53609444, 126.9675222),
        infoWindow: InfoWindow(title: 'My Position', snippet: 'Where am I ?'),
      ),
    );
  }

  final CameraPosition _initCameraPosition = const CameraPosition(
    target: LatLng(37.53609444, 126.9675222),
    zoom: 14,
  );

  void _onMapCreated(GoogleMapController controller) {
    // 이제 이 콘트롤을 프로그램애서 사용준비 완료.
    _controller.complete(controller);
  }

// 지도타입 설정
  void _changeMapType() {
    setState(() {
      _mapType++;
      _mapType = _mapType % 4;

      switch (_mapType) {
        case 0:
          _googleMapType = MapType.normal;
          break;
        case 1:
          _googleMapType = MapType.satellite;
          break;
        case 2:
          _googleMapType = MapType.terrain;
          break;
        case 3:
          _googleMapType = MapType.hybrid;
          break;
        default:
          _googleMapType = MapType.normal;
          break;
      }
    });
  }

  void _submit() {
    if (!_fbKey.currentState!.validate()) {
      return;
    }

    _fbKey.currentState!.save();
    final inputValues = _fbKey.currentState!.value;
    final id = inputValues['placeId'];

    final foundPlace = places.firstWhere(
      (place) => place['id'] == id,
      orElse: () => {}, //'id': 'null', 'placeName': 'null'
    );

    debugPrint(foundPlace.toString());

    if(foundPlace['placeName'] == null){
      Navigator.of(context).pop();
      return;
    } else {
      Navigator.of(context).pop();
    }
  }

  void _gotoGangnam() {
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(15.0),
          topRight: Radius.circular(15.0),
        ),
      ),
      builder: (context) {
        return SingleChildScrollView(
          child: Container(
            padding: EdgeInsets.only(
              bottom: MediaQuery.of(context).viewInsets.bottom,
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.only(
                    top: 40,
                    right: 20,
                    left: 20,
                    bottom: 20,
                  ),
                  child: FormBuilder(
                    key: _fbKey,
                    child: Column(
                      children: <Widget>[
                        FormBuilderDropdown(
                          name: 'placeId', // map 의 키값
                          // attribute: 'placeId',
                          hint: const Text('어떤 장소를 원하세요?'),
                          decoration: const InputDecoration(
                            filled: true,
                            labelText: '장소',
                            border: OutlineInputBorder(),
                          ),
                          validator: FormBuilderValidators.required(
                            errorText: '장소 선택은 필수입니다!',
                          ),
                          items: places.map<DropdownMenuItem<String>>(
                            (place) {
                              return DropdownMenuItem<String>(
                                value: place['id'],
                                child: Text(place['placeName']!),
                              );
                            },
                          ).toList(),
                        ),
                      ],
                    ),
                  ),
                ),
                MaterialButton(
                  onPressed: _submit,
                  color: Colors.indigo,
                  textColor: Colors.white,
                  child: const Text('Submit'),
                ),
                const SizedBox(height: 20),
              ],
            ),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          GoogleMap(
            mapType: _googleMapType,
            initialCameraPosition: _initCameraPosition,
            onMapCreated: _onMapCreated,
            // myLocationEnabled: true,
            markers: _markers,
          ),
          Container(
            margin: const EdgeInsets.only(top: 60, right: 10),
            alignment: Alignment.topRight,
            child: Column(
              children: <Widget>[
                FloatingActionButton.extended(
                  heroTag: 'btn1',
                  label: Text('$_googleMapType'),
                  icon: const Icon(Icons.map),
                  elevation: 8,
                  backgroundColor: Colors.red[400],
                  onPressed: _changeMapType,
                ),
                const SizedBox(height: 10),
                FloatingActionButton.extended(
                  heroTag: 'btn2',
                  label: const Text('어디에서 볼까?'),
                  icon: const Icon(Icons.zoom_out_map),
                  elevation: 8,
                  backgroundColor: Colors.blue[400],
                  onPressed: _gotoGangnam,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

 

 

 

 

 

[참고자료] 헤비프랜

- https://www.youtube.com/watch?v=7Hlos9a70Hg&list=PLGJ958IePUyBeZRFKmL5NrZrYi0mPnNcq&index=3