SlideShare a Scribd company logo
怎樣在 Flutter App 中整合
Google Maps
Weizhong Yang a.k.a zonble
zonble@gmail.com
關於我
Weizhong Yang a.k.a zonble
• 最近⼗年都在做 App 開發
• Flutter GDE (2019-)
• Developer Manager at Cerence Inc (2020-)
• iOS Developer Lead at KKBOX (2011-2020)
• 今年做的事:講了⼀場 Flutter Desktop plugin 開發,下半年常跑⾦⾨,開始重
寫⼆⼗年前的《防區狀況三⽣效》的新章…
• Twitter @zonble
還記得我去年的題⽬?
總之,今年我們把去年題⽬裡頭的那個 App 商品化了
Cerence
Link
這個 App 有哪些功能?
• 顯示 ODB 傳來的讀數(速度、溫度等)
• 在地圖上顯示⽬前⾞⼦與⼿機的位置
• 顯示 Journey(每次⾞⼦上⽕/熄⽕之間的駕駛紀錄)
• 路徑規劃:
• 怎樣⾛到我的⾞⼦?
• 怎樣去加油站/維修站/⾃訂地點?
• Geo Fence 規劃(在某個區域開⾞時發出警告)
• Curfew 規劃(在某個時間開⾞發出警告)
• 各種警告—急彎、急停、撞擊、進⼊ Geo Fence
這個 App ⼤量需要顯示地圖
Agenda
• 放置地圖 Widget
• 新增 Marker
• 新增 Polyline
• 計算 zoom level
• 顯示 information window
• 移動地圖、設定樣式
• 其他…
會⽤到哪些 package?
• google_maps_
fl
utter: The o
ffi
cial package
• custom_info_window: Show custom window in the map
• google_place: Google Place API
• google_maps_utils: 地圖相關計算功能
•
fl
utter_polyline_points: encode/decode Google polyline string
基本導⼊
• 更新 pubspec.yaml
• 加⼊ google_maps_
fl
utter
•
fl
utter pub get
• 需要⼀些平台權限,例如使⽤ GPS 等,需要在 iOS 的 Info Plist 以及
Android 的 Manifest 裡頭加上對應設定
• Android 上需要安裝 Google Maps App
基本⽤法
GoogleMap(
minMaxZoomPreference: MinMaxZoomPreference(0, 16),
initialCameraPosition: LatLng(…..),
mapType: MapType.normal,
mapToolbarEnabled: false,
zoomControlsEnabled: false,
rotateGesturesEnabled: false,
scrollGesturesEnabled: false,
zoomGesturesEnabled: false,
myLocationButtonEnabled: false,
)
Platform View
• Google Maps Widget 是⼀個 Platform View
• 將 Native View 包進 Flutter 中
• 現在我們也可以在 Platform View 上重疊任意的 Flutter Widget
加上 Marker
fi
nal startPoint = await BitmapDescriptor.fromAssetImage(
ImageCon
fi
guration(size: Size(100, 100)), ‘asset/image.png’);
var markers = [];
fi
nal startPoint = Marker(
markerId: MarkerId('pin-start'),
icon: _startPoint,
anchor: O
ff
set(0.5, 0.5),
position: LatLng(….)),
zIndex: 10,
);
markers.add(startPoint);
GoogleMap(markers:markers); Image 的載⼊可以使⽤ Future Builder
需要注意載⼊的解析度(@2x、@3x)
放置 SVG Marker
import 'dart:ui' as ui;
import ‘package:
fl
utter_svg/
fl
utter_svg.dart';
static Future<BitmapDescriptor?>
bitmapDescriptorFromSvgAsset(
BuildContext context, String assetName, Size
size) async {
// Read SVG
fi
le as String
String svgString =
await
DefaultAssetBundle.of(context).loadString(assetNa
me);
// Create DrawableRoot from SVG String
DrawableRoot svgDrawableRoot = await
svg.fromSvgString(svgString, 'a');
// toPicture() and toImage() don't seem to be
pixel ratio aware, so we calculate the actual sizes
here
MediaQueryData queryData =
MediaQuery.of(context);
double devicePixelRatio =
queryData.devicePixelRatio;
double width =
size.width * devicePixelRatio; // where 32 is
your SVG's original width
double height = size.height * devicePixelRatio; //
same thing
// Convert to ui.Picture
ui.Picture picture =
svgDrawableRoot.toPicture(size: Size(width,
height));
ui.Image image = await
picture.toImage(width.toInt(), height.toInt());
ByteData? bytes = await
image.toByteData(format:
ui.ImageByteFormat.png);
fi
nal bu
ff
er = bytes?.bu
ff
er;
if (bu
ff
er != null) {
return
BitmapDescriptor.fromBytes(bu
ff
er.asUint8List());
}
return null;
}
加上 Polyline
fi
nal points = <LatLng>[……];
var lines = <Polyline>{};
fi
nal line = Polyline(
polylineId: PolylineId('planning_route'),
color: planningRouteColor,
width: 6,
points: points,
);
lines.add(line);
GoogleMap(polylines: polylines);
根據 route 計算 zoom level
num latRad(lat) {
var sin = math.sin(lat * math.pi / 180);
var radX2 = math.log((1 + sin) / (1 - sin)) / 2;
return math.max(math.min(radX2, math.pi),
-math.pi) / 2;
}
num zoom(mapPx, worldPx, fraction) {
if (fraction == 0) {
return 21;
}
fi
nal left = math.log(mapPx / worldPx / fraction);
fi
nal result = (left.
fl
oor() / math.ln2);
return result;
}
List<num> _getRegion(num screenWidth, num screenHeight) {
num zoomMax = 16.0;
num? minLong = ….;
num? maxLong = ….;
num? minLat = ….;
num? maxLat = ….;
fi
nal latFraction = (latRad(maxLat) - latRad(minLat)) / math.pi;
fi
nal lngDi
ff
= maxLong - minLong;
fi
nal lngFraction = ((lngDi
ff
< 0) ? (lngDi
ff
+ 360) : lngDi
ff
) / 360;
num latZoom = zoom(screenHeight, 256, latFraction);
num lngZoom = zoom(screenWidth, 256, lngFraction);
fi
nal zoomLevel = math.min(math.min(latZoom, lngZoom) *
0.99, zoomMax);
fi
nal centerLat = (maxLat + minLat) / 2;
fi
nal centerLong = (maxLong + minLong) / 2;
return [centerLat, centerLong, zoomLevel];
}
Custom Info Window
custom_info_window
https://pub.dev/packages/custom_info_window
Custom Info Window
Stack(
children: [
PreloadMapWrapper(
child: GoogleMap(
onCameraMove: (position) {
_customInfoWindowController.onCameraMove?.call();
},
onMapCreated: (GoogleMapController controller) {
_customInfoWindowController.googleMapController = controller;
_mapController = controller;
}, ),
),
CustomInfoWindow(
controller: _customInfoWindowController,
width: 274,
height: 189,
o
ff
set: 30,
), ],
);
var _customInfoWindowController = CustomInfoWindowController();
_customInfoWindowController.addInfoWindow?.call(window, LatLng(…));
_customInfoWindowController.hideInfoWindow?.call();
Circle
fi
nal pin = Circle(
circleId: CircleId('circle'),
radius: radius.toDouble(),
fi
llColor: ….,
strokeColor: ….,
center: location,
);
fi
nal circles = <Circle>{};
circles.add(pin);
GoogleMap(
circles: circles
)
如何更新地圖內容
• 可以透過 setState() 更新包含 GoogleMap Widget 的 Widget
• 如果使⽤ Bloc,可以將 GoogleMap Widget 放在 BlocBuilder 中
• 以上⽅式可以修改有哪些 Marker、Polyline 與 Circle,不會改變地圖的
zoom level 與中央位置
移動地圖
Future<void> goTo(num latitude, num longitude) async {
fi
nal position = CameraPosition(
target: LatLng(latitude.toDouble(), longitude.toDouble()),
zoom: zoomLevel,
);
_mapController?.animateCamera(CameraUpdate.newCameraPosition(position));
}
GoogleMapController? _mapController
GoogleMap(
onMapCreated: (GoogleMapController controller) {
_mapController = controller;
},
),
進階設置
• liteModeEnabled:⽤在完全靜態的地圖上
• indoorViewEnabled:顯示室內導航
• tra
ffi
cEnabled:顯示交通狀況
• buildingsEnabled:顯示建築物模型
設定地圖樣式
• GoogleMap Widget 本身
沒有 light/dark mode
• ⽽是設置整個 Map 樣式
的 JSON
• https://
console.cloud.google.co
m/projectselector2/
google/maps-apis/studio/
styles?pli=1
設定地圖樣式
rootBundle.loadString('assets/map_style.txt').then((string) {
_mapStyle = string;
});
GoogleMap(
onMapCreated: (GoogleMapController controller) {
mapController = controller;
mapController.setMapStyle(_mapStyle);
}
);
這個 Future 也可以考慮放在 FutureBuilder 裡頭
疑難雜症
• Android 上,如果 App 放在背景再回到前景,之後 GoogleMap Widget 可
能畫⾯花掉,或是有奇怪的狀況
• 可以⽤ WidgetsBindingObserver 偵測回到前景重繪
(didChangeAppLifecycleState)
• 重新 Build GoogleMap Widget 也不⾒得會重繪
• 但是呼叫 setMapStyle ⼀定可以重繪
地點搜尋
地點搜尋
fi
nal _googlePlace = GooglePlace(kGooglePlacesApikey);
Future<TextSearchResponse?> _callGooglePlaceAPI(String keyword, {
required double lat,
required double lng
}) async {
return await _googlePlace.search.getTextSearch(keyword,
location: Location(lat: lat, lng: lng),
);
}
路徑規劃
路徑規劃
PolylineResult result = await PolylinePoints().getRouteBetweenCoordinates(
kGoogleDirectionsApiKey,
PointLatLng(lat1, lng1),
PointLatLng(lat2, lng2),
travelMode: TravelMode.driving,
);
import 'package:
fl
utter_polyline_points/
fl
utter_polyline_points.dart';
路徑偏移(Route deviation)
• 判斷⽬前的 GPS 位置是否偏移規劃的路徑
• 可以使⽤ google_map_polyutil
• 呼叫 PolyUtils.isLocationOnEdgeTolerance,判斷座標是否在路徑上
• 還有其他⼯具
Recap
• 放置地圖 Widget
• 新增 Marker
• 新增 Polyline
• 計算 zoom level
• 顯示 information window
• 移動地圖、設定樣式
• 其他…
That’s all
謝謝⼤家

