From 3b01ae715b7ed6457355d4b831a9c950563c9b2b Mon Sep 17 00:00:00 2001 From: John Ryan Date: Wed, 21 Feb 2018 09:14:31 -0800 Subject: [PATCH] move gesture detection into map widget --- flutter_map/lib/flutter_map.dart | 17 ++- flutter_map/lib/src/gestures/gestures.dart | 130 +++++++++++++++++++++ flutter_map/lib/src/layer/tile_layer.dart | 106 +---------------- flutter_map/lib/src/map/map.dart | 15 ++- 4 files changed, 159 insertions(+), 109 deletions(-) create mode 100644 flutter_map/lib/src/gestures/gestures.dart diff --git a/flutter_map/lib/flutter_map.dart b/flutter_map/lib/flutter_map.dart index 85ba72fd1..e4bfa8dc9 100644 --- a/flutter_map/lib/flutter_map.dart +++ b/flutter_map/lib/flutter_map.dart @@ -4,6 +4,7 @@ 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/map/map.dart'; export 'src/layer/layer.dart'; @@ -20,7 +21,8 @@ class FlutterMap extends StatefulWidget { } } -class _FlutterMapState extends State { +class _FlutterMapState extends State + with MapGestureMixin, SingleTickerProviderStateMixin { MapOptions get options => widget.options; MapState mapState; @@ -34,9 +36,16 @@ class _FlutterMapState extends State { builder: (BuildContext context, BoxConstraints constraints) { mapState.size = new Point(constraints.maxWidth, constraints.maxHeight); - return new Container( - child: new Stack( - children: widget.layers.map(_createLayer).toList(), + 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, + ), ), ); }); diff --git a/flutter_map/lib/src/gestures/gestures.dart b/flutter_map/lib/src/gestures/gestures.dart new file mode 100644 index 000000000..07c216e28 --- /dev/null +++ b/flutter_map/lib/src/gestures/gestures.dart @@ -0,0 +1,130 @@ +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:latlong/latlong.dart'; +import 'package:flutter_map/flutter_map.dart'; + +abstract class MapGestureMixin extends State + with SingleTickerProviderStateMixin { + static const double _kMinFlingVelocity = 800.0; + + LatLng _mapCenterStart; + double _mapZoomStart; + Point _focalPointStart; + + AnimationController _controller; + Animation _flingAnimation; + Offset _animationOffset = Offset.zero; + + FlutterMap get widget; + MapState get mapState; + MapState get map => mapState; + MapOptions get options; + + void initState() { + super.initState(); + _controller = new AnimationController(vsync: this) + ..addListener(_handleFlingAnimation); + } + + void handleScaleStart(ScaleStartDetails details) { + setState(() { + _mapZoomStart = map.zoom; + _mapCenterStart = map.center; + + // Get the widget's offset + var renderObject = context.findRenderObject() as RenderBox; + var boxOffset = renderObject.localToGlobal(Offset.zero); + + // determine the focal point within the widget + var localFocalPoint = _offsetToPoint(details.focalPoint - boxOffset); + _focalPointStart = localFocalPoint; + + _controller.stop(); + }); + } + + void handleScaleUpdate(ScaleUpdateDetails details) { + setState(() { + var dScale = details.scale; + for (var i = 0; i < 2; i++) { + dScale = math.sqrt(dScale); + } + var renderObject = context.findRenderObject() as RenderBox; + var boxOffset = renderObject.localToGlobal(Offset.zero); + + // Draw the focal point + var localFocalPoint = _offsetToPoint(details.focalPoint - boxOffset); + + // get the focal point in global coordinates + var dFocalPoint = localFocalPoint - _focalPointStart; + + var focalCenterDistance = localFocalPoint - (map.size / 2); + var newCenter = map.project(_mapCenterStart) + + focalCenterDistance.multiplyBy(1 - 1 / dScale) - + dFocalPoint; + + var offsetPt = newCenter - map.project(_mapCenterStart); + _animationOffset = _pointToOffset(offsetPt); + + var newZoom = _mapZoomStart * dScale; + map.move(map.unproject(newCenter), newZoom); + }); + } + + void handleScaleEnd(ScaleEndDetails details) { + final double magnitude = details.velocity.pixelsPerSecond.distance; + if (magnitude < _kMinFlingVelocity) return; + final Offset direction = details.velocity.pixelsPerSecond / magnitude; + final double distance = (Offset.zero & context.size).shortestSide; + _flingAnimation = new Tween( + begin: _animationOffset, + end: _animationOffset - direction * distance) + .animate(_controller); + _controller + ..value = 0.0 + ..fling(velocity: magnitude / 1000.0); + } + + void handleTapUp(TapUpDetails details) { + if (options.onTap == null) { + return; + } + // Get the widget's offset + var renderObject = context.findRenderObject() as RenderBox; + var boxOffset = renderObject.localToGlobal(Offset.zero); + var width = renderObject.size.width; + var height = renderObject.size.height; + + // convert the point to global coordinates + var localPoint = _offsetToPoint(details.globalPosition - boxOffset); + var localPointCenterDistance = + new Point((width / 2) - localPoint.x, (height / 2) - localPoint.y); + var mapCenter = map.project(map.center); + var point = mapCenter - localPointCenterDistance; + var latlng = map.unproject(point); + + // emit the event + options.onTap(latlng); + } + + void _handleFlingAnimation() { + setState(() { + _animationOffset = _flingAnimation.value; + var newCenterPoint = map.project(_mapCenterStart) + + new Point(_animationOffset.dx, _animationOffset.dy); + var newCenter = map.unproject(newCenterPoint); + map.move(newCenter, map.zoom); + }); + } + + Point _offsetToPoint(Offset offset) { + return new Point(offset.dx, offset.dy); + } + + Offset _pointToOffset(Point point) { + return new Offset(point.x, point.y); + } +} diff --git a/flutter_map/lib/src/layer/tile_layer.dart b/flutter_map/lib/src/layer/tile_layer.dart index 34e9acefb..df6f3b9b8 100644 --- a/flutter_map/lib/src/layer/tile_layer.dart +++ b/flutter_map/lib/src/layer/tile_layer.dart @@ -1,6 +1,5 @@ import 'dart:typed_data'; -import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:latlong/latlong.dart'; @@ -43,8 +42,7 @@ class TileLayer extends StatefulWidget { } } -class _TileLayerState extends State - with SingleTickerProviderStateMixin { +class _TileLayerState extends State { MapState get map => widget.mapState; TileLayerOptions get options => widget.options; Bounds _globalTileRange; @@ -59,8 +57,6 @@ class _TileLayerState extends State void initState() { super.initState(); _resetView(); - _controller = new AnimationController(vsync: this) - ..addListener(_handleFlingAnimation); } String getTileUrl(Coords coords) { @@ -273,106 +269,14 @@ class _TileLayerState extends State tileWidgets.add(_createTileWidget(tile.coords)); } - return new GestureDetector( - onScaleStart: _handleScaleStart, - onScaleUpdate: _handleScaleUpdate, - onScaleEnd: _handleScaleEnd, - child: new Container( - child: new Stack( - children: tileWidgets, - ), - color: Colors.grey[300], + return new Container( + child: new Stack( + children: tileWidgets, ), + color: Colors.grey[300], ); } - Point _offsetToPoint(Offset offset) { - return new Point(offset.dx, offset.dy); - } - - Offset _pointToOffset(Point point) { - return new Offset(point.x, point.y); - } - - LatLng _mapCenterStart; - double _mapZoomStart; - Point _focalPointStart; - - Offset _animationOffset = Offset.zero; - - void _handleScaleStart(ScaleStartDetails details) { - setState(() { - _mapZoomStart = map.zoom; - _mapCenterStart = map.center; - - // Get the widget's offset - var renderObject = context.findRenderObject() as RenderBox; - var boxOffset = renderObject.localToGlobal(Offset.zero); - - // determine the focal point within the widget - var localFocalPoint = _offsetToPoint(details.focalPoint - boxOffset); - _focalPointStart = localFocalPoint; - - _controller.stop(); - }); - } - - void _handleScaleUpdate(ScaleUpdateDetails details) { - setState(() { - var dScale = details.scale; - for (var i = 0; i < 2; i++) { - dScale = math.sqrt(dScale); - } - var renderObject = context.findRenderObject() as RenderBox; - var boxOffset = renderObject.localToGlobal(Offset.zero); - - // Draw the focal point - var localFocalPoint = _offsetToPoint(details.focalPoint - boxOffset); - - // get the focal point in global coordinates - var dFocalPoint = localFocalPoint - _focalPointStart; - - var focalCenterDistance = localFocalPoint - (map.size / 2); - var newCenter = map.project(_mapCenterStart) + - focalCenterDistance.multiplyBy(1 - 1 / dScale) - - dFocalPoint; - - var offsetPt = newCenter - map.project(_mapCenterStart); - _animationOffset = _pointToOffset(offsetPt); - - var newZoom = _mapZoomStart * dScale; - map.move(map.unproject(newCenter), newZoom); - }); - } - - AnimationController _controller; - Animation _flingAnimation; - static const double _kMinFlingVelocity = 800.0; - - void _handleScaleEnd(ScaleEndDetails details) { - final double magnitude = details.velocity.pixelsPerSecond.distance; - if (magnitude < _kMinFlingVelocity) return; - final Offset direction = details.velocity.pixelsPerSecond / magnitude; - final double distance = (Offset.zero & context.size).shortestSide; - _flingAnimation = new Tween( - begin: _animationOffset, - end: _animationOffset - direction * distance) - .animate(_controller); - _controller - ..value = 0.0 - ..fling(velocity: magnitude / 1000.0); - } - - void _handleFlingAnimation() { - setState(() { - _animationOffset = _flingAnimation.value; - var newCenterPoint = map.project(_mapCenterStart) + - new Point(_animationOffset.dx, _animationOffset.dy); - var newCenter = map.unproject(newCenterPoint); - map.move(newCenter, map.zoom); - }); - } - Bounds _getTiledPixelBounds(LatLng center) { var mapZoom = map.zoom; var scale = map.getZoomScale(mapZoom, this._tileZoom); diff --git a/flutter_map/lib/src/map/map.dart b/flutter_map/lib/src/map/map.dart index d9db21e98..3a162d763 100644 --- a/flutter_map/lib/src/map/map.dart +++ b/flutter_map/lib/src/map/map.dart @@ -6,6 +6,8 @@ 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; @@ -13,6 +15,8 @@ class MapOptions { final double maxZoom; final List layers; final bool debug; + final bool interactive; + final TapCallback onTap; LatLng center; MapOptions({ @@ -23,6 +27,8 @@ class MapOptions { this.maxZoom, this.layers, this.debug = false, + this.interactive = true, + this.onTap, }) { if (center == null) center = new LatLng(50.5, 30.51); } @@ -30,17 +36,18 @@ class MapOptions { class MapState { final MapOptions options; - final StreamController _onMovedSink; + final StreamController _onMoveSink; + double zoom; LatLng _lastCenter; Point _pixelOrigin; bool _initialized = false; - MapState(this.options) : _onMovedSink = new StreamController.broadcast(); + MapState(this.options) : _onMoveSink = new StreamController.broadcast(); Point _size; - Stream get onMoved => _onMovedSink.stream; + Stream get onMoved => _onMoveSink.stream; Point get size => _size; set size(Point s) { @@ -66,7 +73,7 @@ class MapState { this.zoom = zoom; this._lastCenter = center; this._pixelOrigin = this.getNewPixelOrigin(center); - _onMovedSink.add(null); + _onMoveSink.add(null); } LatLng getCenter() {