LatLng Picker

A beautiful Flutter package that provides an interactive location latitude and longitude picker with physics-based animations for Google Maps. Pick locations with style using an animated pin that bounces and elevates with realistic physics.

LatLng Picker Demo

What is it?

LatLng Picker is a wrapper widget for Google Maps Flutter that adds an intuitive latitude and longitude picking experience. Instead of placing markers, users can pan the map while a fixed center pin shows where they're selecting. The pin features smooth animations with:

  • Fixed center positioning
  • Physics-based bounce effects
  • Elevation animations with dynamic shadows
  • Haptic feedback
  • Fully customizable appearance

Under the Hood

This package uses:

  • google_maps_flutter - The official Google Maps plugin for Flutter
  • Flutter's Animation Framework - For smooth, physics-based pin animations
  • Custom AnimationControllers - To orchestrate bounce, stretch, and shadow effects

How to Use

Installation

Add to your pubspec.yaml:

dependencies:
  map_latlng_picker: ^1.0.3
  google_maps_flutter: ^2.14.0

Google Maps API Key Setup

Before using this package, you need to configure Google Maps API keys for your target platforms:

🌐 Web

Add to web/index.html:

<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY_HERE"></script>

πŸ€– Android

Add to android/app/src/main/AndroidManifest.xml inside <application>:

<meta-data
    android:name="com.google.android.geo.API_KEY"
    android:value="YOUR_API_KEY_HERE"/>

🍎 iOS

In ios/Runner/AppDelegate.swift:

import GoogleMaps

override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    GMSServices.provideAPIKey("YOUR_API_KEY_HERE")
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

πŸ“š Get your Google Maps API key

Basic Usage

Simply wrap your GoogleMap widget with LatLngLocationPicker:

import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:map_latlng_picker/map_latlng_picker.dart';

class LocationPickerScreen extends StatefulWidget {
  @override
  State<LocationPickerScreen> createState() => _LocationPickerScreenState();
}