More Related Content

What's hot (20)

PDF
Vue JS Intro
Muhammad Rizki Rijal
 
PPTX
Job schedulers android
Deesha Vora
 
PPTX
gRPC on .NET Core - NDC Sydney 2019
James Newton-King
 
PPTX
Flutter
Mohit Sharma
 
PPTX
React js
Alireza Akbari
 
PPT
A Deeper look into Javascript Basics
Mindfire Solutions
 
PDF
An introduction to Vue.js
Javier Lafora Rey
 
PDF
Angular Directives
iFour Technolab Pvt. Ltd.
 
PPT
Spring Framework
nomykk
 
PPTX
Spring framework Controllers and Annotations
Anuj Singh Rajput
 
PPTX
Modern JS with ES6
Kevin Langley Jr.
 
PDF
Spring Boot
HongSeong Jeon
 
PDF
Nest.js Introduction
Takuya Tejima
 
PDF
From Spring Framework 5.3 to 6.0
VMware Tanzu
 
PDF
Rancher Simple User Guide
SANG WON PARK
 
PDF
JavaFX Overview
José Maria Silveira Neto
 
PDF
Try Jetpack Compose
LutasLin
 
PPTX
Java Spring framework, Dependency Injection, DI, IoC, Inversion of Control
Arjun Thakur
 
