Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Last active September 14, 2019 19:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slightfoot/0b960b14c6e3e7d28372c7070aa3bd82 to your computer and use it in GitHub Desktop.
Save slightfoot/0b960b14c6e3e7d28372c7070aa3bd82 to your computer and use it in GitHub Desktop.
Map Widget Example
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'package:flutter/widgets.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.indigo,
accentColor: Colors.pinkAccent,
),
home: ExampleScreen(),
),
);
}
class ExampleScreen extends StatefulWidget {
@override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
final rand = math.Random();
final _offsets = <MapLatLng>[
MapLatLng(51.531139, -0.188121), // 935, 235 gb
MapLatLng(-26.790489, 134.797758), // 1754, 730 aus
MapLatLng(36.935858, -120.835227), // 199, 349 cali
];
final _titles = <String>['UK', 'Australia', 'California'];
int _index = 0;
@override
void reassemble() {
super.reassemble();
nextDestination();
}
void nextDestination() {
int index = _index;
while (index == _index) {
index = rand.nextInt(_offsets.length);
}
setState(() => _index = index);
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.blue,
child: Stack(
children: <Widget>[
MapWidget(
latLng: _offsets[_index],
),
SafeArea(
child: SizedBox(
width: double.infinity,
child: Text(
_titles[_index],
style: TextStyle(
color: Colors.white,
fontSize: 32.0,
shadows: [
Shadow(color: Colors.black, blurRadius: 3.0),
],
),
textAlign: TextAlign.center,
),
),
),
],
),
);
}
}
class MapLatLng {
const MapLatLng(this.lat, this.lng);
final double lat;
final double lng;
@override
String toString() => 'MapLatLng{$lat, $lng}';
}
class MapWidget extends StatefulWidget {
const MapWidget({
Key key,
@required this.latLng,
this.mapImage = const ExactAssetImage('assets/images/world_map_simple.jpg'),
this.mapWidth = 2000,
this.mapHeight = 1075,
this.mapOrigin = const MapLatLng(5.0, 14.0),
this.overflow = Overflow.clip,
}) : assert(latLng != null),
assert(mapImage != null && mapWidth != null && mapHeight != null && mapOrigin != null),
assert(overflow != null),
super(key: key);
final MapLatLng latLng;
final ImageProvider mapImage;
final int mapWidth;
final int mapHeight;
final MapLatLng mapOrigin;
final Overflow overflow;
@override
_MapWidgetState createState() => _MapWidgetState();
}
class _MapWidgetState extends State<MapWidget> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController.unbounded(value: 1.0, vsync: this);
_animation = AlwaysStoppedAnimation<Offset>(_convert(widget.latLng));
}
@override
void didUpdateWidget(MapWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.latLng != widget.latLng) {
final current = _animation?.value ?? Offset.zero;
_animation = Tween<Offset>(begin: current, end: _convert(widget.latLng)).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOutExpo),
);
_controller.animateWith(BoundedFrictionSimulation(0.5, 0.0, 0.7, -1.0, 1.0));
}
}
Offset _convert(MapLatLng latLng) {
// https://en.wikipedia.org/wiki/Equirectangular_projection
final cosLat = math.cos(widget.mapOrigin.lat * math.pi / 180.0);
final dx = ((((latLng.lng - widget.mapOrigin.lng) + 180.0) % 360.0) / 360.0) * widget.mapWidth * cosLat;
final dy = (0.5 - ((latLng.lat - widget.mapOrigin.lat) / 180.0)) * widget.mapHeight;
return Offset(dx, dy);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final size = constraints.biggest;
return Stack(
overflow: Overflow.visible,
children: <Widget>[
AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
return Positioned(
left: -_animation.value.dx + size.width / 2,
top: -_animation.value.dy + size.height / 2,
child: child,
);
},
child: Image(image: widget.mapImage, fit: BoxFit.none),
),
],
);
},
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment