flutter_dynamic_map 0.1.0
flutter_dynamic_map: ^0.1.0 copied to clipboard
flutter_map plugins for animated polylines or marker movements.
example/lib/main.dart
import 'package:flutter_dynamic_map/flutter_dynamic_map.dart';
import 'package:flutter/material.dart';
import 'toggleable_marker/toggleable_marker.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Map Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
@override
void initState() {
animatedMapController = AnimatedMapController(vsync: this);
toggleMarkers = [
ToggleMarkerWrapper(
initialPosition: const LatLng(38.93132871414923, -77.07046226186635),
),
ToggleMarkerWrapper(
initialPosition:
const LatLng(38.930236019389184, -77.05503322891566)),
ToggleMarkerWrapper(
initialPosition: const LatLng(38.94030046698981, -77.03774754300335)),
];
polylineController = StatefulPolylineController(
navigationHandler: defaultORSNavigationHandler(
'5b3ce3597851110001cf6248a1a1addc948a48c0b5adf2c4d07589e4'),
);
// WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
// _centerOnPoints();
// });
super.initState();
}
late List<ToggleMarkerWrapper> toggleMarkers;
late StatefulPolylineController<ORSModel> polylineController;
late final AnimatedMapController animatedMapController;
void _centerOnPoints() {
animatedMapController.animatedFitCamera(
cameraFit: CameraFit.coordinates(
padding: const EdgeInsets.all(64),
coordinates: toggleMarkers
.map((e) => e.animatedMarkerController.currentPosition)
.toList() +
(polylineController.currentPolylinePoints ?? []),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: DynamicMap(
mapOptions: const MapOptions(
initialCenter: LatLng(38.930236019389184, -77.05503322891566),
maxZoom: 18.5),
markers: List.generate(
toggleMarkers.length,
(index) => DynamicMapMarker(
controller: toggleMarkers[index].animatedMarkerController,
// anchorPos: AnchorPos.align(AnchorAlign.top),
builder: (context) => ToggleableMarker(
nameVisibilityNotifier:
toggleMarkers[index].nameVisibilityNotifier,
markerIndex: index),
width: 200,
height: 100,
),
),
animatedMapController: animatedMapController,
polylines: [
StatefulPolyline(
statefulPolylineController: polylineController,
initialRoutingPoints: toggleMarkers
.map((e) => e.animatedMarkerController.currentPosition)
.toList(),
),
],
),
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton.extended(
onPressed: () async {
toggleMarkers[0]
.animatedMarkerController
.moveTo(animatedMapController.mapController.camera.center);
_centerOnPoints();
await polylineController.updateRouteFromAPI([
toggleMarkers[0].animatedMarkerController.currentPosition,
toggleMarkers[1].animatedMarkerController.currentPosition,
toggleMarkers[2].animatedMarkerController.currentPosition
]);
_centerOnPoints();
},
label: const Text('Center 1')),
const SizedBox(
height: 8,
),
FloatingActionButton.extended(
onPressed: () async {
toggleMarkers[1]
.animatedMarkerController
.moveTo(animatedMapController.mapController.camera.center);
_centerOnPoints();
await polylineController.updateRouteFromAPI([
toggleMarkers[0].animatedMarkerController.currentPosition,
toggleMarkers[1].animatedMarkerController.currentPosition,
toggleMarkers[2].animatedMarkerController.currentPosition
]);
_centerOnPoints();
},
label: const Text('Center 2')),
const SizedBox(
height: 8,
),
FloatingActionButton.extended(
onPressed: () async {
// await
toggleMarkers[2].animatedMarkerController.moveTo(
animatedMapController.mapController.camera.center,
// focus: true
);
_centerOnPoints();
await polylineController.updateRouteFromAPI([
toggleMarkers[0].animatedMarkerController.currentPosition,
toggleMarkers[1].animatedMarkerController.currentPosition,
toggleMarkers[2].animatedMarkerController.currentPosition,
]);
_centerOnPoints();
},
label: const Text('Center 3')),
const SizedBox(
height: 8,
),
FloatingActionButton.extended(
onPressed: () async {
for (final element in toggleMarkers) {
element.changeTooltipVisibility();
}
},
label: const Text('Labels')),
const SizedBox(
height: 8,
),
FloatingActionButton.extended(
onPressed: () async {
_centerOnPoints();
},
label: const Text('Center Map')),
const SizedBox(
height: 8,
),
StreamBuilder<ORSModel?>(
stream: polylineController.dataStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return _RoutingFAB(
data: snapshot.data!,
key: ValueKey(snapshot.data!),
);
}
if (snapshot.hasError) {
return FloatingActionButton.extended(
onPressed: () {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Error'),
content: Text(snapshot.error.toString()),
actions: [
TextButton(
child: const Text('Close'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
));
},
label: const Text('Routing Error'),
backgroundColor: Colors.red,
);
} else {
return Container(
width: 0,
);
}
},
),
],
),
);
}
}
class _RoutingFAB extends StatefulWidget {
const _RoutingFAB({Key? key, required this.data}) : super(key: key);
final ORSModel data;
@override
State<_RoutingFAB> createState() => _RoutingFABState();
}
class _RoutingFABState extends State<_RoutingFAB>
with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<Color?> animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 750),
vsync: this,
);
animation = ColorTween(
begin: Colors.green,
end: Colors.blue,
).animate(_controller)
..addListener(() {
setState(() {});
});
_controller.forward();
}
@override
Widget build(BuildContext context) {
return FloatingActionButton.extended(
backgroundColor: animation.value,
onPressed: () async {
showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15))),
builder: (_) {
final duration = Duration(
seconds: widget.data.routes!.first.summary!.duration! ~/ 1);
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'${duration.inHours}h ${duration.inMinutes % 60}m ETA',
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 26),
),
),
const Divider(
height: 16,
),
...List.generate(
widget.data.routes!.first.segments!.length, (index) {
final segment =
widget.data.routes!.first.segments![index];
return _Segment(segment: segment);
})
],
),
);
});
},
label: const Text('Routing Info'));
}
}
class _Segment extends StatelessWidget {
const _Segment({Key? key, required this.segment}) : super(key: key);
final Segments segment;
@override
Widget build(BuildContext context) {
final duration = Duration(seconds: segment.duration! ~/ 1);
return Column(
children: [
const Divider(
thickness: 2,
height: 16,
),
Text(
'${duration.inHours}h ${duration.inMinutes % 60}m ETA, ${segment.distance} meters',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
const Divider(
thickness: 1,
),
...List.generate(segment.steps!.length, (index) {
final item = segment.steps![index];
return ListTile(
title: Text(item.instruction!),
subtitle: Text(item.name!),
leading: Text(
'${item.distance}\nmeters',
textAlign: TextAlign.center,
),
trailing: Text(
'${((item.duration ?? 0) / 60).toStringAsFixed(2)}\nminutes',
textAlign: TextAlign.center,
),
);
})
],
);
}
}
class ToggleMarkerWrapper {
ToggleMarkerWrapper({required LatLng initialPosition})
: animatedMarkerController =
AnimatedMarkerController(initialPosition: initialPosition);
final AnimatedMarkerController animatedMarkerController;
final ValueNotifier<bool> nameVisibilityNotifier = ValueNotifier<bool>(false);
bool changeTooltipVisibility() {
return nameVisibilityNotifier.value = !nameVisibilityNotifier.value;
}
}