PDF
Binderのはじめの一歩とAndroid
l_b__
 
PDF
[Flutter] Flutter Provider 看似簡單卻又不簡單的狀態管理工具 @ Devfest Kaohsiung 2023
Johnny Sung
 
Vue JS Intro
Muhammad Rizki Rijal
 
Job schedulers android
Deesha Vora
 
gRPC on .NET Core - NDC Sydney 2019
James Newton-King
 
Flutter
Mohit Sharma
 
React js
Alireza Akbari
 
A Deeper look into Javascript Basics
Mindfire Solutions
 
An introduction to Vue.js
Javier Lafora Rey
 
Angular Directives
iFour Technolab Pvt. Ltd.
 
Spring Framework
nomykk
 
Spring framework Controllers and Annotations
Anuj Singh Rajput
 
Modern JS with ES6
Kevin Langley Jr.
 
Spring Boot
HongSeong Jeon
 
Nest.js Introduction
Takuya Tejima
 
From Spring Framework 5.3 to 6.0
VMware Tanzu
 
Rancher Simple User Guide
SANG WON PARK
 
JavaFX Overview
José Maria Silveira Neto
 
Try Jetpack Compose
LutasLin
 
Java Spring framework, Dependency Injection, DI, IoC, Inversion of Control
Arjun Thakur
 