class _LocationPickerScreenState extends State<LocationPickerScreen> {
  final LatLngLocationPickerController _controller = LatLngLocationPickerController();
  LatLng? _selectedLocation;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Pick Location')),
      body: LatLngLocationPicker(
        controller: _controller,
        enabled: true,
        onLocationPicked: (location) {
          setState(() {
            _selectedLocation = location;
          });
          print('Selected: ${location.latitude}, ${location.longitude}');
        },
        child: GoogleMap(
          initialCameraPosition: CameraPosition(
            target: LatLng(37.7749, -122.4194),
            zoom: 14,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          _controller.isEnabled
            ? _controller.disable?.call()
            : _controller.enable?.call();
        },
        child: Icon(_controller.isEnabled ? Icons.check : Icons.location_on),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Advanced Usage with Custom Pin

LatLngLocationPicker(
  enabled: true,
  pinOffset: 60,
  useHapticFeedback: true,
  onLocationPicked: (location) {
    // Handle picked location
  },
  pinWidget: AnimatedLocationPin(
    state: _isPanning ? PinState.elevated : PinState.idle,
    color: Colors.blue,
    innerColor: Colors.white,
    size: 50,
    stickHeight: 25,
    shadowDistance: 20,
  ),
  child: GoogleMap(
    initialCameraPosition: CameraPosition(
      target: LatLng(37.7749, -122.4194),
      zoom: 14,
    ),
  ),
)

Simplified Pin Customization with AnimatePinData

LatLngLocationPicker(
  enabled: true,
  pinData: AnimatePinData(
    color: Colors.blue,
    innerColor: Colors.white,
    stickColor: Colors.blueAccent,
    size: 50,
    stickHeight: 25,
    stickBorderRadius: 4.0,
    shadowColor: Colors.black54,
    shadowDistance: 10.0,
  ),
  onLocationPicked: (location) {
    // Handle picked location
  },
  child: GoogleMap(
    initialCameraPosition: CameraPosition(
      target: LatLng(37.7749, -122.4194),
      zoom: 14,
    ),
  ),
)

Parameters

LatLngLocationPicker

Parameter Type Default Description
child Widget required The GoogleMap widget to wrap
controller LatLngLocationPickerController? null Controller to enable/disable picker programmatically
onLocationPicked OnLatLngLocationPicked? null Callback fired when user releases the map
pinWidget Widget? null Custom pin widget (uses AnimatedLocationPin by default)
pinData AnimatePinData? null Pin customization data (alternative to pinWidget)
pinOffset double 50 Vertical offset from center (useful for pins with bottom pointer)
enabled bool false Whether location picker is enabled by default
useHapticFeedback bool true Enable haptic feedback on pan start/end

AnimatePinData

Parameter Type Default Description
state PinState? null Advanced mode: Control pin state
isElevated bool false Simple mode: Whether pin is elevated
color Color required Main circle color
innerColor Color required Inner dot color
stickColor Color? null Stick color (defaults to main color)
stickWidth double? null Stick width (defaults to 15% of size)
stickBorderRadius double 4.0 Border radius of the stick
shadowColor Color required Shadow color
size double 40.0 Pin size (circle diameter)
stickHeight double 20.0 Height of the stick
shadowDistance double 10.0 Shadow distance when elevated (advanced mode)
duration Duration Duration(milliseconds: 300) Animation duration

AnimatedLocationPin

Parameter Type Default Description
state PinState? null Advanced mode: Control pin state (idle/elevated)
isElevated bool false Simple mode: Whether pin is elevated
color Color Colors.red Main circle color
innerColor Color Colors.white Inner dot color
stickColor Color? null Stick color (defaults to main color)
stickWidth double? null Stick width (defaults to 10% of size)
stickBorderRadius double 12 Border radius of the stick
shadowColor Color Colors.black26 Shadow color
size double 40 Pin size (circle diameter)
stickHeight double 20 Height of the stick
shadowDistance double 15 Shadow distance when elevated (advanced mode)
duration Duration Duration(milliseconds: 200) Animation duration (simple mode)

LatLngLocationPickerController

Method/Property Type Description
enable() VoidCallback? Enable location picking mode
disable() VoidCallback? Disable location picking mode
isEnabled bool Get current enabled state
dispose() void Clean up controller resources

Pin Animation Modes

Simple Mode (Boolean)

Use isElevated for basic on/off elevation:

AnimatedLocationPin(
  isElevated: _isPanning,
)

Advanced Mode (PinState)

Use state for enhanced animations with smoother transitions:

AnimatedLocationPin(
  state: _isPanning ? PinState.elevated : PinState.idle,
)

Examples

Example 1: Basic Location Picker

LatLngLocationPicker(
  enabled: true,
  onLocationPicked: (location) {
    print('Location: ${location.latitude}, ${location.longitude}');
  },
  child: GoogleMap(
    initialCameraPosition: CameraPosition(
      target: LatLng(0, 0),
      zoom: 10,
    ),
  ),
)

Example 2: With Controller

final controller = LatLngLocationPickerController();

// Enable picker
controller.enable?.call();

// Disable picker
controller.disable?.call();

// Check state
if (controller.isEnabled) {
  // Picker is active
}

Example 3: Custom Styled Pin

LatLngLocationPicker(
  enabled: true,
  pinWidget: AnimatedLocationPin(
    isElevated: _isPanning,
    color: Colors.purple,
    innerColor: Colors.yellow,
    size: 60,
    stickHeight: 30,
    stickBorderRadius: 4,
  ),
  child: GoogleMap(
    initialCameraPosition: CameraPosition(
      target: LatLng(37.7749, -122.4194),
      zoom: 14,
    ),
  ),
)

Example 4: Using AnimatePinData

LatLngLocationPicker(
  enabled: true,
  pinData: AnimatePinData(
    color: Colors.green,
    innerColor: Colors.white,
    stickColor: Colors.greenAccent,
    size: 55,
    stickHeight: 28,
    stickBorderRadius: 6.0,
    shadowColor: Colors.black45,
    shadowDistance: 12.0,
  ),
  onLocationPicked: (location) {
    print('Picked: ${location.latitude}, ${location.longitude}');
  },
  child: GoogleMap(
    initialCameraPosition: CameraPosition(
      target: LatLng(37.7749, -122.4194),
      zoom: 14,
    ),
  ),
)

Features

  • Smooth physics-based animations
  • Customizable pin appearance
  • Haptic feedback support
  • Controller for programmatic control
  • Works as a simple wrapper - no map recreation needed
  • Preserves all GoogleMap properties and callbacks
  • Two animation modes (simple boolean and advanced state-based)
  • Dynamic shadow effects

Author

Paul Jeremiah
Twitter: @edeme_kong

Support

If you find this package helpful, consider buying me a coffee! β˜•

Buy Me A Coffee

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

map_latlng_picker

Libraries

map_latlng_picker
A Flutter package for interactive location latitude and longitude picking with Google Maps.