Skip to content
3 changes: 3 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flutter_map_example/pages/debouncing_tile_update_transformer.dar
import 'package:flutter_map_example/pages/epsg3996_crs.dart';
import 'package:flutter_map_example/pages/epsg4326_crs.dart';
import 'package:flutter_map_example/pages/fallback_url_page.dart';
import 'package:flutter_map_example/pages/fling_animation_damping.dart';
import 'package:flutter_map_example/pages/home.dart';
import 'package:flutter_map_example/pages/interactive_test_page.dart';
import 'package:flutter_map_example/pages/latlng_to_screen_point.dart';
Expand Down Expand Up @@ -79,6 +80,8 @@ class MyApp extends StatelessWidget {
TileBuilderPage.route: (context) => const TileBuilderPage(),
ErrorTileBuilder.route: (context) => const ErrorTileBuilder(),
InteractiveFlagsPage.route: (context) => const InteractiveFlagsPage(),
FlingAnimationDampingPage.route: (context) =>
const FlingAnimationDampingPage(),
ManyMarkersPage.route: (context) => const ManyMarkersPage(),
MapInsideListViewPage.route: (context) => const MapInsideListViewPage(),
ResetTileLayerPage.route: (context) => const ResetTileLayerPage(),
Expand Down
129 changes: 129 additions & 0 deletions example/lib/pages/fling_animation_damping.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_example/misc/tile_providers.dart';
import 'package:flutter_map_example/widgets/drawer/menu_drawer.dart';
import 'package:latlong2/latlong.dart';

class FlingAnimationDampingPage extends StatefulWidget {
static const String route = '/fling_animation_damping';

const FlingAnimationDampingPage({super.key});

@override
State<FlingAnimationDampingPage> createState() =>
_FlingAnimationDampingPageState();
}

class _FlingAnimationDampingPageState extends State<FlingAnimationDampingPage> {
double _dampingRatio = 2;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Fling Animation Damping')),
drawer: const MenuDrawer(FlingAnimationDampingPage.route),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Damping Ratio: ${_dampingRatio.toStringAsFixed(1)}',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
const Text(
'Drag the map and release to see the fling animation. '
'Lower values = less momentum, stops quicker. '
'Higher values = more momentum, bouncier feel. ',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 12),
),
const SizedBox(height: 8),
Row(
children: [
const Text('Damped (1)'),
Expanded(
child: Slider(
value: _dampingRatio,
min: 1,
max: 10,
divisions: 19,
label: _dampingRatio.toStringAsFixed(1),
onChanged: (value) {
setState(() {
_dampingRatio = value;
});
},
),
),
const Text('Damped (10)'),
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.center,
children: [
ElevatedButton(
onPressed: () => setState(() => _dampingRatio = 1),
child: const Text('Very Damped (1)'),
),
ElevatedButton(
onPressed: () => setState(() => _dampingRatio = 2),
child: const Text('Damped (2)'),
),
ElevatedButton(
onPressed: () => setState(() => _dampingRatio = 5),
child: const Text('Default (5)'),
),
ElevatedButton(
onPressed: () => setState(() => _dampingRatio = 7),
child: const Text('Bouncy (4)'),
),
ElevatedButton(
onPressed: () => setState(() => _dampingRatio = 10),
child: const Text('Very Bouncy (10)'),
),
],
),
],
),
),
Expanded(
child: FlutterMap(
options: MapOptions(
initialCenter: const LatLng(51.5, -0.09),
initialZoom: 11,
interactionOptions: InteractionOptions(
flags: InteractiveFlag.all,
flingAnimationDampingRatio: _dampingRatio,
),
),
children: [
openStreetMapTileLayer,
Center(
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(4),
),
child: const Text(
'Drag and release to see the fling effect!',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
],
),
),
],
),
);
}
}
6 changes: 6 additions & 0 deletions example/lib/widgets/drawer/menu_drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:flutter_map_example/pages/debouncing_tile_update_transformer.dar
import 'package:flutter_map_example/pages/epsg3996_crs.dart';
import 'package:flutter_map_example/pages/epsg4326_crs.dart';
import 'package:flutter_map_example/pages/fallback_url_page.dart';
import 'package:flutter_map_example/pages/fling_animation_damping.dart';
import 'package:flutter_map_example/pages/home.dart';
import 'package:flutter_map_example/pages/interactive_test_page.dart';
import 'package:flutter_map_example/pages/latlng_to_screen_point.dart';
Expand Down Expand Up @@ -190,6 +191,11 @@ class MenuDrawer extends StatelessWidget {
routeName: InteractiveFlagsPage.route,
currentRoute: currentRoute,
),
MenuItemWidget(
caption: 'Fling Animation Damping',
routeName: FlingAnimationDampingPage.route,
currentRoute: currentRoute,
),
const Divider(),
MenuItemWidget(
caption: 'WMS Sourced Map',
Expand Down
2 changes: 1 addition & 1 deletion lib/src/gestures/map_interactive_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ class MapInteractiveViewerState extends State<MapInteractiveViewer>
springDescription: SpringDescription.withDampingRatio(
mass: 1,
stiffness: 1000,
ratio: 5,
ratio: _interactionOptions.flingAnimationDampingRatio,
));
}

Expand Down
16 changes: 16 additions & 0 deletions lib/src/map/options/interaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ class InteractionOptions {
/// Defaults to [Curves.fastOutSlowIn].
final Curve doubleTapZoomCurve;

/// The damping ratio for the fling animation spring simulation
///
/// This controls how the fling animation decelerates after a drag gesture.
/// Lower values result in less damping (more momentum, bouncier).
/// Higher values result in more damping (stops quicker, less bouncy).
///
/// Defaults to 5.0.
final double flingAnimationDampingRatio;

/// Options to configure cursor/keyboard rotation
///
/// Cursor/keyboard rotation is designed for desktop platforms, and allows the
Expand Down Expand Up @@ -129,6 +138,7 @@ class InteractionOptions {
defaultDoubleTapDragZoomChangeCalculator,
this.doubleTapZoomDuration = const Duration(milliseconds: 200),
this.doubleTapZoomCurve = Curves.fastOutSlowIn,
this.flingAnimationDampingRatio = 5.0,
this.cursorKeyboardRotationOptions = const CursorKeyboardRotationOptions(),
this.keyboardOptions = const KeyboardOptions(),
}) : assert(
Expand All @@ -142,6 +152,10 @@ class InteractionOptions {
assert(
pinchMoveThreshold >= 0.0,
'`pinchMoveThreshold` must be positive',
),
assert(
flingAnimationDampingRatio > 0.0,
'`flingAnimationDampingRatio` must be positive',
);

/// Default calculator function for [doubleTapDragZoomChangeCalculator]
Expand Down Expand Up @@ -171,6 +185,7 @@ class InteractionOptions {
other.doubleTapDragZoomChangeCalculator &&
doubleTapZoomDuration == other.doubleTapZoomDuration &&
doubleTapZoomCurve == other.doubleTapZoomCurve &&
flingAnimationDampingRatio == other.flingAnimationDampingRatio &&
keyboardOptions == other.keyboardOptions;

@override
Expand All @@ -188,6 +203,7 @@ class InteractionOptions {
doubleTapDragZoomChangeCalculator,
doubleTapZoomDuration,
doubleTapZoomCurve,
flingAnimationDampingRatio,
keyboardOptions,
);
}