Skip to content

Instantly share code, notes, and snippets.

@hleinone
Last active June 29, 2022 12:43
Show Gist options
  • Save hleinone/59fe6276fb56850e42c83e0c611f551f to your computer and use it in GitHub Desktop.
Save hleinone/59fe6276fb56850e42c83e0c611f551f to your computer and use it in GitHub Desktop.
Google Maps for Flutter Customizable Buttons
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
/// A button resembling the built-in 'compass' indicator from Google Maps SDK.
/// This supports both light and dark themes. Requires material,
/// google_maps_flutter and flutter_svg.
@immutable
class Compass extends StatelessWidget {
const Compass({
Key? key,
Completer<GoogleMapController>? controller,
required this.cameraPosition,
this.onPressed,
this.tooltip,
}) : assert(
controller != null || onPressed != null,
'Either controller or onPressed must be provided',
),
_controller = controller,
super(key: key);
final Completer<GoogleMapController>? _controller;
final Stream<CameraPosition> cameraPosition;
final void Function()? onPressed;
final String? tooltip;
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: cameraPosition,
builder: (context, AsyncSnapshot<CameraPosition> cameraPosition) {
final data = cameraPosition.data;
final isStraight = data?.isStraight ?? true;
final tiltRadians = data?.tiltRadians ?? 0;
final bearingRadians = data?.bearingRadians ?? 0;
final button = AnimatedOpacity(
duration: isStraight
? const Duration(seconds: 2)
: kThemeChangeDuration,
opacity: data?.isStraight ?? true ? 0 : 1,
curve: const Interval(0.8, 1),
child: OutlinedButton(
onPressed: isStraight
? () {}
: onPressed ??
() async {
final controller = await _controller!.future;
final center = data?.target ?? await () async {
final visibleRegion = await controller.getVisibleRegion();
final northeastScreen = await controller
.getScreenCoordinate(visibleRegion.northeast);
final southwestScreen = await controller
.getScreenCoordinate(visibleRegion.southwest);
final centerScreen = ScreenCoordinate(
x: ((northeastScreen.x + southwestScreen.x) / 2)
.round(),
y: ((northeastScreen.y + southwestScreen.y) / 2)
.round(),
);
return await controller.getLatLng(centerScreen);
}();
final zoom = data?.zoom ?? await controller.getZoomLevel();
controller.animateCamera(
CameraUpdate.newCameraPosition(
CameraPosition(
tilt: 0,
bearing: 0,
target: center,
zoom: zoom,
),
),
);
},
style: OutlinedButton.styleFrom(
elevation: 0,
backgroundColor:
Theme.of(context).colorScheme.surface.withAlpha(191),
padding: const EdgeInsets.all(0),
fixedSize: const Size(36, 36),
minimumSize: const Size(36, 36),
primary: Theme.of(context).colorScheme.onSurface.withAlpha(191),
shape: const CircleBorder(),
side: BorderSide(
color: Theme.of(context).colorScheme.onSurface.withAlpha(64),
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(6),
child: Transform(
transform: Matrix4.rotationX(tiltRadians),
alignment: Alignment.center,
transformHitTests: true,
child: Transform.rotate(
transformHitTests: true,
angle: bearingRadians,
child: SvgPicture.asset('assets/vector/compass_needle.svg'),
),
),
),
),
);
return IgnorePointer(
ignoring: isStraight,
child: tooltip != null ?
Tooltip(message: tooltip, child: button) :
button,
);
},
);
}
}
extension CameraPositionExtension on CameraPosition {
bool get isStraight => bearing == 0 && tilt == 0;
double get bearingRadians => bearing * -pi / 180;
double get tiltRadians => tilt * pi / 180;
}
Display the source blob
Display the rendered blob
Raw
<?xml version="1.0" encoding="UTF-8"?>
<svg width="36px" height="36px" viewBox="0 0 36 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Compass needle</title>
<g id="Compass-needle" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M18.3162278,2.21359436 C18.6148328,2.31312936 18.8491483,2.5474449 18.9486833,2.84604989 L24,18 L18,18 L17.9998483,2.16200636 C18.1046837,2.1620052 18.2113054,2.17862025 18.3162278,2.21359436 Z" id="Combined-Shape" fill="#E90000"></path>
<path d="M12.3162278,2.21359436 C12.6148328,2.31312936 12.8491483,2.5474449 12.9486833,2.84604989 L18,18 L12,18 L11.9998483,2.16200636 C12.1046837,2.1620052 12.2113054,2.17862025 12.3162278,2.21359436 Z" id="Combined-Shape" fill="#FF0004" transform="translate(14.999924, 10.081003) scale(-1, 1) translate(-14.999924, -10.081003) "></path>
<path d="M18.3162278,18.051588 C18.6148328,18.151123 18.8491483,18.3854385 18.9486833,18.6840435 L24,33.8379936 L18,33.8379936 L17.9998483,18 C18.1046837,17.9999988 18.2113054,18.0166139 18.3162278,18.051588 Z" id="Combined-Shape" fill="#DADADA" transform="translate(20.999924, 25.918997) scale(1, -1) translate(-20.999924, -25.918997) "></path>
<path d="M12.3162278,18.051588 C12.6148328,18.151123 12.8491483,18.3854385 12.9486833,18.6840435 L18,33.8379936 L12,33.8379936 L11.9998483,18 C12.1046837,17.9999988 12.2113054,18.0166139 12.3162278,18.051588 Z" id="Combined-Shape" fill="#C2C2C2" transform="translate(14.999924, 25.918997) scale(-1, -1) translate(-14.999924, -25.918997) "></path>
<circle id="Oval" fill="#FFFFFF" cx="18" cy="18" r="2"></circle>
</g>
</svg>
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
/// A button resembling the built-in 'my location' button from Google Maps SDK.
/// This supports both light and dark themes. Requires material,
/// google_maps_flutter and location.
@immutable
class MyLocationButton extends StatelessWidget {
const MyLocationButton({
Key? key,
Completer<GoogleMapController>? controller,
Location? location,
this.onPressed,
this.tooltip,
}) : assert(
(controller != null && location != null) || onPressed != null,
'Either controller and location or onPressed must be provided',
),
_controller = controller,
_location = location,
super(key: key);
final Completer<GoogleMapController>? _controller;
final Location? _location;
final void Function()? onPressed;
final String? tooltip;
@override
Widget build(BuildContext context) {
final button = OutlinedButton(
onPressed: onPressed ??
() async {
final controller = await _controller!.future;
final location = await _location!.getLocation();
final latitude = location.latitude;
final longitude = location.longitude;
if (latitude == null || longitude == null) return;
controller.animateCamera(
CameraUpdate.newLatLng(LatLng(latitude, longitude)),
);
},
style: OutlinedButton.styleFrom(
elevation: 0,
backgroundColor:
Theme.of(context).colorScheme.surface.withAlpha(191),
padding: const EdgeInsets.all(0),
fixedSize: const Size(40, 40),
minimumSize: const Size(40, 40),
primary: Theme.of(context).colorScheme.onSurface.withAlpha(191),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
side: BorderSide(
color: Theme.of(context).colorScheme.onSurface.withAlpha(64),
width: 1,
),
),
child: const Icon(Icons.my_location),
);
if (tooltip != null) {
return Tooltip(message: tooltip, child: button);
}
return button;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment