Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions flutter_map/lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -20,7 +21,8 @@ class FlutterMap extends StatefulWidget {
}
}

class _FlutterMapState extends State<FlutterMap> {
class _FlutterMapState extends State<FlutterMap>
with MapGestureMixin, SingleTickerProviderStateMixin {
MapOptions get options => widget.options;
MapState mapState;

Expand All @@ -34,9 +36,16 @@ class _FlutterMapState extends State<FlutterMap> {
builder: (BuildContext context, BoxConstraints constraints) {
mapState.size =
new Point<double>(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,
),
),
);
});
Expand Down
130 changes: 130 additions & 0 deletions flutter_map/lib/src/gestures/gestures.dart
Original file line number Diff line number Diff line change
@@ -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<FlutterMap>
with SingleTickerProviderStateMixin {
static const double _kMinFlingVelocity = 800.0;

LatLng _mapCenterStart;
double _mapZoomStart;
Point _focalPointStart;

AnimationController _controller;
Animation<Offset> _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<Offset>(
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);
}
}
106 changes: 5 additions & 101 deletions flutter_map/lib/src/layer/tile_layer.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -43,8 +42,7 @@ class TileLayer extends StatefulWidget {
}
}

class _TileLayerState extends State<TileLayer>
with SingleTickerProviderStateMixin {
class _TileLayerState extends State<TileLayer> {
MapState get map => widget.mapState;
TileLayerOptions get options => widget.options;
Bounds _globalTileRange;
Expand All @@ -59,8 +57,6 @@ class _TileLayerState extends State<TileLayer>
void initState() {
super.initState();
_resetView();
_controller = new AnimationController(vsync: this)
..addListener(_handleFlingAnimation);
}

String getTileUrl(Coords coords) {
Expand Down Expand Up @@ -273,106 +269,14 @@ class _TileLayerState extends State<TileLayer>
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<Offset> _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<Offset>(
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);
Expand Down
15 changes: 11 additions & 4 deletions flutter_map/lib/src/map/map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ 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<LayerOptions> layers;
final bool debug;
final bool interactive;
final TapCallback onTap;
LatLng center;

MapOptions({
Expand All @@ -23,24 +27,27 @@ class MapOptions {
this.maxZoom,
this.layers,
this.debug = false,
this.interactive = true,
this.onTap,
}) {
if (center == null) center = new LatLng(50.5, 30.51);
}
}

class MapState {
final MapOptions options;
final StreamController<Null> _onMovedSink;
final StreamController<Null> _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<Null> get onMoved => _onMovedSink.stream;
Stream<Null> get onMoved => _onMoveSink.stream;

Point get size => _size;
set size(Point s) {
Expand All @@ -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() {
Expand Down