Binderのはじめの一歩とAndroid
l_b__
 
[Flutter] Flutter Provider 看似簡單卻又不簡單的狀態管理工具 @ Devfest Kaohsiung 2023
Johnny Sung
 

Similar to 怎樣在 Flutter app 中使用 Google Maps (20)

PPTX
再接再勵學 Swift 程式設計
政斌 楊
 
PDF
I os 07
信嘉 陳
 
PPTX
2016輕鬆開發自有網路地圖工作坊 進階班 0701
family
 
DOC
Google map api接口整理
lileinba
 
PPTX
Flutter Forward Extended in Google Developer Taipei
abc873693
 
PDF
Responsive Web UI Design
jay li
 
PPT
Html5移动web应用开发(PhoneGap)
amd6400
 
PPT
Html5移动web应用开发(PhoneGap)
amd6400
 
PPTX
Study mapapi v0.1
Paul Yang
 
PDF
I os 02
信嘉 陳
 
PDF
AngularJS training in Luster
Jason Chung
 
PDF
Android 智慧型手機程式設計
Kyle Lin
 
PDF
Behind Tetris5
Junwen Sun
 
PPT
Anroid development part.1
RANK LIU
 
PPTX
行動商務實務 - PhoneGap Advance
My own sweet home!
 
PDF
I os 01
信嘉 陳
 
PPTX
Introduction to corona sdk
馬 萬圳
 
PPT
HTML5移动应用开发分享会(PhoneGap)
amd6400
 
PPT
HTML5移动WEB应用程序开发(PhoneGap)
amd6400
 
PDF
2021laravelconftwslides12
LiviaLiaoFontech
 
再接再勵學 Swift 程式設計
政斌 楊
 
I os 07
信嘉 陳
 
2016輕鬆開發自有網路地圖工作坊 進階班 0701
family
 
Google map api接口整理
lileinba
 
Flutter Forward Extended in Google Developer Taipei
abc873693
 
Responsive Web UI Design
jay li
 
Html5移动web应用开发(PhoneGap)
amd6400
 
Html5移动web应用开发(PhoneGap)
amd6400
 
Study mapapi v0.1
Paul Yang
 
I os 02
信嘉 陳
 
AngularJS training in Luster
Jason Chung
 
Android 智慧型手機程式設計
Kyle Lin
 
Behind Tetris5
Junwen Sun
 
Anroid development part.1
RANK LIU
 
行動商務實務 - PhoneGap Advance
My own sweet home!
 
I os 01
信嘉 陳
 
Introduction to corona sdk
馬 萬圳
 
HTML5移动应用开发分享会(PhoneGap)
amd6400
 
HTML5移动WEB应用程序开发(PhoneGap)
amd6400
 
2021laravelconftwslides12
LiviaLiaoFontech
 
Ad

More from Weizhong Yang (20)

PDF
Flutter BLE
Weizhong Yang
 
PDF
關於延長役期這件事情
Weizhong Yang
 
PDF
Dart null safety
Weizhong Yang
 
PDF
Github Actions
Weizhong Yang
 
PDF
iPlayground: CarPlay and MFI Hearing Aids
Weizhong Yang
 
