API documentation and cleanup (#545)
diff --git a/packages/google_maps_flutter/README.md b/packages/google_maps_flutter/README.md
index 0302726..7b9b5f7 100644
--- a/packages/google_maps_flutter/README.md
+++ b/packages/google_maps_flutter/README.md
@@ -2,13 +2,45 @@
[](https://pub.dartlang.org/packages/google_maps_flutter)
-A Flutter plugin to use [Google Maps](https://developers.google.com/maps/) for iOS and Android.
+A Flutter plugin to use [Google Maps](https://developers.google.com/maps/) in
+iOS and Android apps.
-*Note*: This plugin is currently a stub and under active development.
+## Caveat
+
+This plugin provides an *unpublished preview* of the Flutter API for GoogleMaps:
+* Dart APIs for controlling and interacting with a GoogleMap view from Flutter
+ code are still being consolidated and expanded. The intention is to grow
+ current coverage into a complete offering. Issues and pull requests aimed to
+ help us prioritize and speed up this effort are very welcome.
+* The technique currently used for compositing GoogleMap views with Flutter
+ widgets is *inherently limited* and will be replaced by a fully compositional
+ [Texture](https://docs.flutter.io/flutter/widgets/Texture-class.html)-based
+ approach before we publish this plugin.
+
+ In detail: the plugin currently relies on placing platform overlays on top of
+ a bitmap snapshotting widget for creating the illusion of in-line compositing
+ of GoogleMap views with Flutter widgets. This works only in very limited
+ situations where
+ * the widget is stationary
+ * the widget is drawn on top of all other widgets within bounds
+ * touch events within widget bounds can be safely ignored by Flutter
+
+ The root problem with platform overlays is that they cannot be freely composed
+ with other widgets. Many workarounds can be devised to address this shortcoming
+ in particular situations, but the Flutter team does not intend to support such
+ work, as it would not move us forward towards our goal of a fully compositional
+ GoogleMaps widget.
## Usage
-To use this plugin, add `google_maps_flutter` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
+To use this plugin, add
+```yaml
+ google_maps_flutter:
+ git:
+ url: git://github.com/flutter/plugins
+ path: packages/google_maps_flutter
+```
+as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
## Getting Started
diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
index 0ee9d40..de2023e 100644
--- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
+++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
@@ -104,6 +104,9 @@
}
static Object toJson(CameraPosition position) {
+ if (position == null) {
+ return null;
+ }
final Map<String, Object> data = new HashMap<>();
data.put("bearing", position.bearing);
data.put("target", toJson(position.target));
diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
index 13a1862..ef905c3 100644
--- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
+++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
@@ -163,6 +163,10 @@
googleMap.animateCamera(cameraUpdate);
}
+ CameraPosition getCameraPosition() {
+ return trackCameraPosition ? googleMap.getCameraPosition() : null;
+ }
+
MarkerBuilder newMarkerBuilder() {
return new MarkerBuilder(this);
}
diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
index 692a820..5805aab 100644
--- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
+++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
@@ -65,7 +65,7 @@
result.success(null);
break;
}
- case "createMap":
+ case "map#create":
{
final int width = Convert.toPixels(call.argument("width"), density);
final int height = Convert.toPixels(call.argument("height"), density);
@@ -82,7 +82,7 @@
final Map<String, Object> arguments = new HashMap<>(2);
arguments.put("map", controller.id());
arguments.put("isGesture", isGesture);
- channel.invokeMethod("map#onCameraMoveStarted", arguments);
+ channel.invokeMethod("camera#onMoveStarted", arguments);
}
@Override
@@ -90,13 +90,13 @@
final Map<String, Object> arguments = new HashMap<>(2);
arguments.put("map", controller.id());
arguments.put("position", Convert.toJson(position));
- channel.invokeMethod("map#onCameraMove", arguments);
+ channel.invokeMethod("camera#onMove", arguments);
}
@Override
public void onCameraIdle() {
channel.invokeMethod(
- "map#onCameraIdle", Collections.singletonMap("map", controller.id()));
+ "camera#onIdle", Collections.singletonMap("map", controller.id()));
}
});
controller.setOnMarkerTappedListener(
@@ -113,14 +113,14 @@
// is ready
break;
}
- case "updateMapOptions":
+ case "map#update":
{
final GoogleMapController controller = mapsController(call);
Convert.interpretGoogleMapOptions(call.argument("options"), controller);
- result.success(null);
+ result.success(Convert.toJson(controller.getCameraPosition()));
break;
}
- case "moveCamera":
+ case "camera#move":
{
final GoogleMapController controller = mapsController(call);
final CameraUpdate cameraUpdate =
@@ -129,7 +129,7 @@
result.success(null);
break;
}
- case "animateCamera":
+ case "camera#animate":
{
final GoogleMapController controller = mapsController(call);
final CameraUpdate cameraUpdate =
@@ -138,7 +138,7 @@
result.success(null);
break;
}
- case "addMarker":
+ case "marker#add":
{
final GoogleMapController controller = mapsController(call);
final MarkerBuilder markerBuilder = controller.newMarkerBuilder();
@@ -164,7 +164,7 @@
result.success(null);
break;
}
- case "showMapOverlay":
+ case "map#show":
{
final GoogleMapController controller = mapsController(call);
final int x = Convert.toPixels(call.argument("x"), density);
@@ -173,14 +173,14 @@
result.success(null);
break;
}
- case "hideMapOverlay":
+ case "map#hide":
{
final GoogleMapController controller = mapsController(call);
controller.hideOverlay();
result.success(null);
break;
}
- case "disposeMap":
+ case "map#dispose":
{
final GoogleMapController controller = mapsController(call);
controller.dispose();
diff --git a/packages/google_maps_flutter/example/lib/animate_camera.dart b/packages/google_maps_flutter/example/lib/animate_camera.dart
index 25a7769..0a7bfe1 100644
--- a/packages/google_maps_flutter/example/lib/animate_camera.dart
+++ b/packages/google_maps_flutter/example/lib/animate_camera.dart
@@ -9,31 +9,31 @@
class AnimateCameraPage extends Page {
AnimateCameraPage()
- : super(const Icon(Icons.map), "Camera control, animated");
+ : super(const Icon(Icons.map), 'Camera control, animated');
@override
final GoogleMapOverlayController controller =
- new GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
+ GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
@override
Widget build(BuildContext context) {
- return new Column(
+ return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
- new Center(child: new GoogleMapOverlay(controller: controller)),
- new Row(
+ Center(child: GoogleMapOverlay(controller: controller)),
+ Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
- new Column(
+ Column(
children: <Widget>[
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.newCameraPosition(
const CameraPosition(
bearing: 270.0,
- target: const LatLng(51.5160895, -0.1294527),
+ target: LatLng(51.5160895, -0.1294527),
tilt: 30.0,
zoom: 17.0,
),
@@ -42,7 +42,7 @@
},
child: const Text('newCameraPosition'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.newLatLng(
@@ -52,11 +52,11 @@
},
child: const Text('newLatLng'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.newLatLngBounds(
- const LatLngBounds(
+ LatLngBounds(
southwest: const LatLng(-38.483935, 113.248673),
northeast: const LatLng(-8.982446, 153.823821),
),
@@ -66,7 +66,7 @@
},
child: const Text('newLatLngBounds'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.newLatLngZoom(
@@ -77,7 +77,7 @@
},
child: const Text('newLatLngZoom'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.scrollBy(150.0, -225.0),
@@ -87,9 +87,9 @@
),
],
),
- new Column(
+ Column(
children: <Widget>[
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.zoomBy(
@@ -100,7 +100,7 @@
},
child: const Text('zoomBy with focus'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.zoomBy(-0.5),
@@ -108,7 +108,7 @@
},
child: const Text('zoomBy'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.zoomIn(),
@@ -116,7 +116,7 @@
},
child: const Text('zoomIn'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.zoomOut(),
@@ -124,7 +124,7 @@
},
child: const Text('zoomOut'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.animateCamera(
CameraUpdate.zoomTo(16.0),
diff --git a/packages/google_maps_flutter/example/lib/main.dart b/packages/google_maps_flutter/example/lib/main.dart
index de1affa..e8161b6 100644
--- a/packages/google_maps_flutter/example/lib/main.dart
+++ b/packages/google_maps_flutter/example/lib/main.dart
@@ -11,30 +11,30 @@
import 'place_marker.dart';
final List<Page> _allPages = <Page>[
- new MapUiPage(),
- new AnimateCameraPage(),
- new MoveCameraPage(),
- new PlaceMarkerPage(),
+ MapUiPage(),
+ AnimateCameraPage(),
+ MoveCameraPage(),
+ PlaceMarkerPage(),
];
class MapsDemo extends StatelessWidget {
void _pushPage(BuildContext context, Page page) {
- Navigator.of(context).push(new MaterialPageRoute<void>(
- builder: (_) => new Scaffold(
- appBar: new AppBar(title: new Text(page.title)),
+ Navigator.of(context).push(MaterialPageRoute<void>(
+ builder: (_) => Scaffold(
+ appBar: AppBar(title: Text(page.title)),
body: page,
)));
}
@override
Widget build(BuildContext context) {
- return new Scaffold(
- appBar: new AppBar(title: const Text('GoogleMaps examples')),
- body: new ListView.builder(
+ return Scaffold(
+ appBar: AppBar(title: const Text('GoogleMaps examples')),
+ body: ListView.builder(
itemCount: _allPages.length,
- itemBuilder: (_, int index) => new ListTile(
+ itemBuilder: (_, int index) => ListTile(
leading: _allPages[index].leading,
- title: new Text(_allPages[index].title),
+ title: Text(_allPages[index].title),
onTap: () => _pushPage(context, _allPages[index]),
),
),
@@ -48,5 +48,5 @@
for (Page p in _allPages) {
observers.add(p.controller.overlayController);
}
- runApp(new MaterialApp(home: new MapsDemo(), navigatorObservers: observers));
+ runApp(MaterialApp(home: MapsDemo(), navigatorObservers: observers));
}
diff --git a/packages/google_maps_flutter/example/lib/map_ui.dart b/packages/google_maps_flutter/example/lib/map_ui.dart
index b362e32..3c11273 100644
--- a/packages/google_maps_flutter/example/lib/map_ui.dart
+++ b/packages/google_maps_flutter/example/lib/map_ui.dart
@@ -7,22 +7,22 @@
import 'page.dart';
-const LatLngBounds sydneyBounds = const LatLngBounds(
+final LatLngBounds sydneyBounds = LatLngBounds(
southwest: const LatLng(-34.022631, 150.620685),
northeast: const LatLng(-33.571835, 151.325952),
);
class MapUiPage extends Page {
- MapUiPage() : super(const Icon(Icons.map), "User interface");
+ MapUiPage() : super(const Icon(Icons.map), 'User interface');
@override
final GoogleMapOverlayController controller =
- new GoogleMapOverlayController.fromSize(
+ GoogleMapOverlayController.fromSize(
width: 300.0,
height: 200.0,
- options: const GoogleMapOptions(
+ options: GoogleMapOptions(
cameraPosition: const CameraPosition(
- target: const LatLng(-33.852, 151.211),
+ target: LatLng(-33.852, 151.211),
zoom: 11.0,
),
trackCameraPosition: true,
@@ -31,7 +31,7 @@
@override
Widget build(BuildContext context) {
- return new MapUiBody(controller);
+ return MapUiBody(controller);
}
}
@@ -41,7 +41,7 @@
const MapUiBody(this.controller);
@override
- State<StatefulWidget> createState() => new MapUiBodyState();
+ State<StatefulWidget> createState() => MapUiBodyState();
}
class MapUiBodyState extends State<MapUiBody> {
@@ -66,29 +66,28 @@
}
Widget _compassToggler() {
- return new FlatButton(
- child:
- new Text('${_options.compassEnabled ? 'disable' : 'enable'} compass'),
+ return FlatButton(
+ child: Text('${_options.compassEnabled ? 'disable' : 'enable'} compass'),
onPressed: () {
widget.controller.mapController.updateMapOptions(
- new GoogleMapOptions(compassEnabled: !_options.compassEnabled),
+ GoogleMapOptions(compassEnabled: !_options.compassEnabled),
);
},
);
}
Widget _latLngBoundsToggler() {
- return new FlatButton(
- child: new Text(
+ return FlatButton(
+ child: Text(
_options.cameraTargetBounds.bounds == null
? 'bound camera target'
: 'release camera target',
),
onPressed: () {
widget.controller.mapController.updateMapOptions(
- new GoogleMapOptions(
+ GoogleMapOptions(
cameraTargetBounds: _options.cameraTargetBounds.bounds == null
- ? const CameraTargetBounds(sydneyBounds)
+ ? CameraTargetBounds(sydneyBounds)
: CameraTargetBounds.unbounded,
),
);
@@ -97,13 +96,13 @@
}
Widget _zoomBoundsToggler() {
- return new FlatButton(
- child: new Text(_options.minMaxZoomPreference.minZoom == null
+ return FlatButton(
+ child: Text(_options.minMaxZoomPreference.minZoom == null
? 'bound zoom'
: 'release zoom'),
onPressed: () {
widget.controller.mapController.updateMapOptions(
- new GoogleMapOptions(
+ GoogleMapOptions(
minMaxZoomPreference: _options.minMaxZoomPreference.minZoom == null
? const MinMaxZoomPreference(12.0, 16.0)
: MinMaxZoomPreference.unbounded,
@@ -116,23 +115,23 @@
Widget _mapTypeCycler() {
final MapType nextType =
MapType.values[(_options.mapType.index + 1) % MapType.values.length];
- return new FlatButton(
- child: new Text('change map type to $nextType'),
+ return FlatButton(
+ child: Text('change map type to $nextType'),
onPressed: () {
widget.controller.mapController.updateMapOptions(
- new GoogleMapOptions(mapType: nextType),
+ GoogleMapOptions(mapType: nextType),
);
},
);
}
Widget _rotateToggler() {
- return new FlatButton(
- child: new Text(
+ return FlatButton(
+ child: Text(
'${_options.rotateGesturesEnabled ? 'disable' : 'enable'} rotate'),
onPressed: () {
widget.controller.mapController.updateMapOptions(
- new GoogleMapOptions(
+ GoogleMapOptions(
rotateGesturesEnabled: !_options.rotateGesturesEnabled,
),
);
@@ -141,12 +140,12 @@
}
Widget _scrollToggler() {
- return new FlatButton(
- child: new Text(
+ return FlatButton(
+ child: Text(
'${_options.scrollGesturesEnabled ? 'disable' : 'enable'} scroll'),
onPressed: () {
widget.controller.mapController.updateMapOptions(
- new GoogleMapOptions(
+ GoogleMapOptions(
scrollGesturesEnabled: !_options.scrollGesturesEnabled,
),
);
@@ -155,12 +154,12 @@
}
Widget _tiltToggler() {
- return new FlatButton(
- child: new Text(
- '${_options.tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
+ return FlatButton(
+ child:
+ Text('${_options.tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
onPressed: () {
widget.controller.mapController.updateMapOptions(
- new GoogleMapOptions(
+ GoogleMapOptions(
tiltGesturesEnabled: !_options.tiltGesturesEnabled,
),
);
@@ -169,12 +168,12 @@
}
Widget _zoomToggler() {
- return new FlatButton(
- child: new Text(
- '${_options.zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
+ return FlatButton(
+ child:
+ Text('${_options.zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
onPressed: () {
widget.controller.mapController.updateMapOptions(
- new GoogleMapOptions(
+ GoogleMapOptions(
zoomGesturesEnabled: !_options.zoomGesturesEnabled,
),
);
@@ -184,25 +183,25 @@
@override
Widget build(BuildContext context) {
- return new Column(
+ return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
- new Padding(
+ Padding(
padding: const EdgeInsets.all(10.0),
- child: new Center(
- child: new GoogleMapOverlay(controller: widget.controller),
+ child: Center(
+ child: GoogleMapOverlay(controller: widget.controller),
),
),
- new Column(
+ Column(
children: <Widget>[
- new Text('camera bearing: ${_position.bearing}'),
- new Text(
+ Text('camera bearing: ${_position.bearing}'),
+ Text(
'camera target: ${_position.target.latitude.toStringAsFixed(4)},'
'${_position.target.longitude.toStringAsFixed(4)}'),
- new Text('camera zoom: ${_position.zoom}'),
- new Text('camera tilt: ${_position.tilt}'),
- new Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
+ Text('camera zoom: ${_position.zoom}'),
+ Text('camera tilt: ${_position.tilt}'),
+ Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
_compassToggler(),
_latLngBoundsToggler(),
_mapTypeCycler(),
diff --git a/packages/google_maps_flutter/example/lib/move_camera.dart b/packages/google_maps_flutter/example/lib/move_camera.dart
index 2675d6b..8160495 100644
--- a/packages/google_maps_flutter/example/lib/move_camera.dart
+++ b/packages/google_maps_flutter/example/lib/move_camera.dart
@@ -8,31 +8,31 @@
import 'page.dart';
class MoveCameraPage extends Page {
- MoveCameraPage() : super(const Icon(Icons.map), "Camera control");
+ MoveCameraPage() : super(const Icon(Icons.map), 'Camera control');
@override
final GoogleMapOverlayController controller =
- new GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
+ GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
@override
Widget build(BuildContext context) {
- return new Column(
+ return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
- new Center(child: new GoogleMapOverlay(controller: controller)),
- new Row(
+ Center(child: GoogleMapOverlay(controller: controller)),
+ Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
- new Column(
+ Column(
children: <Widget>[
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.newCameraPosition(
const CameraPosition(
bearing: 270.0,
- target: const LatLng(51.5160895, -0.1294527),
+ target: LatLng(51.5160895, -0.1294527),
tilt: 30.0,
zoom: 17.0,
),
@@ -41,7 +41,7 @@
},
child: const Text('newCameraPosition'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.newLatLng(
@@ -51,11 +51,11 @@
},
child: const Text('newLatLng'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.newLatLngBounds(
- const LatLngBounds(
+ LatLngBounds(
southwest: const LatLng(-38.483935, 113.248673),
northeast: const LatLng(-8.982446, 153.823821),
),
@@ -65,7 +65,7 @@
},
child: const Text('newLatLngBounds'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.newLatLngZoom(
@@ -76,7 +76,7 @@
},
child: const Text('newLatLngZoom'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.scrollBy(150.0, -225.0),
@@ -86,9 +86,9 @@
),
],
),
- new Column(
+ Column(
children: <Widget>[
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.zoomBy(
@@ -99,7 +99,7 @@
},
child: const Text('zoomBy with focus'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.zoomBy(-0.5),
@@ -107,7 +107,7 @@
},
child: const Text('zoomBy'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.zoomIn(),
@@ -115,7 +115,7 @@
},
child: const Text('zoomIn'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.zoomOut(),
@@ -123,7 +123,7 @@
},
child: const Text('zoomOut'),
),
- new FlatButton(
+ FlatButton(
onPressed: () {
controller.mapController.moveCamera(
CameraUpdate.zoomTo(16.0),
diff --git a/packages/google_maps_flutter/example/lib/place_marker.dart b/packages/google_maps_flutter/example/lib/place_marker.dart
index b2fe8c6..5b0a3eb 100644
--- a/packages/google_maps_flutter/example/lib/place_marker.dart
+++ b/packages/google_maps_flutter/example/lib/place_marker.dart
@@ -10,16 +10,16 @@
import 'page.dart';
class PlaceMarkerPage extends Page {
- PlaceMarkerPage() : super(const Icon(Icons.place), "Place marker");
+ PlaceMarkerPage() : super(const Icon(Icons.place), 'Place marker');
@override
final GoogleMapOverlayController controller =
- new GoogleMapOverlayController.fromSize(
+ GoogleMapOverlayController.fromSize(
width: 300.0,
height: 200.0,
- options: const GoogleMapOptions(
+ options: GoogleMapOptions(
cameraPosition: const CameraPosition(
- target: const LatLng(-33.852, 151.211),
+ target: LatLng(-33.852, 151.211),
zoom: 11.0,
),
),
@@ -27,7 +27,7 @@
@override
Widget build(BuildContext context) {
- return new PlaceMarkerBody(controller);
+ return PlaceMarkerBody(controller);
}
}
@@ -37,39 +37,53 @@
const PlaceMarkerBody(this.controller);
@override
- State<StatefulWidget> createState() => new PlaceMarkerBodyState();
+ State<StatefulWidget> createState() {
+ return PlaceMarkerBodyState(controller.mapController);
+ }
}
class PlaceMarkerBodyState extends State<PlaceMarkerBody> {
- static const LatLng center = const LatLng(-33.86711, 151.1947171);
+ static final LatLng center = const LatLng(-33.86711, 151.1947171);
+ PlaceMarkerBodyState(this.controller);
+
+ final GoogleMapController controller;
int _markerCount = 0;
Marker _selectedMarker;
@override
void initState() {
super.initState();
- widget.controller.mapController.onMarkerTapped.add((Marker marker) {
+ controller.onMarkerTapped.add((Marker marker) {
if (_selectedMarker != null) {
- _selectedMarker
- .update(const MarkerOptions(icon: BitmapDescriptor.defaultMarker));
+ _updateSelectedMarker(
+ const MarkerOptions(icon: BitmapDescriptor.defaultMarker),
+ );
}
setState(() {
_selectedMarker = marker;
});
- _selectedMarker.update(new MarkerOptions(
- icon: BitmapDescriptor
- .defaultMarkerWithHue(BitmapDescriptor.hueGreen)));
+ _updateSelectedMarker(
+ MarkerOptions(
+ icon: BitmapDescriptor.defaultMarkerWithHue(
+ BitmapDescriptor.hueGreen,
+ ),
+ ),
+ );
});
}
+ void _updateSelectedMarker(MarkerOptions changes) {
+ controller.updateMarker(_selectedMarker, changes);
+ }
+
void _add() {
- widget.controller.mapController.addMarker(new MarkerOptions(
- position: new LatLng(
+ controller.addMarker(MarkerOptions(
+ position: LatLng(
center.latitude + sin(_markerCount * pi / 6.0) / 20.0,
center.longitude + cos(_markerCount * pi / 6.0) / 20.0,
),
- infoWindowText: new InfoWindowText('Marker #${_markerCount + 1}', '*'),
+ infoWindowText: InfoWindowText('Marker #${_markerCount + 1}', '*'),
));
setState(() {
_markerCount += 1;
@@ -77,7 +91,7 @@
}
void _remove() {
- _selectedMarker.remove();
+ controller.removeMarker(_selectedMarker);
setState(() {
_selectedMarker = null;
_markerCount -= 1;
@@ -86,13 +100,13 @@
void _changePosition() {
final LatLng current = _selectedMarker.options.position;
- final Offset offset = new Offset(
+ final Offset offset = Offset(
center.latitude - current.latitude,
center.longitude - current.longitude,
);
- _selectedMarker.update(
- new MarkerOptions(
- position: new LatLng(
+ _updateSelectedMarker(
+ MarkerOptions(
+ position: LatLng(
center.latitude + offset.dy,
center.longitude + offset.dx,
),
@@ -102,32 +116,30 @@
void _changeAnchor() {
final Offset currentAnchor = _selectedMarker.options.anchor;
- final Offset newAnchor =
- new Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
- _selectedMarker.update(new MarkerOptions(anchor: newAnchor));
+ final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
+ _updateSelectedMarker(MarkerOptions(anchor: newAnchor));
}
Future<void> _changeInfoAnchor() async {
final Offset currentAnchor = _selectedMarker.options.infoWindowAnchor;
- final Offset newAnchor =
- new Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
- _selectedMarker.update(new MarkerOptions(infoWindowAnchor: newAnchor));
+ final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
+ _updateSelectedMarker(MarkerOptions(infoWindowAnchor: newAnchor));
}
Future<void> _toggleDraggable() async {
- _selectedMarker.update(
- new MarkerOptions(draggable: !_selectedMarker.options.draggable));
+ _updateSelectedMarker(
+ MarkerOptions(draggable: !_selectedMarker.options.draggable),
+ );
}
Future<void> _toggleFlat() async {
- _selectedMarker
- .update(new MarkerOptions(flat: !_selectedMarker.options.flat));
+ _updateSelectedMarker(MarkerOptions(flat: !_selectedMarker.options.flat));
}
Future<void> _changeInfo() async {
final InfoWindowText currentInfo = _selectedMarker.options.infoWindowText;
- _selectedMarker.update(new MarkerOptions(
- infoWindowText: new InfoWindowText(
+ _updateSelectedMarker(MarkerOptions(
+ infoWindowText: InfoWindowText(
currentInfo.title,
currentInfo.snippet + '*',
),
@@ -136,100 +148,101 @@
Future<void> _changeAlpha() async {
final double current = _selectedMarker.options.alpha;
- _selectedMarker.update(
- new MarkerOptions(alpha: current < 0.1 ? 1.0 : current * 0.75),
+ _updateSelectedMarker(
+ MarkerOptions(alpha: current < 0.1 ? 1.0 : current * 0.75),
);
}
Future<void> _changeRotation() async {
final double current = _selectedMarker.options.rotation;
- _selectedMarker.update(
- new MarkerOptions(rotation: current == 330.0 ? 0.0 : current + 30.0),
+ _updateSelectedMarker(
+ MarkerOptions(rotation: current == 330.0 ? 0.0 : current + 30.0),
);
}
Future<void> _toggleVisible() async {
- _selectedMarker
- .update(new MarkerOptions(visible: !_selectedMarker.options.visible));
+ _updateSelectedMarker(
+ MarkerOptions(visible: !_selectedMarker.options.visible),
+ );
}
Future<void> _changeZIndex() async {
final double current = _selectedMarker.options.zIndex;
- _selectedMarker.update(
- new MarkerOptions(zIndex: current == 12.0 ? 0.0 : current + 1.0),
+ _updateSelectedMarker(
+ MarkerOptions(zIndex: current == 12.0 ? 0.0 : current + 1.0),
);
}
@override
Widget build(BuildContext context) {
- return new Column(
+ return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
- new Center(child: new GoogleMapOverlay(controller: widget.controller)),
- new Row(
+ Center(child: GoogleMapOverlay(controller: widget.controller)),
+ Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
- new Row(
+ Row(
children: <Widget>[
- new Column(
+ Column(
children: <Widget>[
- new FlatButton(
+ FlatButton(
child: const Text('add'),
onPressed: (_markerCount == 12) ? null : _add,
),
- new FlatButton(
+ FlatButton(
child: const Text('remove'),
onPressed: (_selectedMarker == null) ? null : _remove,
),
- new FlatButton(
+ FlatButton(
child: const Text('change info'),
onPressed: (_selectedMarker == null) ? null : _changeInfo,
),
- new FlatButton(
+ FlatButton(
child: const Text('change info anchor'),
onPressed:
(_selectedMarker == null) ? null : _changeInfoAnchor,
),
],
),
- new Column(
+ Column(
children: <Widget>[
- new FlatButton(
+ FlatButton(
child: const Text('change alpha'),
onPressed:
(_selectedMarker == null) ? null : _changeAlpha,
),
- new FlatButton(
+ FlatButton(
child: const Text('change anchor'),
onPressed:
(_selectedMarker == null) ? null : _changeAnchor,
),
- new FlatButton(
+ FlatButton(
child: const Text('toggle draggable'),
onPressed:
(_selectedMarker == null) ? null : _toggleDraggable,
),
- new FlatButton(
+ FlatButton(
child: const Text('toggle flat'),
onPressed: (_selectedMarker == null) ? null : _toggleFlat,
),
- new FlatButton(
+ FlatButton(
child: const Text('change position'),
onPressed:
(_selectedMarker == null) ? null : _changePosition,
),
- new FlatButton(
+ FlatButton(
child: const Text('change rotation'),
onPressed:
(_selectedMarker == null) ? null : _changeRotation,
),
- new FlatButton(
+ FlatButton(
child: const Text('toggle visible'),
onPressed:
(_selectedMarker == null) ? null : _toggleVisible,
),
- new FlatButton(
+ FlatButton(
child: const Text('change zIndex'),
onPressed:
(_selectedMarker == null) ? null : _changeZIndex,
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapController.h b/packages/google_maps_flutter/ios/Classes/GoogleMapController.h
index 16ea81d..a111525 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapController.h
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapController.h
@@ -41,6 +41,7 @@
- (void)hide;
- (void)animateWithCameraUpdate:(GMSCameraUpdate*)cameraUpdate;
- (void)moveWithCameraUpdate:(GMSCameraUpdate*)cameraUpdate;
+- (GMSCameraPosition*)cameraPosition;
- (NSString*)addMarkerWithPosition:(CLLocationCoordinate2D)position;
- (FLTGoogleMapMarkerController*)markerWithId:(NSString*)markerId;
- (void)removeMarkerWithId:(NSString*)markerId;
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m
index 4391106..0cfae0b 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m
@@ -59,6 +59,14 @@
[_mapView moveCamera:cameraUpdate];
}
+- (GMSCameraPosition*)cameraPosition {
+ if (_trackCameraPosition) {
+ return _mapView.camera;
+ } else {
+ return nil;
+ }
+}
+
- (NSString*)addMarkerWithPosition:(CLLocationCoordinate2D)position {
FLTGoogleMapMarkerController* markerController =
[[FLTGoogleMapMarkerController alloc] initWithPosition:position mapView:_mapView];
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
index ee724b5..2e5b80a 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
@@ -15,6 +15,7 @@
- (void)setIcon:(UIImage*)icon;
- (void)setInfoWindowAnchor:(CGPoint)anchor;
- (void)setInfoWindowTitle:(NSString*)title snippet:(NSString*)snippet;
+- (void)setPosition:(CLLocationCoordinate2D)position;
- (void)setRotation:(CLLocationDegrees)rotation;
- (void)setVisible:(BOOL)visible;
- (void)setZIndex:(int)zIndex;
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
index d552db9..600c755 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
@@ -48,6 +48,9 @@
_marker.title = title;
_marker.snippet = snippet;
}
+- (void)setPosition:(CLLocationCoordinate2D)position {
+ _marker.position = position;
+}
- (void)setRotation:(CLLocationDegrees)rotation {
_marker.rotation = rotation;
}
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapsPlugin.m b/packages/google_maps_flutter/ios/Classes/GoogleMapsPlugin.m
index 29063e9..a251ceb 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapsPlugin.m
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapsPlugin.m
@@ -53,7 +53,7 @@
}
[_mapControllers removeAllObjects];
result(nil);
- } else if ([call.method isEqualToString:@"createMap"]) {
+ } else if ([call.method isEqualToString:@"map#create"]) {
NSDictionary* options = call.arguments[@"options"];
GMSCameraPosition* camera = toOptionalCameraPosition(options[@"cameraPosition"]);
FLTGoogleMapController* controller =
@@ -73,22 +73,22 @@
result(error);
return;
}
- if ([call.method isEqualToString:@"showMapOverlay"]) {
+ if ([call.method isEqualToString:@"map#show"]) {
[controller showAtX:toDouble(call.arguments[@"x"]) Y:toDouble(call.arguments[@"y"])];
result(nil);
- } else if ([call.method isEqualToString:@"hideMapOverlay"]) {
+ } else if ([call.method isEqualToString:@"map#hide"]) {
[controller hide];
result(nil);
- } else if ([call.method isEqualToString:@"animateCamera"]) {
+ } else if ([call.method isEqualToString:@"camera#animate"]) {
[controller animateWithCameraUpdate:toCameraUpdate(call.arguments[@"cameraUpdate"])];
result(nil);
- } else if ([call.method isEqualToString:@"moveCamera"]) {
+ } else if ([call.method isEqualToString:@"camera#move"]) {
[controller moveWithCameraUpdate:toCameraUpdate(call.arguments[@"cameraUpdate"])];
result(nil);
- } else if ([call.method isEqualToString:@"updateMapOptions"]) {
+ } else if ([call.method isEqualToString:@"map#update"]) {
interpretMapOptions(call.arguments[@"options"], controller);
- result(nil);
- } else if ([call.method isEqualToString:@"addMarker"]) {
+ result(positionToJson([controller cameraPosition]));
+ } else if ([call.method isEqualToString:@"marker#add"]) {
NSDictionary* options = call.arguments[@"options"];
NSString* markerId = [controller addMarkerWithPosition:toLocation(options[@"position"])];
interpretMarkerOptions(options, [controller markerWithId:markerId], _registrar);
@@ -118,7 +118,7 @@
#pragma mark - FLTGoogleMapsDelegate methods, used to send platform messages to Flutter
- (void)onCameraMoveStartedOnMap:(id)mapId gesture:(BOOL)gesture {
- [_channel invokeMethod:@"map#onCameraMoveStarted"
+ [_channel invokeMethod:@"camera#onMoveStarted"
arguments:@{
@"map" : mapId,
@"isGesture" : @(gesture)
@@ -126,12 +126,12 @@
}
- (void)onCameraMoveOnMap:(id)mapId cameraPosition:(GMSCameraPosition*)cameraPosition {
- [_channel invokeMethod:@"map#onCameraMove"
+ [_channel invokeMethod:@"camera#onMove"
arguments:@{@"map" : mapId, @"position" : positionToJson(cameraPosition)}];
}
- (void)onCameraIdleOnMap:(id)mapId {
- [_channel invokeMethod:@"map#onCameraIdle" arguments:@{@"map" : mapId}];
+ [_channel invokeMethod:@"camera#onIdle" arguments:@{@"map" : mapId}];
}
- (void)onMarkerTappedOnMap:(id)mapId marker:(NSString*)markerId {
@@ -146,6 +146,9 @@
}
static id positionToJson(GMSCameraPosition* position) {
+ if (!position) {
+ return nil;
+ }
return @{
@"target" : locationToJson([position target]),
@"zoom" : @([position zoom]),
@@ -338,6 +341,10 @@
NSString* snippet = (infoWindowTextData[1] == [NSNull null]) ? nil : infoWindowTextData[1];
[sink setInfoWindowTitle:title snippet:snippet];
}
+ id position = data[@"position"];
+ if (position) {
+ [sink setPosition:toLocation(position)];
+ }
id rotation = data[@"rotation"];
if (rotation) {
[sink setRotation:toDouble(rotation)];
diff --git a/packages/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/lib/google_maps_flutter.dart
index f07c568..8f98368 100644
--- a/packages/google_maps_flutter/lib/google_maps_flutter.dart
+++ b/packages/google_maps_flutter/lib/google_maps_flutter.dart
@@ -18,7 +18,7 @@
part 'src/callbacks.dart';
part 'src/camera.dart';
part 'src/controller.dart';
-part 'src/ui.dart';
part 'src/marker.dart';
part 'src/location.dart';
part 'src/platform_overlay.dart';
+part 'src/ui.dart';
diff --git a/packages/google_maps_flutter/lib/src/callbacks.dart b/packages/google_maps_flutter/lib/src/callbacks.dart
index eb2ab86..d8e906f 100644
--- a/packages/google_maps_flutter/lib/src/callbacks.dart
+++ b/packages/google_maps_flutter/lib/src/callbacks.dart
@@ -11,10 +11,17 @@
///
/// Additions and removals happening during a single [call] invocation do not
/// change who gets a callback until the next such invocation.
+///
+/// Optimized for the singleton case.
class ArgumentCallbacks<T> {
final List<ArgumentCallback<T>> _callbacks = <ArgumentCallback<T>>[];
- VoidCallback _onEmptyChanged;
+ /// Callback method. Invokes the corresponding method on each callback
+ /// in this collection.
+ ///
+ /// The list of callbacks being invoked is computed at the start of the
+ /// method and is unaffected by any changes subsequently made to this
+ /// collection.
void call(T argument) {
final int length = _callbacks.length;
if (length == 1) {
@@ -27,18 +34,22 @@
}
}
+ /// Adds a callback to this collection.
void add(ArgumentCallback<T> callback) {
+ assert(callback != null);
_callbacks.add(callback);
- if (_onEmptyChanged != null && _callbacks.length == 1) _onEmptyChanged();
}
+ /// Removes a callback from this collection.
+ ///
+ /// Does nothing, if the callback was not present.
void remove(ArgumentCallback<T> callback) {
- final bool removed = _callbacks.remove(callback);
- if (_onEmptyChanged != null && removed && _callbacks.isEmpty)
- _onEmptyChanged();
+ _callbacks.remove(callback);
}
+ /// Whether this collection is empty.
bool get isEmpty => _callbacks.isEmpty;
+ /// Whether this collection is non-empty.
bool get isNotEmpty => _callbacks.isNotEmpty;
}
diff --git a/packages/google_maps_flutter/lib/src/camera.dart b/packages/google_maps_flutter/lib/src/camera.dart
index e105980..3362a27 100644
--- a/packages/google_maps_flutter/lib/src/camera.dart
+++ b/packages/google_maps_flutter/lib/src/camera.dart
@@ -4,6 +4,9 @@
part of google_maps_flutter;
+/// The position of the map "camera", the view point from which the world is
+/// shown in the map view. Aggregates the camera's [target] geographical
+/// location, its [zoom] level, [tilt] angle, and [bearing].
class CameraPosition {
const CameraPosition({
this.bearing = 0.0,
@@ -15,9 +18,37 @@
assert(tilt != null),
assert(zoom != null);
+ /// The camera's bearing in degrees, measured clockwise from north.
+ ///
+ /// A bearing of 0.0, the default, means the camera points north.
+ /// A bearing of 90.0 means the camera points east.
final double bearing;
+
+ /// The geographical location that the camera is pointing at.
final LatLng target;
+
+ /// The angle, in degrees, of the camera angle from the nadir.
+ ///
+ /// A tilt of 0.0, the default and minimum supported value, means the camera
+ /// is directly facing the Earth.
+ ///
+ /// The maximum tilt value depends on the current zoom level. Values beyond
+ /// the supported range are allowed, but on applying them to a map they will
+ /// be silently clamped to the supported range.
final double tilt;
+
+ /// The zoom level of the camera.
+ ///
+ /// A zoom of 0.0, the default, means the screen width of the world is 256.
+ /// Adding 1.0 to the zoom level doubles the screen width of the map. So at
+ /// zoom level 3.0, the screen width of the world is 2³x256=2048.
+ ///
+ /// Larger zoom levels thus means the camera is placed closer to the surface
+ /// of the Earth, revealing more detail in a narrower geographical region.
+ ///
+ /// The supported zoom level range depends on the map data and device. Values
+ /// beyond the supported range are allowed, but on applying them to a map they
+ /// will be silently clamped to the supported range.
final double zoom;
dynamic _toJson() => <String, dynamic>{
@@ -40,21 +71,28 @@
}
}
+/// Defines a camera move, supporting absolute moves as well as moves relative
+/// the current position.
class CameraUpdate {
CameraUpdate._(this._json);
- final dynamic _json;
-
+ /// Returns a camera update that moves the camera to the specified position.
static CameraUpdate newCameraPosition(CameraPosition cameraPosition) {
return new CameraUpdate._(
<dynamic>['newCameraPosition', cameraPosition._toJson()],
);
}
+ /// Returns a camera update that moves the camera target to the specified
+ /// geographical location.
static CameraUpdate newLatLng(LatLng latLng) {
return new CameraUpdate._(<dynamic>['newLatLng', latLng._toJson()]);
}
+ /// Returns a camera update that transforms the camera so that the specified
+ /// geographical bounding box is centered in the map view at the greatest
+ /// possible zoom level. A non-zero [padding] insets the bounding box from the
+ /// map view's edges. The camera's new tilt and bearing will both be 0.0.
static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) {
return new CameraUpdate._(<dynamic>[
'newLatLngBounds',
@@ -63,18 +101,29 @@
]);
}
+ /// Returns a camera update that moves the camera target to the specified
+ /// geographical location and zoom level.
static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) {
return new CameraUpdate._(
<dynamic>['newLatLngZoom', latLng._toJson(), zoom],
);
}
+ /// Returns a camera update that moves the camera target the specified screen
+ /// distance.
+ ///
+ /// For a camera with bearing 0.0 (pointing north), scrolling by 50,75 moves
+ /// the camera's target to a geographical location that is 50 to the east and
+ /// 75 to the south of the current location, measured in screen coordinates.
static CameraUpdate scrollBy(double dx, double dy) {
return new CameraUpdate._(
<dynamic>['scrollBy', dx, dy],
);
}
+ /// Returns a camera update that modifies the camera zoom level by the
+ /// specified amount. The optional [focus] is a screen point whose underlying
+ /// geographical location should be invariant, if possible, by the movement.
static CameraUpdate zoomBy(double amount, [Offset focus]) {
if (focus == null) {
return new CameraUpdate._(<dynamic>['zoomBy', amount]);
@@ -87,17 +136,28 @@
}
}
+ /// Returns a camera update that zooms the camera in, bringing the camera
+ /// closer to the surface of the Earth.
+ ///
+ /// Equivalent to the result of calling `zoomBy(1.0)`.
static CameraUpdate zoomIn() {
return new CameraUpdate._(<dynamic>['zoomIn']);
}
+ /// Returns a camera update that zooms the camera out, bringing the camera
+ /// further away from the surface of the Earth.
+ ///
+ /// Equivalent to the result of calling `zoomBy(-1.0)`.
static CameraUpdate zoomOut() {
return new CameraUpdate._(<dynamic>['zoomOut']);
}
+ /// Returns a camera update that sets the camera zoom level.
static CameraUpdate zoomTo(double zoom) {
return new CameraUpdate._(<dynamic>['zoomTo', zoom]);
}
+ final dynamic _json;
+
dynamic _toJson() => _json;
}
diff --git a/packages/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/lib/src/controller.dart
index dd5d8fd..032766c 100644
--- a/packages/google_maps_flutter/lib/src/controller.dart
+++ b/packages/google_maps_flutter/lib/src/controller.dart
@@ -7,25 +7,26 @@
final MethodChannel _channel =
const MethodChannel('plugins.flutter.io/google_maps');
-/// Controller for a single GoogleMap instance.
-///
-/// Used for programmatically controlling a platform-specific GoogleMap view.
+/// Controller for a single GoogleMap instance running on the host platform.
///
/// Change listeners are notified upon changes to any of
///
-/// * the [options] property,
+/// * the [options] property
/// * the collection of [Marker]s added to this map
-/// * the [cameraPosition] property,
+/// * the [isCameraMoving] property
+/// * the [cameraPosition] property
///
-/// Listeners are notified when changes have been applied on the platform side.
+/// Listeners are notified after changes have been applied on the platform side.
///
/// Marker tap events can be received by adding callbacks to [onMarkerTapped].
class GoogleMapController extends ChangeNotifier {
- GoogleMapController._({
- this.id,
- GoogleMapOptions options,
- }) : _options = options {
- id.then((int id) {
+ @visibleForTesting
+ GoogleMapController(this._id, GoogleMapOptions options)
+ : assert(_id != null),
+ assert(options != null),
+ assert(options.cameraPosition != null),
+ _options = options {
+ _id.then((int id) {
_controllers[id] = this;
});
if (options.trackCameraPosition) {
@@ -33,9 +34,7 @@
}
}
- /// An ID identifying the GoogleMaps instance, once created.
- final Future<int> id;
-
+ /// Callbacks to receive tap events for markers placed on this map.
final ArgumentCallbacks<Marker> onMarkerTapped =
new ArgumentCallbacks<Marker>();
@@ -44,9 +43,13 @@
GoogleMapOptions get options => _options;
GoogleMapOptions _options;
+ /// The current set of markers on this map.
+ ///
+ /// The returned set will be a detached snapshot of the markers collection.
Set<Marker> get markers => new Set<Marker>.from(_markers.values);
final Map<String, Marker> _markers = <String, Marker>{};
+ /// True if the map camera is currently moving.
bool get isCameraMoving => _isCameraMoving;
bool _isCameraMoving = false;
@@ -56,11 +59,18 @@
CameraPosition get cameraPosition => _cameraPosition;
CameraPosition _cameraPosition;
+ final Future<int> _id;
+
static Map<int, GoogleMapController> _controllers =
<int, GoogleMapController>{};
+ /// Initializes the GoogleMaps plugin. Should be called from the Flutter
+ /// application's main entry point.
+ // Clears any existing platform-side map instances after hot restart.
+ // Sets up method call handlers for receiving map events.
static Future<void> init() async {
await _channel.invokeMethod('init');
+ _controllers.clear();
_channel.setMethodCallHandler((MethodCall call) {
final int mapId = call.arguments['map'];
final GoogleMapController controller = _controllers[mapId];
@@ -79,15 +89,15 @@
onMarkerTapped(marker);
}
break;
- case 'map#onCameraMoveStarted':
+ case 'camera#onMoveStarted':
_isCameraMoving = true;
notifyListeners();
break;
- case 'map#onCameraMove':
+ case 'camera#onMove':
_cameraPosition = CameraPosition._fromJson(call.arguments['position']);
notifyListeners();
break;
- case 'map#onCameraIdle':
+ case 'camera#onIdle':
_isCameraMoving = false;
notifyListeners();
break;
@@ -96,108 +106,149 @@
}
}
- Future<void> updateMapOptions(GoogleMapOptions options) async {
- assert(options != null);
- final int id = await this.id;
- await _channel.invokeMethod('updateMapOptions', <String, dynamic>{
- 'map': id,
- 'options': options._toJson(),
- });
- _options = _options._updateWith(options);
- if (!_options.trackCameraPosition) {
- _cameraPosition = null;
- }
+ /// Updates configuration options of the map user interface.
+ ///
+ /// Change listeners are notified once the update has been made on the
+ /// platform side.
+ ///
+ /// The returned [Future] completes after listeners have been notified.
+ Future<void> updateMapOptions(GoogleMapOptions changes) async {
+ assert(changes != null);
+ final int id = await _id;
+ final dynamic json = await _channel.invokeMethod(
+ 'map#update',
+ <String, dynamic>{
+ 'map': id,
+ 'options': changes._toJson(),
+ },
+ );
+ _options = _options.copyWith(changes);
+ _cameraPosition = CameraPosition._fromJson(json);
notifyListeners();
}
+ /// Starts an animated change of the map camera position.
+ ///
+ /// The returned [Future] completes after the change has been started on the
+ /// platform side.
Future<void> animateCamera(CameraUpdate cameraUpdate) async {
- final int id = await this.id;
- await _channel.invokeMethod('animateCamera', <String, dynamic>{
+ final int id = await _id;
+ await _channel.invokeMethod('camera#animate', <String, dynamic>{
'map': id,
'cameraUpdate': cameraUpdate._toJson(),
});
}
+ /// Changes the map camera position.
+ ///
+ /// The returned [Future] completes after the change has been made on the
+ /// platform side.
Future<void> moveCamera(CameraUpdate cameraUpdate) async {
- final int id = await this.id;
- await _channel.invokeMethod('moveCamera', <String, dynamic>{
+ final int id = await _id;
+ await _channel.invokeMethod('camera#move', <String, dynamic>{
'map': id,
'cameraUpdate': cameraUpdate._toJson(),
});
}
+ /// Adds a marker to the map, configured using the specified custom [options].
+ ///
+ /// Change listeners are notified once the marker has been added on the
+ /// platform side.
+ ///
+ /// The returned [Future] completes with the added marker once listeners have
+ /// been notified.
Future<Marker> addMarker(MarkerOptions options) async {
- assert(options != null);
- assert(options.position != null);
- final int id = await this.id;
+ final int id = await _id;
final MarkerOptions effectiveOptions =
- MarkerOptions.defaultOptions._updateWith(options);
+ MarkerOptions.defaultOptions.copyWith(options);
final String markerId = await _channel.invokeMethod(
- 'addMarker',
+ 'marker#add',
<String, dynamic>{
'map': id,
'options': effectiveOptions._toJson(),
},
);
- final Marker marker = new Marker._(this, markerId, effectiveOptions);
+ final Marker marker = new Marker(markerId, effectiveOptions);
_markers[markerId] = marker;
notifyListeners();
return marker;
}
- Future<void> _updateMarker(Marker marker, MarkerOptions changes) async {
- assert(_markers[marker.id] == marker);
+ /// Updates the specified [marker] with the given [changes]. The marker must
+ /// be a current member of the [markers] set.
+ ///
+ /// Change listeners are notified once the marker has been updated on the
+ /// platform side.
+ ///
+ /// The returned [Future] completes once listeners have been notified.
+ Future<void> updateMarker(Marker marker, MarkerOptions changes) async {
+ assert(marker != null);
+ assert(_markers[marker._id] == marker);
assert(changes != null);
- final int id = await this.id;
+ final int id = await _id;
await _channel.invokeMethod('marker#update', <String, dynamic>{
'map': id,
- 'marker': marker.id,
+ 'marker': marker._id,
'options': changes._toJson(),
});
- marker._options = marker._options._updateWith(changes);
+ marker._options = marker._options.copyWith(changes);
notifyListeners();
}
- Future<void> _removeMarker(Marker marker) async {
- assert(_markers[marker.id] == marker);
- final int id = await this.id;
+ /// Removes the specified [marker] from the map. The marker must be a current
+ /// member of the [markers] set.
+ ///
+ /// Change listeners are notified once the marker has been removed on the
+ /// platform side.
+ ///
+ /// The returned [Future] completes once listeners have been notified.
+ Future<void> removeMarker(Marker marker) async {
+ assert(marker != null);
+ assert(_markers[marker._id] == marker);
+ final int id = await _id;
await _channel.invokeMethod('marker#remove', <String, dynamic>{
'map': id,
- 'marker': marker.id,
+ 'marker': marker._id,
});
- _markers.remove(marker.id);
+ _markers.remove(marker._id);
notifyListeners();
}
}
-/// Controller for a GoogleMap instance that is integrated as a
+/// Controller pair for a GoogleMap instance that is integrated as a
/// platform overlay.
///
+/// The [mapController] programmatically controls the platform GoogleMap view
+/// and supports event handling.
+///
+/// The [overlayController] is used to hide and show the platform overlay at
+/// appropriate times to avoid rendering artifacts when the necessary conditions
+/// for correctly displaying a platform overlay are not met: the underlying
+/// widget must be stationary and rendered on top of all other widgets within
+/// bounds.
+///
/// *Warning*: Platform overlays cannot be freely composed with
/// other widgets. See [PlatformOverlayController] for caveats and
/// limitations.
class GoogleMapOverlayController {
GoogleMapOverlayController._(this.mapController, this.overlayController);
- /// Creates a controller for a GoogleMaps of the specified size in
- /// logical pixels.
+ /// Creates a controller for a GoogleMaps of the specified size and with the
+ /// specified custom [options], if any.
factory GoogleMapOverlayController.fromSize({
@required double width,
@required double height,
- GoogleMapOptions options = const GoogleMapOptions(),
+ GoogleMapOptions options,
}) {
assert(width != null);
assert(height != null);
- assert(options != null);
final GoogleMapOptions effectiveOptions =
- GoogleMapOptions.defaultOptions._updateWith(options);
+ GoogleMapOptions.defaultOptions.copyWith(options);
final _GoogleMapsPlatformOverlay overlay =
new _GoogleMapsPlatformOverlay(effectiveOptions);
return new GoogleMapOverlayController._(
- new GoogleMapController._(
- id: overlay._textureId.future,
- options: effectiveOptions,
- ),
+ new GoogleMapController(overlay._textureId.future, effectiveOptions),
new PlatformOverlayController(width, height, overlay),
);
}
@@ -221,7 +272,7 @@
@override
Future<int> create(Size size) {
- _textureId.complete(_channel.invokeMethod('createMap', <String, dynamic>{
+ _textureId.complete(_channel.invokeMethod('map#create', <String, dynamic>{
'width': size.width,
'height': size.height,
'options': options._toJson(),
@@ -232,7 +283,7 @@
@override
Future<void> show(Offset offset) async {
final int id = await _textureId.future;
- _channel.invokeMethod('showMapOverlay', <String, dynamic>{
+ _channel.invokeMethod('map#show', <String, dynamic>{
'map': id,
'x': offset.dx,
'y': offset.dy,
@@ -242,7 +293,7 @@
@override
Future<void> hide() async {
final int id = await _textureId.future;
- _channel.invokeMethod('hideMapOverlay', <String, dynamic>{
+ _channel.invokeMethod('map#hide', <String, dynamic>{
'map': id,
});
}
@@ -250,13 +301,25 @@
@override
Future<void> dispose() async {
final int id = await _textureId.future;
- _channel.invokeMethod('disposeMap', <String, dynamic>{
+ _channel.invokeMethod('map#dispose', <String, dynamic>{
'map': id,
});
}
}
-/// A Widget covered by a GoogleMaps platform overlay.
+/// A widget covered by a GoogleMap platform overlay.
+///
+/// The overlay is intended to be shown only while the map is interactive,
+/// stationary, and the widget is rendered on top of all other widgets. In all
+/// other situations, the overlay should be hidden to avoid rendering artifacts.
+/// While the overlay is hidden, the widget shows a Texture with the most recent
+/// bitmap snapshot extracted from the GoogleMap view. That bitmap will be
+/// slightly delayed compared to the actual platform view which will be visible,
+/// if a map animation is started and the overlay then hidden.
+///
+/// *Warning*: Platform overlays cannot be freely composed with
+/// other widgets. See [PlatformOverlayController] for caveats and
+/// limitations.
class GoogleMapOverlay extends StatefulWidget {
final GoogleMapOverlayController controller;
@@ -283,7 +346,7 @@
Widget build(BuildContext context) {
return new SizedBox(
child: new FutureBuilder<int>(
- future: widget.controller.mapController.id,
+ future: widget.controller.mapController._id,
builder: (_, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData) {
return new Texture(textureId: snapshot.data);
diff --git a/packages/google_maps_flutter/lib/src/location.dart b/packages/google_maps_flutter/lib/src/location.dart
index f459d70..9a2b4a1 100644
--- a/packages/google_maps_flutter/lib/src/location.dart
+++ b/packages/google_maps_flutter/lib/src/location.dart
@@ -6,12 +6,25 @@
/// A pair of latitude and longitude coordinates, stored as degrees.
class LatLng {
+ /// The latitude in degrees between -90.0 and 90.0, both inclusive.
final double latitude;
+
+ /// The longitude in degrees between -180.0 (inclusive) and 180.0 (exclusive).
final double longitude;
- const LatLng(this.latitude, this.longitude)
+ /// Creates a geographical location specified in degrees [latitude] and
+ /// [longitude].
+ ///
+ /// The latitude is clamped to the inclusive interval from -90.0 to +90.0.
+ ///
+ /// The longitude is normalized to the half-open interval from -180.0
+ /// (inclusive) to +180.0 (exclusive)
+ const LatLng(double latitude, double longitude)
: assert(latitude != null),
- assert(longitude != null);
+ assert(longitude != null),
+ latitude =
+ (latitude < -90.0 ? -90.0 : (90.0 < latitude ? 90.0 : latitude)),
+ longitude = (longitude + 180.0) % 360.0 - 180.0;
dynamic _toJson() {
return <double>[latitude, longitude];
@@ -39,13 +52,28 @@
}
/// A latitude/longitude aligned rectangle.
+///
+/// The rectangle conceptually includes all points (lat, lng) where
+/// * lat ∈ [`southwest.latitude`, `northeast.latitude`]
+/// * lng ∈ [`southwest.longitude`, `northeast.longitude`],
+/// if `southwest.longitude` ≤ `northeast.longitude`,
+/// * lng ∈ [-180, `northeast.longitude`] ∪ [`southwest.longitude`, 180[,
+/// if `northeast.longitude` < `southwest.longitude`
class LatLngBounds {
+ /// The southwest corner of the rectangle.
final LatLng southwest;
+
+ /// The northeast corner of the rectangle.
final LatLng northeast;
- const LatLngBounds({@required this.southwest, @required this.northeast})
+ /// Creates geographical bounding box with the specified corners.
+ ///
+ /// The latitude of the southwest corner cannot be larger than the
+ /// latitude of the northeast corner.
+ LatLngBounds({@required this.southwest, @required this.northeast})
: assert(southwest != null),
- assert(northeast != null);
+ assert(northeast != null),
+ assert(southwest.latitude <= northeast.latitude);
dynamic _toJson() {
return <dynamic>[southwest._toJson(), northeast._toJson()];
diff --git a/packages/google_maps_flutter/lib/src/marker.dart b/packages/google_maps_flutter/lib/src/marker.dart
index 4552cf8..c830adc 100644
--- a/packages/google_maps_flutter/lib/src/marker.dart
+++ b/packages/google_maps_flutter/lib/src/marker.dart
@@ -4,32 +4,25 @@
part of google_maps_flutter;
-/// An icon placed at a particular point on the map's surface. A marker icon is
-/// drawn oriented against the device's screen rather than the map's surface;
-/// that is, it will not necessarily change orientation due to map rotations,
-/// tilting, or zooming.
+/// An icon placed at a particular geographical location on the map's surface.
+/// A marker icon is drawn oriented against the device's screen rather than the
+/// map's surface; that is, it will not necessarily change orientation due to
+/// map rotations, tilting, or zooming.
///
-/// Markers are owned by a single [GoogleMapController] which fires change
-/// events when markers are added, updated, or removed.
+/// Markers are owned by a single [GoogleMapController] which fires events
+/// as markers are added, updated, tapped, and removed.
class Marker {
- Marker._(this._mapController, this.id, this._options);
+ @visibleForTesting
+ Marker(this._id, this._options);
- final GoogleMapController _mapController;
- final String id;
+ final String _id;
MarkerOptions _options;
- Future<void> remove() {
- return _mapController._removeMarker(this);
- }
-
- Future<void> update(MarkerOptions changes) {
- return _mapController._updateMarker(this, changes);
- }
-
- /// The configuration options most recently applied programmatically.
+ /// The marker configuration options most recently applied programmatically
+ /// via the map controller.
///
/// The returned value does not reflect any changes made to the marker through
- /// touch events. Add listeners to track those.
+ /// touch events. Add listeners to the owning map controller to track those.
MarkerOptions get options => _options;
}
@@ -44,9 +37,17 @@
class InfoWindowText {
const InfoWindowText(this.title, this.snippet);
+ /// Text labels specifying that no text is to be displayed.
static const InfoWindowText noText = const InfoWindowText(null, null);
+ /// Text displayed in an info window when the user taps the marker.
+ ///
+ /// A null value means no title.
final String title;
+
+ /// Additional text displayed below the [title].
+ ///
+ /// A null value means no additional text.
final String snippet;
dynamic _toJson() => <dynamic>[title, snippet];
@@ -55,21 +56,66 @@
/// Configuration options for [Marker] instances.
///
/// When used to change configuration, null values will be interpreted as
-/// "do not change this configuration item".
+/// "do not change this configuration option".
class MarkerOptions {
+ /// The opacity of the marker, between 0.0 and 1.0 inclusive.
+ ///
+ /// 0.0 means fully transparent, 1.0 means fully opaque.
final double alpha;
+
+ /// The icon image point that will be placed at the [position] of the marker.
+ ///
+ /// The image point is specified in normalized coordinates: An anchor of
+ /// (0.0, 0.0) means the top left corner of the image. An anchor
+ /// of (1.0, 1.0) means the bottom right corner of the image.
final Offset anchor;
+
+ /// True if the marker icon consumes tap events. If not, the map will perform
+ /// default tap handling by centering the map on the marker and displaying its
+ /// info window.
final bool consumeTapEvents;
+
+ /// True if the marker is draggable by user touch events.
final bool draggable;
+
+ /// True if the marker is rendered flatly against the surface of the Earth, so
+ /// that it will rotate and tilt along with map camera movements.
final bool flat;
+
+ /// A description of the bitmap used to draw the marker icon.
final BitmapDescriptor icon;
+
+ /// The icon image point that will be the anchor of the info window when
+ /// displayed.
+ ///
+ /// The image point is specified in normalized coordinates: An anchor of
+ /// (0.0, 0.0) means the top left corner of the image. An anchor
+ /// of (1.0, 1.0) means the bottom right corner of the image.
final Offset infoWindowAnchor;
+
+ /// Text content for the info window.
final InfoWindowText infoWindowText;
+
+ /// Geographical location of the marker.
final LatLng position;
+
+ /// Rotation of the marker image in degrees clockwise from the [anchor] point.
final double rotation;
+
+ /// True if the marker is visible.
final bool visible;
+
+ /// The z-index of the marker, used to determine relative drawing order of
+ /// map overlays.
+ ///
+ /// Overlays are drawn in order of z-index, so that lower values means drawn
+ /// earlier, and thus appearing to be closer to the surface of the Earth.
final double zIndex;
+ /// Creates a set of marker configuration options.
+ ///
+ /// By default, every non-specified field is null, meaning no desire to change
+ /// marker defaults or current configuration.
const MarkerOptions({
this.alpha,
this.anchor,
@@ -83,9 +129,24 @@
this.rotation,
this.visible,
this.zIndex,
- });
+ }) : assert(alpha == null || (0.0 <= alpha && alpha <= 1.0));
- static const MarkerOptions defaultOptions = const MarkerOptions(
+ /// Default marker options.
+ ///
+ /// Specifies a marker that
+ /// * is fully opaque; [alpha] is 1.0
+ /// * uses icon bottom center to indicate map position; [anchor] is (0.5, 1.0)
+ /// * has default tap handling; [consumeTapEvents] is false
+ /// * is stationary; [draggable] is false
+ /// * is drawn against the screen, not the map; [flat] is false
+ /// * has a default icon; [icon] is `BitmapDescriptor.defaultMarker`
+ /// * anchors the info window at top center; [infoWindowAnchor] is (0.5, 0.0)
+ /// * has no info window text; [infoWindowText] is `InfoWindowText.noText`
+ /// * is positioned at 0, 0; [position] is `LatLng(0.0, 0.0)`
+ /// * has an axis-aligned icon; [rotation] is 0.0
+ /// * is visible; [visible] is true
+ /// * is placed at the base of the drawing order; [zIndex] is 0.0
+ static const MarkerOptions defaultOptions = MarkerOptions(
alpha: 1.0,
anchor: const Offset(0.5, 1.0),
consumeTapEvents: false,
@@ -94,12 +155,20 @@
icon: BitmapDescriptor.defaultMarker,
infoWindowAnchor: const Offset(0.5, 0.0),
infoWindowText: InfoWindowText.noText,
+ position: LatLng(0.0, 0.0),
rotation: 0.0,
visible: true,
zIndex: 0.0,
);
- MarkerOptions _updateWith(MarkerOptions changes) {
+ /// Creates a new options object whose values are the same as this instance,
+ /// unless overwritten by the specified [changes].
+ ///
+ /// Returns this instance, if [changes] is null.
+ MarkerOptions copyWith(MarkerOptions changes) {
+ if (changes == null) {
+ return this;
+ }
return new MarkerOptions(
alpha: changes.alpha ?? alpha,
anchor: changes.anchor ?? anchor,
diff --git a/packages/google_maps_flutter/lib/src/platform_overlay.dart b/packages/google_maps_flutter/lib/src/platform_overlay.dart
index f56d4bb..5f61e30 100644
--- a/packages/google_maps_flutter/lib/src/platform_overlay.dart
+++ b/packages/google_maps_flutter/lib/src/platform_overlay.dart
@@ -4,25 +4,48 @@
part of google_maps_flutter;
-/// Controller of platform overlays, supporting a very limited form
-/// of compositing with Flutter Widgets.
+/// Controller of platform overlays used for creating the illusion, in *very
+/// limited situations*, of in-line compositing of platform views with Flutter
+/// widgets.
///
-/// Platform overlays are normal platform-specific views that are
-/// created, shown on top of the Flutter view, or hidden below it,
-/// under control of the Flutter app. The platform overlay is
-/// typically placed on top of a [Texture] widget acting as stand-in
-/// while Flutter movement or transformations are ongoing.
+/// Platform overlays are normal platform views that are displayed on top of the
+/// Flutter view when so directed by the Flutter app's Dart code. The platform
+/// overlay is placed on top of a [Texture] widget acting as a non-interactive
+/// stand in while the conditions for correctly displaying the overlay are not
+/// met. Those conditions are:
///
-/// Overlays are attached to a [BuildContext] when used in a Widget and
-/// are deactivated when the ambient ModalRoute (if any) is not on top of the
-/// navigator stack.
+/// * the widget must be stationary
+/// * the widget must be rendered on top of all other widgets within bounds
+/// * touch events originating within the widget's bounds can be safely ignored
+/// by Flutter code (they will be intercepted by the platform overlay)
///
-/// *Warning*: Platform overlays cannot be freely composed with
-/// over widgets.
+/// These conditions severely restrict the contexts in which platform overlays
+/// can be used. Worse, there is no easy way of learning if a given widget
+/// currently satisfies those conditions, so they must be explicitly enforced
+/// by the app author. Examples include avoiding placing the widget on a
+/// scrollable view; hiding the overlay during animated transitions or while a
+/// drawer is being shown on top; avoiding placing the widget at the edge of
+/// the screen where the platform view would interfere with edge swipes; etc.
+/// The app author should expect little help from existing widgets in this
+/// endeavor; some widgets (Material Scaffold being a prime example) do not
+/// offer to notify clients before and after they display Flutter overlays or
+/// animate to new configurations. Using platform overlays may require custom
+/// implementations of such widgets.
///
-/// Limitations and caveats:
+/// *Warning*: Platform overlays cannot be freely composed with other widgets.
///
-/// * TODO(mravn)
+/// For the above reasons, *the use of platform overlays is generally
+/// discouraged*. Still, overlays provide an interim solution in situations
+/// where one wants to create the illusion of in-line compositing of native
+/// platform views (such as GoogleMaps) for which no API exists for connecting a
+/// Texture widget directly to the native OpenGL rendering pipeline.
+///
+/// Overlays may be attached to the [BuildContext] in which the Texture widget
+/// is built and are then automatically hidden when the ambient ModalRoute (if
+/// any) is not on top of the navigator stack. This is currently the *only*
+/// built-in mechanism for helping the app author ensure that the overlay
+/// conditions mentioned above are met. Making use of this mechanism requires
+/// the overlay controller to be added as an observer of the main Navigator.
class PlatformOverlayController extends NavigatorObserver
with WidgetsBindingObserver {
final double width;
@@ -184,11 +207,6 @@
}
}
- @override
- void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
- // TODO(mravn)
- }
-
void dispose() {
if (!_disposed) {
overlay.dispose();
@@ -204,12 +222,14 @@
}
}
-/// Platform overlay.
+/// Interface of platform overlay implementations. Typical implementation use
+/// a [MethodChannel] to communicate with platform-specific code and have it
+/// manage a collection of related platform overlays.
abstract class PlatformOverlay {
/// Creates a platform view of the specified [size].
///
/// The platform view should remain hidden until explicitly shown by calling
- /// [showOverlay].
+ /// [show].
Future<int> create(Size size);
/// Shows the platform view at the specified [offset].
diff --git a/packages/google_maps_flutter/lib/src/ui.dart b/packages/google_maps_flutter/lib/src/ui.dart
index 496d45b..fcc870e 100644
--- a/packages/google_maps_flutter/lib/src/ui.dart
+++ b/packages/google_maps_flutter/lib/src/ui.dart
@@ -5,41 +5,61 @@
part of google_maps_flutter;
/// Type of map tiles to display.
-///
-/// Enum constants must be indexed to match the corresponding int constants of
-/// the platform APIs, see
-/// <https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.html#MAP_TYPE_NORMAL>
+// Enum constants must be indexed to match the corresponding int constants of
+// the Android platform API, see
+// <https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.html#MAP_TYPE_NORMAL>
enum MapType {
+ /// Do not display map tiles.
none,
+
+ /// Normal tiles (traffic and labels, subtle terrain information).
normal,
+
+ /// Satellite imaging tiles (aerial photos)
satellite,
+
+ /// Terrain tiles (indicates type and height of terrain)
terrain,
+
+ /// Hybrid tiles (satellite images with some labels/overlays)
hybrid,
}
/// Bounds for the map camera target.
+// Used with [GoogleMapOptions] to wrap a [LatLngBounds] value. This allows
+// distinguishing between specifying an unbounded target (null `LatLngBounds`)
+// from not specifying anything (null `CameraTargetBounds`).
class CameraTargetBounds {
+ /// Creates a camera target bounds with the specified bounding box, or null
+ /// to indicate that the camera target is not bounded.
const CameraTargetBounds(this.bounds);
- /// The current bounds or null, if the camera target is unbounded.
+ /// The geographical bounding box for the map camera target.
+ ///
+ /// A null value means the camera target is unbounded.
final LatLngBounds bounds;
+ /// Unbounded camera target.
static const CameraTargetBounds unbounded = const CameraTargetBounds(null);
dynamic _toJson() => <dynamic>[bounds?._toJson()];
}
/// Preferred bounds for map camera zoom level.
+// Used with [GoogleMapOptions] to wrap min and max zoom. This allows
+// distinguishing between specifying unbounded zooming (null `minZoom` and
+// `maxZoom`) from not specifying anything (null `MinMaxZoomPreference`).
class MinMaxZoomPreference {
const MinMaxZoomPreference(this.minZoom, this.maxZoom)
: assert(minZoom == null || maxZoom == null || minZoom <= maxZoom);
- /// The current minimum zoom level or null, if unbounded from below.
+ /// The preferred minimum zoom level or null, if unbounded from below.
final double minZoom;
- /// The current maximum zoom level or null, if unbounded from above.
+ /// The preferred maximum zoom level or null, if unbounded from above.
final double maxZoom;
+ /// Unbounded zooming.
static const MinMaxZoomPreference unbounded =
const MinMaxZoomPreference(null, null);
@@ -49,20 +69,50 @@
/// Configuration options for the GoogleMaps user interface.
///
/// When used to change configuration, null values will be interpreted as
-/// "do not change this configuration item".
+/// "do not change this configuration option".
class GoogleMapOptions {
+ /// The desired position of the map camera.
+ ///
+ /// This field is used to indicate initial camera position and to update that
+ /// position programmatically along with other changes to the map user
+ /// interface. It does not track the camera position through animations or
+ /// reflect movements caused by user touch events.
final CameraPosition cameraPosition;
+
+ /// True if the map should show a compass when rotated.
final bool compassEnabled;
+
+ /// Geographical bounding box for the camera target.
final CameraTargetBounds cameraTargetBounds;
+
+ /// Type of map tiles to be rendered.
final MapType mapType;
+
+ /// Preferred bounds for the camera zoom level.
+ ///
+ /// Actual bounds depend on map data and device.
final MinMaxZoomPreference minMaxZoomPreference;
+
+ /// True if the map view should respond to rotate gestures.
final bool rotateGesturesEnabled;
+
+ /// True if the map view should respond to scroll gestures.
final bool scrollGesturesEnabled;
+
+ /// True if the map view should respond to tilt gestures.
final bool tiltGesturesEnabled;
+
+ /// True if the map view should relay camera move events to Flutter.
final bool trackCameraPosition;
+
+ /// True if the map view should respond to zoom gestures.
final bool zoomGesturesEnabled;
- const GoogleMapOptions({
+ /// Creates a set of map user interface configuration options.
+ ///
+ /// By default, every non-specified field is null, meaning no desire to change
+ /// user interface defaults or current configuration.
+ GoogleMapOptions({
this.cameraPosition,
this.compassEnabled,
this.cameraTargetBounds,
@@ -75,8 +125,22 @@
this.zoomGesturesEnabled,
});
- static const GoogleMapOptions defaultOptions = const GoogleMapOptions(
+ /// Default user interface options.
+ ///
+ /// Specifies a map view that
+ /// * displays a compass when rotated; [compassEnabled] is true
+ /// * positions the camera at 0,0; [cameraPosition] has target `LatLng(0.0, 0.0)`
+ /// * does not bound the camera target; [cameraTargetBounds] is `CameraTargetBounds.unbounded`
+ /// * uses normal map tiles; [mapType] is `MapType.normal`
+ /// * does not bound zooming; [minMaxZoomPreference] is `MinMaxZoomPreference.unbounded`
+ /// * responds to rotate gestures; [rotateGesturesEnabled] is true
+ /// * responds to scroll gestures; [scrollGesturesEnabled] is true
+ /// * responds to tilt gestures; [tiltGesturesEnabled] is true
+ /// * is silent about camera movement; [trackCameraPosition] is false
+ /// * responds to zoom gestures; [zoomGesturesEnabled] is true
+ static final GoogleMapOptions defaultOptions = GoogleMapOptions(
compassEnabled: true,
+ cameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)),
cameraTargetBounds: CameraTargetBounds.unbounded,
mapType: MapType.normal,
minMaxZoomPreference: MinMaxZoomPreference.unbounded,
@@ -87,7 +151,14 @@
zoomGesturesEnabled: true,
);
- GoogleMapOptions _updateWith(GoogleMapOptions change) {
+ /// Creates a new options object whose values are the same as this instance,
+ /// unless overwritten by the specified [changes].
+ ///
+ /// Returns this instance, if [changes] is null.
+ GoogleMapOptions copyWith(GoogleMapOptions change) {
+ if (change == null) {
+ return this;
+ }
return new GoogleMapOptions(
cameraPosition: change.cameraPosition ?? cameraPosition,
compassEnabled: change.compassEnabled ?? compassEnabled,