Flutter : การใช้ Google Maps ใน Flutter
สวัสดีผู้อ่านครับ มาลองลองเล่น library ต่างๆใน Flutter กันต่อ บล็อกนี้เป็นคิวของ Google Maps ครับ ซึ่งใน Flutter ก็มี Google Maps Library ให้ใช้งานกัน ความสามารถก็ค่อนข้างครอบคลุม แต่ว่าในขณะที่เขียนบล็อกนี้อยู่นี้ google maps flutter เป็นเวอชัน 0.5.7 และยังอยู่ใน Developer Preview อยู่ด้วย นั่นหมายความว่าอาจจะยังมีบัคและอาจการเปลี่ยนแปลงอีกพอสมควรในเวอชันถัดๆไป โอเค มาลองเล่นกันเล้ยย
เปิดใช้งาน Maps SDK
ก่อนอื่นเปิดใช้งาน Google Maps API กันก่อน โดยไปที่ https://console.cloud.google.com/
ซึ่งให้เปิดใช้งาน 2 อัน คือ Maps SDK for Android และ Maps SDK for iOS
กดปุ่ม enable
จากนั้นไปที่ Credential จะมี API key อยู่ ให้ copy เอาไว้
เพิ่ม API key ในโปรเจค
พอได้ api key แล้วก็ไปที่ android/app/src/main/AndroidManifest.xml เอา API key ไปใส่ไว้ในนี้ โดยเพิ่มเป็น <meta-data> และเพิ่ม permission สำหรับใช้งาน location
<manifest ...
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application ...
<meta-data android:name="com.google.android.geo.API_KEY"
android:value="YOUR KEY HERE"/>
สำหรับ iOS จะเพิ่ม API key ที่ ios/Runner/AppDelegate.swift
import UIKit
import Flutter
import GoogleMaps
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("YOUR KEY HERE")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
เพิ่ม permission ที่ ios/Runner/Info.plist
<plist version="1.0">
<dict>
...
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs your location to test the location feature of the Google Maps plugin.</string>
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict>
</plist>
เพิ่ม Dependencies ใน Flutter
จากนั้นเพิ่ม dependencies ใน Flutter โดยตัวอย่างนี้ผมจะใช้งาน 3 ตัว คือ google maps อันนี้แน่นอนอยู่แล้ว location สำหรับรับค่าตำแหน่งของเรา ส่วน url launcher เอาไว้เปิดแอป google maps จากแอปของเรา
โดยเพิ่มที่ไฟล์ pubspec.yaml
dependencies:
flutter:
sdk: flutter
..
google_maps_flutter: 0.5.7
location: 2.3.5
url_launcher: 5.0.2
การใช้งาน Google Maps
มาลองใช้ google maps กันเลย ก่อนอื่น import package google maps
import 'package:google_maps_flutter/google_maps_flutter.dart';
จากนั้นเพิ่ม Widget ที่ชื่อว่า GoogleMap โดยกำหนด mapType และ camera ให้มัน อีกตัวที่สำคัญคือ GoogleMapController ซึ่งในที่นี้เขาจะใช้ Completer class มันก็คือ class ที่เอาไว้สร้าง Future อีกที
class MyMapPageState extends State<MapSample> {
Completer<GoogleMapController> _controller = Completer();
@override
Widget build(BuildContext context) {
return Scaffold(
body: GoogleMap(
mapType: MapType.normal,
initialCameraPosition: CameraPosition(
target: LatLng(13.7650836, 100.5379664),
zoom: 16,
),
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
)
);
}
ลองรัน จะได้ map แล้วล่ะ
หากใครที่รันใน emulator แล้ว error เป็นไปได้ว่า emulator นั้นไม่มี google play หรือไม่ก็ google play service เวอชันเก่าเกินไป อ่านวิธีอัพเดทได้ที่ลิงค์ข้างล่าง
เกร็ดเล็กๆเพิ่มอีกนิด คือ mapType มีให้เลือก 4 แบบ
/// Do not display map tiles.
MapType.none
/// Normal tiles (traffic and labels, subtle terrain information).
MapType.normal
/// Satellite imaging tiles (aerial photos)
MapType.satellite
/// Terrain tiles (indicates type and height of terrain)
MapType.terrain
/// Hybrid tiles (satellite images with some labels/overlays)
MapType.hybrid
ตัวอย่าง mapType แต่ละแบบ
ค่าการ zoom ของ camera จะมีตั้งแต่ 0-20 โดยยิ่งเลขมากก็ยิ่งซูมมาก
เราสามารถกำหนดให้มีปุ่ม my location ได้ด้วย แค่กำหนด myLocationEnabled
GoogleMap(
myLocationEnabled: true,
..
การใช้งาน Location
ถ้าเราอยากรู้ตำแหน่งปัจจุบันของเราล่ะ มาลองใช้งาน library location กัน
ให้ import location เข้ามาก่อน
import 'package:location/location.dart';
ประกาศตัวแปร LocationData
class MyMapPageState extends State<MapSample> {
LocationData currentLocation;
..
ดึงตำแหน่ง location โดยใช้ location.getLocation() ซึ่งต้องระวังกรณีไม่มี permission ด้วย
Future<LocationData> getCurrentLocation() async {
Location location = Location();
try {
return await location.getLocation();
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
// Permission denied
}
return null;
}
}
เขียน method สำหรับนำ currentLocation มาใช้กับ Google Maps
วิธีการคือ เอา Lat Lng มาใส่ใน CameraPosition แล้วกำหนด newCameraPosition ผ่าน GoogleMapController
Future _goToMe() async {
final GoogleMapController controller = await _controller.future;
currentLocation = await getCurrentLocation();
controller.animateCamera(CameraUpdate.newCameraPosition(
CameraPosition(
target: LatLng(
currentLocation.latitude,
currentLocation.longitude),
zoom: 16,
)));
}
เพิ่มปุ่มให้ กดแล้วเรียกใช้ method ที่เราทำไว้เมื่อกี้
@override
Widget build(BuildContext context) {
return Scaffold(
..
floatingActionButton: FloatingActionButton.extended(
onPressed: _goToMe,
label: Text('My location'),
icon: Icon(Icons.near_me),
),
);
}
ลองรันก็จะมีปุ่มเพิ่มขึ้นมาแล้ว โดยเริ่มต้นผมกำหนด Lat Lng ที่อนุเสาวรีย์ชัยฯ
จากนั้นกดที่ปุ่ม My Location แอปจะถามหา permission
หากอนุญาต แอปก็จะเปลี่ยนมุมกล้องไปยังตำแหน่งที่เรากำหนดไว้แล้ว
ซึ่งตัว CameraUpdate ก็มีลูกเล่นพอสมควร เช่นหากเราอยากแค่เปลี่ยนตำแหน่ง แต่ระดับซูมเท่าเดิมเราก็ใช้ CameraUpdate.newLatLng ก็ได้ ตัวอย่างนี้ผมจะให้กดแล้วจะแสดงแผนที่สนามบินสุวรรณภูมิ
Future _goToSuwannabhumiAirport() async {
final GoogleMapController controller = await _controller.future;
currentLocation = await getCurrentLocation();
controller
.animateCamera(CameraUpdate.newLatLng(LatLng(13.6900043, 100.7479237)));
}
เพิ่มไอคอนปุ่มไปที่ AppBar สำหรับกดแล้วแสดงแผนที่สนามบิน
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("My Map"), actions: <Widget>[
IconButton(
icon: Icon(Icons.airplanemode_active),
onPressed: _goToSuwannabhumiAirport)
]),
..
}
ลองเล่นอีกสักอัน เพิ่มอีกปุ่ม โดยกดแล้วจะขยายออกให้เห็น กรุงเทพทั้งหมด
Future _zoomOutToBangkok() async {
final GoogleMapController controller = await _controller.future;
currentLocation = await getCurrentLocation();
controller
.animateCamera(CameraUpdate.newLatLngZoom(LatLng(13.6846021,100.5883304),10));
}
เพิ่มปุ่มอีกอัน
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("My Map"), actions: <Widget>[
IconButton(
icon: Icon(Icons.home),
onPressed: _zoomOutToBangkok),
..
}
การใส่ Marker
ปกติในแผนที่ การใช้งานจริงมักจะมีจุดแสดงตำแหน่งด้วย จะเป็นรูปหมุด หรือไอคอนต่างๆ ซี่งเราเรียกว่า Marker การใช้งานง่ายมาก โดย GoogleMap Widget มี field ให้ใส่ markers อยู่แล้วโดยกำหนดเป็น Set ซึ่ง marker แต่ละตัวเราต้องกำหนด id และ Lat Lng ให้มัน แถมยังใส่ชื่อ รายละเอียดได้อีกด้วย
GoogleMap(
..
markers: {
Marker(
markerId: MarkerId("1"),
position: LatLng(13.6900043, 100.7479237),
infoWindow: InfoWindow(title: "สนามบินสุวรรณภูมิ", snippet: "สนามบินนานาชาติของประเทศไทย")),
},
)
การส่งตำแหน่งไปเปิดในแอป Google Maps
ในหลายๆครั้งแอปของเราก็มีข้อจำกัด เราสามารถส่งตำแหน่งไปให้ผู้ใช้ ใช้งานใน Google Maps ได้เลย เช่นการนำทาง การคำนวณระยะทาง
เริ่มจาก import url_launcher
import 'package:url_launcher/url_launcher.dart';
การใช้งานก็แค่เรียกผ่าน url ของ Google Map ได้เลย โดยส่ง parameter เป็น query ได้เลย ถ้าใครได้อ่านบล็อกของผมเรื่อง Deep link น่าจะรู้ทันทีได้เลยว่า มันคือการใช้ Universal App Link นั่นเอง
_openOnGoogleMapApp(double latitude, double longitude) async {
String googleUrl =
'https://www.google.com/maps/search/?api=1&query=$latitude,$longitude';
if (await canLaunch(googleUrl)) {
await launch(googleUrl);
} else {
// Could not open the map.
}
}
ตัวอย่างนี้ ผมจะเพิ่ม onTap ให้กับ Marker ทำให้เมื่อกดที่ marker ก็จะเปิดแอป Google Maps ขึ้นมา
GoogleMap(
..
markers: {
Marker(
markerId: MarkerId("1"),
position: LatLng(13.6900043, 100.7479237),
infoWindow: InfoWindow(
title: "สนามบินสุวรรณภูมิ",
snippet: "สนามบินนานาชาติของประเทศไทย"),
onTap: () => _openOnGoogleMapApp(13.6900043, 100.7479237)),
},
)
การเพิ่มไอคอนให้ Marker
ไอคอนอันเดิมอาจจะยังไม่เป็นที่ถูกใจเราสามารถเพิ่มไอคอนของเราเป็น marker ได้ โดยกำหนดไอคอนไว้ใน asset folder ก่อน
เพิ่ม path ของ asset ใน pubspec.yaml
flutter:
assets:
- assets/images/ic_airport.png
ประกาศตัวแปร BitmapDescriptor
class MyMapPageState extends State<MapSample> {
BitmapDescriptor _markerIcon;
..
เขียน method สำหรับดึง ไอคอนใน asset มาใส่ BitmapDescriptor
Future _createMarkerImageFromAsset(BuildContext context) async {
if (_markerIcon == null) {
ImageConfiguration configuration = ImageConfiguration();
BitmapDescriptor bmpd = await BitmapDescriptor.fromAssetImage(
configuration, 'assets/images/ic_airport.png');
setState(() {
_markerIcon = bmpd;
});
}
}
เรียกใช้ method นี้ใน build
@override
Widget build(BuildContext context) {
_createMarkerImageFromAsset(context);
..
กำหนด BitmapDescriptor ให้กับ icon ของ Marker ได้เลย
GoogleMap(
..
markers: {
Marker(
icon: _markerIcon,
..
},
)
จะได้ไอคอนแบบนี้
ในเวอชันนี้ BitmapDescriptor.fromAssetImage จะยังมีบัคเรื่องของขนาดไอคอนอยู่ ทั้งๆที่สามารถใส่ค่า size ได้ แต่ไม่แสดงผลตามขนาด
การใส่ Polyline
ลองเพิ่ม polyline ใน map กันครับ มันคือการใส่เส้นที่เราต้องการลงไปในแผนที่ ซึ่ง GoogleMap Widget ก็เตรียมไว้ให้แล้ว คือกำหนดที่ polylines โดยสามารถกำหนดได้หลายอัน เพราะเป็น Set และใน polyline แต่ละอัน ก็กำหนด Lat Lng ให้มันลากเส้น จากจุดหนึ่งไปจุดหนึ่ง
ในตัวอย่างนี้มจะลากเส้นรอบพื้นที่สนามบินสุวรรณภูมิ
GoogleMap(
..
polylines: {
Polyline(
polylineId: PolylineId("p1"),
color: Colors.red[300],
points: [
LatLng(13.7123167,100.728104),
LatLng(13.655067, 100.722697),
LatLng(13.648389, 100.753335),
LatLng(13.705761, 100.779158),
LatLng(13.7123167,100.728104),
])},
จะได้แบบนี้
สรุป
Google Maps สำหรับ Flutter ภาพรวมทำได้ดีเลย การใช้งานผ่าน widget ง่ายจริงๆ แม้ว่าจะยังมียัคอยู่บ้างเพราะยังอยู่ใน develoepr preview อยู่ ในอนาคตก็จะเสถียรมากขึ้น เดี๋ยวผมจะมาอัพเดทเรื่อยๆครับ