PDF
CocoaPods private repo
Weizhong Yang
 
PDF
那些年被蘋果 Ban 掉的 API
Weizhong Yang
 
PDF
給 iOS 工程師的 Vue.js 開發
Weizhong Yang
 
PDF
苦集滅道:透過開發客製 Sketch Plug-in 改善產品設計流程
Weizhong Yang
 
PDF
使用 switch/case 重構程式碼
Weizhong Yang
 
PDF
怎樣寫出比較沒有問題的 Code
Weizhong Yang
 
PDF
貪食蛇
Weizhong Yang
 
PDF
Aspect Oriented Programming
Weizhong Yang
 
PDF
Mac OS X 與 iOS 的 Audio API
Weizhong Yang
 
KEY
Html 5 native drag
Weizhong Yang
 
KEY
Retina mac
Weizhong Yang
 
KEY
Python 的文件系統
Weizhong Yang
 
KEY
Input Method Kit
Weizhong Yang
 
KEY
Refactoring
Weizhong Yang
 
KEY
Core animation
Weizhong Yang
 
Flutter BLE
Weizhong Yang
 
關於延長役期這件事情
Weizhong Yang
 
Dart null safety
Weizhong Yang
 
Github Actions
Weizhong Yang
 
iPlayground: CarPlay and MFI Hearing Aids
Weizhong Yang
 
CocoaPods private repo
Weizhong Yang
 
那些年被蘋果 Ban 掉的 API
Weizhong Yang
 
給 iOS 工程師的 Vue.js 開發
Weizhong Yang
 
苦集滅道:透過開發客製 Sketch Plug-in 改善產品設計流程
Weizhong Yang
 
使用 switch/case 重構程式碼
Weizhong Yang
 
怎樣寫出比較沒有問題的 Code
Weizhong Yang
 
貪食蛇
Weizhong Yang
 
Aspect Oriented Programming
Weizhong Yang
 
Mac OS X 與 iOS 的 Audio API
Weizhong Yang
 
Html 5 native drag
Weizhong Yang
 
Retina mac
Weizhong Yang
 
Python 的文件系統
Weizhong Yang
 
Input Method Kit
Weizhong Yang
 
Refactoring
Weizhong Yang
 
Core animation
Weizhong Yang
 
Ad

