diff --git a/flutter_map/lib/flutter_map.dart b/flutter_map/lib/flutter_map.dart index f9b630765..b87733c2c 100644 --- a/flutter_map/lib/flutter_map.dart +++ b/flutter_map/lib/flutter_map.dart @@ -3,64 +3,76 @@ 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/gestures/gestures.dart'; +import 'package:flutter_map/src/geo/crs/crs.dart'; +import 'package:flutter_map/src/map/flutter_map_state.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'; +export 'src/geo/crs/crs.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 [MapController], used to control the map + final MapControllerImpl _mapController; + + FlutterMap({ + Key key, + this.options, + this.layers, + MapController mapController, + }) : _mapController = mapController ?? new MapController(), + super(key: key); + + FlutterMapState createState() => new FlutterMapState(_mapController); } -class _FlutterMapState extends MapGestureMixin { - MapOptions get options => widget.options; - MapState mapState; +abstract class MapController { + /// Moves the map to a specific location and zoom level + void move(LatLng center, double zoom); - initState() { - super.initState(); - mapState = new MapState(options); - } + factory MapController() => new MapControllerImpl(); +} - 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, - ), - ), - ); - }); - } +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; - 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; + 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); } } diff --git a/flutter_map/lib/src/gestures/gestures.dart b/flutter_map/lib/src/gestures/gestures.dart index bf4970e90..e83444308 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 2855c4be8..4bc525f61 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 c1473da98..ada5fe798 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/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; + } +} diff --git a/flutter_map/lib/src/map/map.dart b/flutter_map/lib/src/map/map.dart index 2997caff6..a9e4d53a2 100644 --- a/flutter_map/lib/src/map/map.dart +++ b/flutter_map/lib/src/map/map.dart @@ -4,41 +4,22 @@ 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; - double zoom; + double _zoom; + double get zoom => _zoom; + LatLng _lastCenter; Point _pixelOrigin; bool _initialized = false; @@ -52,7 +33,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 +43,22 @@ class MapState { LatLng get center => getCenter() ?? options.center; void _init() { - this.zoom = options.zoom; + _zoom = options.zoom; move(options.center, zoom); } - void move(LatLng center, double zoom, [dynamic data]) { + void dispose() { + _onMoveSink.close(); + } + + void move(LatLng center, double zoom) { 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 +71,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 +92,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() { 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