From 89954bd091c6bcc317fd38ff9cc4addc92f5b1d2 Mon Sep 17 00:00:00 2001 From: Evo Stamatov Date: Thu, 29 Mar 2018 15:00:24 +1100 Subject: [PATCH 1/4] accept a custom MapState, so users can control the map --- flutter_map/lib/flutter_map.dart | 40 ++++++++++++++++++++++++++++---- flutter_map/lib/src/map/map.dart | 34 ++++++++++++++++----------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/flutter_map/lib/flutter_map.dart b/flutter_map/lib/flutter_map.dart index f9b630765..267b5fc2b 100644 --- a/flutter_map/lib/flutter_map.dart +++ b/flutter_map/lib/flutter_map.dart @@ -14,12 +14,31 @@ export 'src/layer/polyline_layer.dart'; export 'src/map/map.dart'; class FlutterMap extends StatefulWidget { + /// A set of layers' options to used to create the layers on the map + /// + /// Usually a list of [TileLayerOptions], [MarkerLayerOptions] and + /// [PolylineLayerOptions]. final List layers; + + /// [MapOptions] to create a [MapState] with + /// + /// Please note: If both [options] and [mapState] are set, mapState's options + /// will take precedence, but the [:onTap:] callback of the options will be + /// used! final MapOptions options; - FlutterMap({this.options, this.layers}); - State createState() { - return new _FlutterMapState(); - } + + /// A [MapState], used to control the map + final MapState mapState; + + FlutterMap({ + Key key, + this.options, + this.layers, + this.mapState, + }) + : super(key: key); + + _FlutterMapState createState() => new _FlutterMapState(); } class _FlutterMapState extends MapGestureMixin { @@ -28,7 +47,18 @@ class _FlutterMapState extends MapGestureMixin { initState() { super.initState(); - mapState = new MapState(options); + mapState = widget.mapState ?? new MapState(options); + } + + void didUpdateWidget(FlutterMap oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.mapState != oldWidget.mapState) { + final MapState newMapState = widget.mapState ?? new MapState(options); + if (newMapState == mapState) return; + if (mapState != null) mapState.dispose(); + mapState = newMapState; + } } Widget build(BuildContext context) { diff --git a/flutter_map/lib/src/map/map.dart b/flutter_map/lib/src/map/map.dart index 5b1482f11..b1710a464 100644 --- a/flutter_map/lib/src/map/map.dart +++ b/flutter_map/lib/src/map/map.dart @@ -38,7 +38,9 @@ class MapState { final MapOptions options; final StreamController _onMoveSink; - double zoom; + double _zoom; + double get zoom => _zoom; + LatLng _lastCenter; Point _pixelOrigin; bool _initialized = false; @@ -52,7 +54,7 @@ class MapState { Point get size => _size; set size(Point s) { _size = s; - _pixelOrigin = getNewPixelOrigin(this._lastCenter); + _pixelOrigin = getNewPixelOrigin(_lastCenter); if (!_initialized) { _init(); _initialized = true; @@ -62,18 +64,22 @@ class MapState { LatLng get center => getCenter() ?? options.center; void _init() { - this.zoom = options.zoom; + _zoom = options.zoom; move(options.center, zoom); } + void dispose() { + _onMoveSink.close(); + } + void move(LatLng center, double zoom, [data]) { if (zoom == null) { - zoom = this.zoom; + zoom = _zoom; } - this.zoom = zoom; - this._lastCenter = center; - this._pixelOrigin = this.getNewPixelOrigin(center); + _zoom = zoom; + _lastCenter = center; + _pixelOrigin = getNewPixelOrigin(center); _onMoveSink.add(null); } @@ -86,14 +92,14 @@ class MapState { Point project(LatLng latlng, [double zoom]) { if (zoom == null) { - zoom = this.zoom; + zoom = _zoom; } return options.crs.latLngToPoint(latlng, zoom); } LatLng unproject(Point point, [double zoom]) { if (zoom == null) { - zoom = this.zoom; + zoom = _zoom; } return options.crs.pointToLatLng(point, zoom); } @@ -107,13 +113,13 @@ class MapState { } double getZoomScale(double toZoom, double fromZoom) { - var crs = this.options.crs; - fromZoom = fromZoom == null ? this.zoom : fromZoom; + var crs = options.crs; + fromZoom = fromZoom == null ? _zoom : fromZoom; return crs.scale(toZoom) / crs.scale(fromZoom); } Bounds getPixelWorldBounds(double zoom) { - return options.crs.getProjectedBounds(zoom == null ? this.zoom : zoom); + return options.crs.getProjectedBounds(zoom == null ? _zoom : zoom); } Point getPixelOrigin() { @@ -121,7 +127,7 @@ class MapState { } Point getNewPixelOrigin(LatLng center, [double zoom]) { - var viewHalf = this.size / 2; - return (this.project(center, zoom) - viewHalf).round(); + var viewHalf = _size / 2; + return (project(center, zoom) - viewHalf).round(); } } From 434189dc918f978638e78d3f822874e7fe507499 Mon Sep 17 00:00:00 2001 From: John Ryan Date: Wed, 18 Apr 2018 10:01:37 -0700 Subject: [PATCH 2/4] switch MapState MapController to avoid exposing MapState publicly --- flutter_map/lib/flutter_map.dart | 60 +++++++++++++++---- flutter_map/lib/src/gestures/gestures.dart | 1 + flutter_map/lib/src/layer/marker_layer.dart | 1 + flutter_map/lib/src/layer/polyline_layer.dart | 1 + flutter_map/lib/src/map/map.dart | 37 +++--------- 5 files changed, 58 insertions(+), 42 deletions(-) diff --git a/flutter_map/lib/flutter_map.dart b/flutter_map/lib/flutter_map.dart index 267b5fc2b..4c2affc26 100644 --- a/flutter_map/lib/flutter_map.dart +++ b/flutter_map/lib/flutter_map.dart @@ -4,14 +4,15 @@ import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/geo/crs/crs.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; import 'package:flutter_map/src/map/map.dart'; +import 'package:latlong/latlong.dart'; export 'src/layer/layer.dart'; export 'src/layer/tile_layer.dart'; export 'src/layer/marker_layer.dart'; export 'src/layer/polyline_layer.dart'; -export 'src/map/map.dart'; class FlutterMap extends StatefulWidget { /// A set of layers' options to used to create the layers on the map @@ -27,37 +28,70 @@ class FlutterMap extends StatefulWidget { /// used! final MapOptions options; - /// A [MapState], used to control the map - final MapState mapState; + /// A [MapController], used to control the map + final MapControllerImpl _mapController; FlutterMap({ Key key, this.options, this.layers, - this.mapState, - }) - : super(key: key); + MapController mapController, + }) : _mapController = mapController ?? new MapController(), + super(key: key); _FlutterMapState createState() => new _FlutterMapState(); } +abstract class MapController { + /// Moves the map to a specific location and zoom level + void move(LatLng center, double zoom); + + factory MapController() => new MapControllerImpl(); +} + +typedef TapCallback(LatLng point); + +class MapOptions { + final Crs crs; + final double zoom; + final double minZoom; + final double maxZoom; + final List layers; + final bool debug; + final bool interactive; + final TapCallback onTap; + LatLng center; + + MapOptions({ + this.crs: const Epsg3857(), + this.center, + this.zoom = 13.0, + this.minZoom, + this.maxZoom, + this.layers, + this.debug = false, + this.interactive = true, + this.onTap, + }) { + if (center == null) center = new LatLng(50.5, 30.51); + } +} + class _FlutterMapState extends MapGestureMixin { - MapOptions get options => widget.options; + MapOptions get options => widget.options ?? new MapOptions(); MapState mapState; initState() { super.initState(); - mapState = widget.mapState ?? new MapState(options); + mapState = new MapState(options); + widget._mapController.state = mapState; } void didUpdateWidget(FlutterMap oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.mapState != oldWidget.mapState) { - final MapState newMapState = widget.mapState ?? new MapState(options); - if (newMapState == mapState) return; - if (mapState != null) mapState.dispose(); - mapState = newMapState; + if (widget._mapController != oldWidget._mapController) { + widget._mapController.state = mapState; } } diff --git a/flutter_map/lib/src/gestures/gestures.dart b/flutter_map/lib/src/gestures/gestures.dart index 07c216e28..24446128f 100644 --- a/flutter_map/lib/src/gestures/gestures.dart +++ b/flutter_map/lib/src/gestures/gestures.dart @@ -3,6 +3,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/map/map.dart'; import 'package:latlong/latlong.dart'; import 'package:flutter_map/flutter_map.dart'; diff --git a/flutter_map/lib/src/layer/marker_layer.dart b/flutter_map/lib/src/layer/marker_layer.dart index c81b2a253..1124991c0 100644 --- a/flutter_map/lib/src/layer/marker_layer.dart +++ b/flutter_map/lib/src/layer/marker_layer.dart @@ -1,4 +1,5 @@ import 'package:flutter/widgets.dart'; +import 'package:flutter_map/src/map/map.dart'; import 'package:latlong/latlong.dart'; import 'package:flutter_map/flutter_map.dart'; diff --git a/flutter_map/lib/src/layer/polyline_layer.dart b/flutter_map/lib/src/layer/polyline_layer.dart index e9a544ea2..62313fe1b 100644 --- a/flutter_map/lib/src/layer/polyline_layer.dart +++ b/flutter_map/lib/src/layer/polyline_layer.dart @@ -1,4 +1,5 @@ import 'package:flutter/widgets.dart'; +import 'package:flutter_map/src/map/map.dart'; import 'package:latlong/latlong.dart'; import 'package:flutter_map/flutter_map.dart'; import 'dart:ui'; diff --git a/flutter_map/lib/src/map/map.dart b/flutter_map/lib/src/map/map.dart index b1710a464..e45f9fdc3 100644 --- a/flutter_map/lib/src/map/map.dart +++ b/flutter_map/lib/src/map/map.dart @@ -4,37 +4,16 @@ import 'package:latlong/latlong.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/core/bounds.dart'; import 'package:flutter_map/src/core/point.dart'; -import 'package:flutter_map/src/geo/crs/crs.dart'; - -typedef TapCallback(LatLng point); - -class MapOptions { - final Crs crs; - final double zoom; - final double minZoom; - final double maxZoom; - final List layers; - final bool debug; - final bool interactive; - final TapCallback onTap; - LatLng center; - - MapOptions({ - this.crs: const Epsg3857(), - this.center, - this.zoom = 13.0, - this.minZoom, - this.maxZoom, - this.layers, - this.debug = false, - this.interactive = true, - this.onTap, - }) { - if (center == null) center = new LatLng(50.5, 30.51); + +class MapControllerImpl implements MapController { + MapState state; + + void move(LatLng center, double zoom) { + state.move(center, zoom); } } -class MapState { +class MapState { final MapOptions options; final StreamController _onMoveSink; @@ -72,7 +51,7 @@ class MapState { _onMoveSink.close(); } - void move(LatLng center, double zoom, [data]) { + void move(LatLng center, double zoom) { if (zoom == null) { zoom = _zoom; } From 8ffad40da6aba76dbac3bb04f7c1bb4c4a87989a Mon Sep 17 00:00:00 2001 From: John Ryan Date: Wed, 18 Apr 2018 10:01:45 -0700 Subject: [PATCH 3/4] add example for MapController --- flutter_map_example/lib/main.dart | 119 +++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 3 deletions(-) diff --git a/flutter_map_example/lib/main.dart b/flutter_map_example/lib/main.dart index 870257486..2ed7320fc 100644 --- a/flutter_map_example/lib/main.dart +++ b/flutter_map_example/lib/main.dart @@ -18,6 +18,7 @@ class MyApp extends StatelessWidget { TapToAddPage.route: (context) => new TapToAddPage(), EsriPage.route: (context) => new EsriPage(), PolylinePage.route: (context) => new PolylinePage(), + MapControllerPage.route: (context) => new MapControllerPage(), }, ); } @@ -221,9 +222,8 @@ class PolylinePage extends StatelessWidget { polylines: [ new Polyline( points: points, - strokeWidth: 4.0, - color: Colors.purple - ), + strokeWidth: 4.0, + color: Colors.purple), ], ) ], @@ -282,11 +282,124 @@ Drawer _buildDrawer(BuildContext context, String currentRoute) { Navigator.popAndPushNamed(context, PolylinePage.route); }, ), + new ListTile( + title: const Text('MapController'), + selected: currentRoute == MapControllerPage.route, + onTap: () { + Navigator.popAndPushNamed(context, MapControllerPage.route); + }, + ), ], ), ); } +class MapControllerPage extends StatefulWidget { + static const String route = 'map_controller'; + + @override + MapControllerPageState createState() { + return new MapControllerPageState(); + } +} + +class MapControllerPageState extends State { + static LatLng london = new LatLng(51.5, -0.09); + static LatLng paris = new LatLng(48.8566, 2.3522); + static LatLng dublin = new LatLng(53.3498, -6.2603); + + MapController mapController; + + void initState() { + super.initState(); + mapController = new MapController(); + } + + Widget build(BuildContext context) { + var markers = [ + new Marker( + width: 80.0, + height: 80.0, + point: london, + builder: (ctx) => new Container( + child: new FlutterLogo(), + ), + ), + new Marker( + width: 80.0, + height: 80.0, + point: dublin, + builder: (ctx) => new Container( + child: new FlutterLogo( + colors: Colors.green, + ), + ), + ), + new Marker( + width: 80.0, + height: 80.0, + point: paris, + builder: (ctx) => new Container( + child: new FlutterLogo(colors: Colors.purple), + ), + ), + ]; + + return new Scaffold( + appBar: new AppBar(title: new Text("MapController")), + drawer: _buildDrawer(context, MapControllerPage.route), + body: new Padding( + padding: new EdgeInsets.all(8.0), + child: new Column( + children: [ + new Padding( + padding: new EdgeInsets.only(top: 8.0, bottom: 8.0), + child: new Row( + children: [ + new MaterialButton( + child: new Text("London"), + onPressed: () { + mapController.move(london, 5.0); + }, + ), + new MaterialButton( + child: new Text("Paris"), + onPressed: () { + mapController.move(paris, 5.0); + }, + ), + new MaterialButton( + child: new Text("Dublin"), + onPressed: () { + mapController.move(dublin, 5.0); + }, + ), + ], + ), + ), + new Flexible( + child: new FlutterMap( + mapController: mapController, + options: new MapOptions( + center: new LatLng(51.5, -0.09), + zoom: 5.0, + ), + layers: [ + new TileLayerOptions( + urlTemplate: + "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", + subdomains: ['a', 'b', 'c']), + new MarkerLayerOptions(markers: markers) + ], + ), + ), + ], + ), + ), + ); + } +} + // Generated using Material Design Palette/Theme Generator // http://mcg.mbitson.com/ // https://github.com/mbitson/mcg From 426cf9dac5e90fb7f1718d823f01b518272be34a Mon Sep 17 00:00:00 2001 From: John Ryan Date: Wed, 18 Apr 2018 10:07:45 -0700 Subject: [PATCH 4/4] move flutter map state into src/ --- flutter_map/lib/flutter_map.dart | 58 +------------------ .../lib/src/map/flutter_map_state.dart | 52 +++++++++++++++++ 2 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 flutter_map/lib/src/map/flutter_map_state.dart diff --git a/flutter_map/lib/flutter_map.dart b/flutter_map/lib/flutter_map.dart index 4c2affc26..b87733c2c 100644 --- a/flutter_map/lib/flutter_map.dart +++ b/flutter_map/lib/flutter_map.dart @@ -3,9 +3,8 @@ library leaflet_flutter; import 'package:flutter/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/core/point.dart'; import 'package:flutter_map/src/geo/crs/crs.dart'; -import 'package:flutter_map/src/gestures/gestures.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong/latlong.dart'; @@ -13,6 +12,7 @@ export 'src/layer/layer.dart'; export 'src/layer/tile_layer.dart'; export 'src/layer/marker_layer.dart'; export 'src/layer/polyline_layer.dart'; +export 'src/geo/crs/crs.dart'; class FlutterMap extends StatefulWidget { /// A set of layers' options to used to create the layers on the map @@ -39,7 +39,7 @@ class FlutterMap extends StatefulWidget { }) : _mapController = mapController ?? new MapController(), super(key: key); - _FlutterMapState createState() => new _FlutterMapState(); + FlutterMapState createState() => new FlutterMapState(_mapController); } abstract class MapController { @@ -76,55 +76,3 @@ class MapOptions { if (center == null) center = new LatLng(50.5, 30.51); } } - -class _FlutterMapState extends MapGestureMixin { - MapOptions get options => widget.options ?? new MapOptions(); - MapState mapState; - - initState() { - super.initState(); - mapState = new MapState(options); - widget._mapController.state = mapState; - } - - void didUpdateWidget(FlutterMap oldWidget) { - super.didUpdateWidget(oldWidget); - - if (widget._mapController != oldWidget._mapController) { - widget._mapController.state = mapState; - } - } - - Widget build(BuildContext context) { - return new LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - mapState.size = - new Point(constraints.maxWidth, constraints.maxHeight); - var layerWidgets = widget.layers.map(_createLayer).toList(); - return new GestureDetector( - onScaleStart: handleScaleStart, - onScaleUpdate: handleScaleUpdate, - onScaleEnd: handleScaleEnd, - onTapUp: handleTapUp, - child: new Container( - child: new Stack( - children: layerWidgets, - ), - ), - ); - }); - } - - Widget _createLayer(LayerOptions options) { - if (options is TileLayerOptions) { - return new TileLayer(options: options, mapState: mapState); - } - if (options is MarkerLayerOptions) { - return new MarkerLayer(options, mapState); - } - if (options is PolylineLayerOptions) { - return new PolylineLayer(options, mapState); - } - return null; - } -} diff --git a/flutter_map/lib/src/map/flutter_map_state.dart b/flutter_map/lib/src/map/flutter_map_state.dart new file mode 100644 index 000000000..f13e21ee3 --- /dev/null +++ b/flutter_map/lib/src/map/flutter_map_state.dart @@ -0,0 +1,52 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/core/point.dart'; +import 'package:flutter_map/src/gestures/gestures.dart'; +import 'package:flutter_map/src/map/map.dart'; + +class FlutterMapState extends MapGestureMixin { + final MapControllerImpl mapController; + MapOptions get options => widget.options ?? new MapOptions(); + MapState mapState; + + FlutterMapState(this.mapController); + + initState() { + super.initState(); + mapState = new MapState(options); + mapController.state = mapState; + } + + Widget build(BuildContext context) { + return new LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + mapState.size = + new Point(constraints.maxWidth, constraints.maxHeight); + var layerWidgets = widget.layers.map(_createLayer).toList(); + return new GestureDetector( + onScaleStart: handleScaleStart, + onScaleUpdate: handleScaleUpdate, + onScaleEnd: handleScaleEnd, + onTapUp: handleTapUp, + child: new Container( + child: new Stack( + children: layerWidgets, + ), + ), + ); + }); + } + + Widget _createLayer(LayerOptions options) { + if (options is TileLayerOptions) { + return new TileLayer(options: options, mapState: mapState); + } + if (options is MarkerLayerOptions) { + return new MarkerLayer(options, mapState); + } + if (options is PolylineLayerOptions) { + return new PolylineLayer(options, mapState); + } + return null; + } +}