怎樣在 Flutter app 中使用 Google Maps

  • 1. 怎樣在 Flutter App 中整合 Google Maps Weizhong Yang a.k.a zonble [email protected]
  • 2. 關於我 Weizhong Yang a.k.a zonble • 最近⼗年都在做 App 開發 • Flutter GDE (2019-) • Developer Manager at Cerence Inc (2020-) • iOS Developer Lead at KKBOX (2011-2020) • 今年做的事:講了⼀場 Flutter Desktop plugin 開發,下半年常跑⾦⾨,開始重 寫⼆⼗年前的《防區狀況三⽣效》的新章… • Twitter @zonble
  • 5. 這個 App 有哪些功能? • 顯示 ODB 傳來的讀數(速度、溫度等) • 在地圖上顯示⽬前⾞⼦與⼿機的位置 • 顯示 Journey(每次⾞⼦上⽕/熄⽕之間的駕駛紀錄) • 路徑規劃: • 怎樣⾛到我的⾞⼦? • 怎樣去加油站/維修站/⾃訂地點? • Geo Fence 規劃(在某個區域開⾞時發出警告) • Curfew 規劃(在某個時間開⾞發出警告) • 各種警告—急彎、急停、撞擊、進⼊ Geo Fence
  • 7. Agenda • 放置地圖 Widget • 新增 Marker • 新增 Polyline • 計算 zoom level • 顯示 information window • 移動地圖、設定樣式 • 其他…
  • 8. 會⽤到哪些 package? • google_maps_ fl utter: The o ffi cial package • custom_info_window: Show custom window in the map • google_place: Google Place API • google_maps_utils: 地圖相關計算功能 • fl utter_polyline_points: encode/decode Google polyline string
  • 9. 基本導⼊ • 更新 pubspec.yaml • 加⼊ google_maps_ fl utter • fl utter pub get • 需要⼀些平台權限,例如使⽤ GPS 等,需要在 iOS 的 Info Plist 以及 Android 的 Manifest 裡頭加上對應設定 • Android 上需要安裝 Google Maps App
  • 10. 基本⽤法 GoogleMap( minMaxZoomPreference: MinMaxZoomPreference(0, 16), initialCameraPosition: LatLng(…..), mapType: MapType.normal, mapToolbarEnabled: false, zoomControlsEnabled: false, rotateGesturesEnabled: false, scrollGesturesEnabled: false, zoomGesturesEnabled: false, myLocationButtonEnabled: false, )
  • 11. Platform View • Google Maps Widget 是⼀個 Platform View • 將 Native View 包進 Flutter 中 • 現在我們也可以在 Platform View 上重疊任意的 Flutter Widget
  • 12. 加上 Marker fi nal startPoint = await BitmapDescriptor.fromAssetImage( ImageCon fi guration(size: Size(100, 100)), ‘asset/image.png’); var markers = []; fi nal startPoint = Marker( markerId: MarkerId('pin-start'), icon: _startPoint, anchor: O ff set(0.5, 0.5), position: LatLng(….)), zIndex: 10, ); markers.add(startPoint); GoogleMap(markers:markers); Image 的載⼊可以使⽤ Future Builder 需要注意載⼊的解析度(@2x、@3x)
  • 13. 放置 SVG Marker import 'dart:ui' as ui; import ‘package: fl utter_svg/ fl utter_svg.dart'; static Future<BitmapDescriptor?> bitmapDescriptorFromSvgAsset( BuildContext context, String assetName, Size size) async { // Read SVG fi le as String String svgString = await DefaultAssetBundle.of(context).loadString(assetNa me); // Create DrawableRoot from SVG String DrawableRoot svgDrawableRoot = await svg.fromSvgString(svgString, 'a'); // toPicture() and toImage() don't seem to be pixel ratio aware, so we calculate the actual sizes here MediaQueryData queryData = MediaQuery.of(context); double devicePixelRatio = queryData.devicePixelRatio; double width = size.width * devicePixelRatio; // where 32 is your SVG's original width double height = size.height * devicePixelRatio; // same thing // Convert to ui.Picture ui.Picture picture = svgDrawableRoot.toPicture(size: Size(width, height)); ui.Image image = await picture.toImage(width.toInt(), height.toInt()); ByteData? bytes = await image.toByteData(format: ui.ImageByteFormat.png); fi nal bu ff er = bytes?.bu ff er; if (bu ff er != null) { return BitmapDescriptor.fromBytes(bu ff er.asUint8List()); } return null; }
  • 14. 加上 Polyline fi nal points = <LatLng>[……]; var lines = <Polyline>{}; fi nal line = Polyline( polylineId: PolylineId('planning_route'), color: planningRouteColor, width: 6, points: points, ); lines.add(line); GoogleMap(polylines: polylines);
  • 15. 根據 route 計算 zoom level num latRad(lat) { var sin = math.sin(lat * math.pi / 180); var radX2 = math.log((1 + sin) / (1 - sin)) / 2; return math.max(math.min(radX2, math.pi), -math.pi) / 2; } num zoom(mapPx, worldPx, fraction) { if (fraction == 0) { return 21; } fi nal left = math.log(mapPx / worldPx / fraction); fi nal result = (left. fl oor() / math.ln2); return result; } List<num> _getRegion(num screenWidth, num screenHeight) { num zoomMax = 16.0; num? minLong = ….; num? maxLong = ….; num? minLat = ….; num? maxLat = ….; fi nal latFraction = (latRad(maxLat) - latRad(minLat)) / math.pi; fi nal lngDi ff = maxLong - minLong; fi nal lngFraction = ((lngDi ff < 0) ? (lngDi ff + 360) : lngDi ff ) / 360; num latZoom = zoom(screenHeight, 256, latFraction); num lngZoom = zoom(screenWidth, 256, lngFraction); fi nal zoomLevel = math.min(math.min(latZoom, lngZoom) * 0.99, zoomMax); fi nal centerLat = (maxLat + minLat) / 2; fi nal centerLong = (maxLong + minLong) / 2; return [centerLat, centerLong, zoomLevel]; }
  • 17. Custom Info Window Stack( children: [ PreloadMapWrapper( child: GoogleMap( onCameraMove: (position) { _customInfoWindowController.onCameraMove?.call(); }, onMapCreated: (GoogleMapController controller) { _customInfoWindowController.googleMapController = controller; _mapController = controller; }, ), ), CustomInfoWindow( controller: _customInfoWindowController, width: 274, height: 189, o ff set: 30, ), ], ); var _customInfoWindowController = CustomInfoWindowController(); _customInfoWindowController.addInfoWindow?.call(window, LatLng(…)); _customInfoWindowController.hideInfoWindow?.call();
  • 18. Circle fi nal pin = Circle( circleId: CircleId('circle'), radius: radius.toDouble(), fi llColor: …., strokeColor: …., center: location, ); fi nal circles = <Circle>{}; circles.add(pin); GoogleMap( circles: circles )
  • 19. 如何更新地圖內容 • 可以透過 setState() 更新包含 GoogleMap Widget 的 Widget • 如果使⽤ Bloc,可以將 GoogleMap Widget 放在 BlocBuilder 中 • 以上⽅式可以修改有哪些 Marker、Polyline 與 Circle,不會改變地圖的 zoom level 與中央位置
  • 20. 移動地圖 Future<void> goTo(num latitude, num longitude) async { fi nal position = CameraPosition( target: LatLng(latitude.toDouble(), longitude.toDouble()), zoom: zoomLevel, ); _mapController?.animateCamera(CameraUpdate.newCameraPosition(position)); } GoogleMapController? _mapController GoogleMap( onMapCreated: (GoogleMapController controller) { _mapController = controller; }, ),
  • 21. 進階設置 • liteModeEnabled:⽤在完全靜態的地圖上 • indoorViewEnabled:顯示室內導航 • tra ffi cEnabled:顯示交通狀況 • buildingsEnabled:顯示建築物模型
  • 22. 設定地圖樣式 • GoogleMap Widget 本身 沒有 light/dark mode • ⽽是設置整個 Map 樣式 的 JSON • https:// console.cloud.google.co m/projectselector2/ google/maps-apis/studio/ styles?pli=1
  • 23. 設定地圖樣式 rootBundle.loadString('assets/map_style.txt').then((string) { _mapStyle = string; }); GoogleMap( onMapCreated: (GoogleMapController controller) { mapController = controller; mapController.setMapStyle(_mapStyle); } ); 這個 Future 也可以考慮放在 FutureBuilder 裡頭
  • 24. 疑難雜症 • Android 上,如果 App 放在背景再回到前景,之後 GoogleMap Widget 可 能畫⾯花掉,或是有奇怪的狀況 • 可以⽤ WidgetsBindingObserver 偵測回到前景重繪 (didChangeAppLifecycleState) • 重新 Build GoogleMap Widget 也不⾒得會重繪 • 但是呼叫 setMapStyle ⼀定可以重繪
  • 26. 地點搜尋 fi nal _googlePlace = GooglePlace(kGooglePlacesApikey); Future<TextSearchResponse?> _callGooglePlaceAPI(String keyword, { required double lat, required double lng }) async { return await _googlePlace.search.getTextSearch(keyword, location: Location(lat: lat, lng: lng), ); }
  • 28. 路徑規劃 PolylineResult result = await PolylinePoints().getRouteBetweenCoordinates( kGoogleDirectionsApiKey, PointLatLng(lat1, lng1), PointLatLng(lat2, lng2), travelMode: TravelMode.driving, ); import 'package: fl utter_polyline_points/ fl utter_polyline_points.dart';
  • 29. 路徑偏移(Route deviation) • 判斷⽬前的 GPS 位置是否偏移規劃的路徑 • 可以使⽤ google_map_polyutil • 呼叫 PolyUtils.isLocationOnEdgeTolerance,判斷座標是否在路徑上 • 還有其他⼯具
  • 30. Recap • 放置地圖 Widget • 新增 Marker • 新增 Polyline • 計算 zoom level • 顯示 information window • 移動地圖、設定樣式 • 其他…