Tap to Highlight Features in Flutter
This tutorial shows how to highlight map features when the user taps on them — the Flutter equivalent of hover effects on web maps.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Highlight Tapped Marker
Change a marker's appearance when tapped:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class TapHighlightScreen extends StatefulWidget {
@override
_TapHighlightScreenState createState() => _TapHighlightScreenState();
}
class _TapHighlightScreenState extends State<TapHighlightScreen> {
MapMetricsController? mapController;
String? selectedMarkerId;
final List<Map<String, dynamic>> cities = [
{'id': 'paris', 'name': 'Paris', 'lat': 48.8566, 'lng': 2.3522},
{'id': 'london', 'name': 'London', 'lat': 51.5074, 'lng': -0.1276},
{'id': 'berlin', 'name': 'Berlin', 'lat': 52.52, 'lng': 13.405},
{'id': 'rome', 'name': 'Rome', 'lat': 41.9028, 'lng': 12.4964},
{'id': 'madrid', 'name': 'Madrid', 'lat': 40.4168, 'lng': -3.7038},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Tap to Highlight')),
body: Stack(
children: [
MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.0, 5.0),
zoom: 4.0,
),
markers: _buildMarkers(),
onMapClick: (Point point, LatLng coordinates) {
// Deselect when tapping the map background
setState(() {
selectedMarkerId = null;
});
},
),
// Info panel for selected city
if (selectedMarkerId != null)
Positioned(
top: 16,
left: 16,
right: 16,
child: _buildInfoPanel(),
),
],
),
);
}
Set<Marker> _buildMarkers() {
return cities.map((city) {
final isSelected = city['id'] == selectedMarkerId;
return Marker(
markerId: MarkerId(city['id']),
position: LatLng(city['lat'], city['lng']),
icon: BitmapDescriptor.defaultMarkerWithHue(
isSelected ? BitmapDescriptor.hueBlue : BitmapDescriptor.hueRed,
),
infoWindow: InfoWindow(title: city['name']),
onTap: () {
setState(() {
selectedMarkerId = city['id'];
});
},
);
}).toSet();
}
Widget _buildInfoPanel() {
final city = cities.firstWhere((c) => c['id'] == selectedMarkerId);
return Card(
elevation: 4,
child: ListTile(
leading: Icon(Icons.location_on, color: Colors.blue, size: 32),
title: Text(city['name'],
style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(
'Lat: ${city['lat']}, Lng: ${city['lng']}',
),
trailing: IconButton(
icon: Icon(Icons.close),
onPressed: () {
setState(() {
selectedMarkerId = null;
});
},
),
),
);
}
}Highlight GeoJSON Circles on Tap
Tap on circle features to highlight them:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class CircleTapHighlightScreen extends StatefulWidget {
@override
_CircleTapHighlightScreenState createState() =>
_CircleTapHighlightScreenState();
}
class _CircleTapHighlightScreenState extends State<CircleTapHighlightScreen> {
MapMetricsController? mapController;
int? highlightedIndex;
final List<Map<String, dynamic>> locations = [
{'name': 'Paris', 'lat': 48.8566, 'lng': 2.3522, 'visitors': '30M'},
{'name': 'London', 'lat': 51.5074, 'lng': -0.1276, 'visitors': '20M'},
{'name': 'Berlin', 'lat': 52.52, 'lng': 13.405, 'visitors': '14M'},
{'name': 'Rome', 'lat': 41.9028, 'lng': 12.4964, 'visitors': '10M'},
{'name': 'Madrid', 'lat': 40.4168, 'lng': -3.7038, 'visitors': '7M'},
{'name': 'Amsterdam', 'lat': 52.3676, 'lng': 4.9041, 'visitors': '8M'},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Circle Tap Highlight')),
body: Stack(
children: [
MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(48.0, 5.0),
zoom: 4.0,
),
circles: _buildCircles(),
onMapClick: (Point point, LatLng coordinates) {
_checkCircleTap(coordinates);
},
),
if (highlightedIndex != null)
Positioned(
bottom: 24,
left: 16,
right: 16,
child: Card(
elevation: 6,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: Icon(Icons.location_city,
color: Colors.white, size: 24),
),
SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
locations[highlightedIndex!]['name'],
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
Text(
'Annual visitors: ${locations[highlightedIndex!]['visitors']}',
style: TextStyle(color: Colors.grey[600]),
),
],
),
),
IconButton(
icon: Icon(Icons.close),
onPressed: () {
setState(() {
highlightedIndex = null;
});
},
),
],
),
),
),
),
],
),
);
}
Set<Circle> _buildCircles() {
return locations.asMap().entries.map((entry) {
final i = entry.key;
final loc = entry.value;
final isHighlighted = i == highlightedIndex;
return Circle(
circleId: CircleId('circle_$i'),
center: LatLng(loc['lat'], loc['lng']),
radius: isHighlighted ? 60000 : 40000, // meters
fillColor: isHighlighted
? Colors.blue.withOpacity(0.5)
: Colors.blue.withOpacity(0.2),
strokeColor: isHighlighted ? Colors.blue : Colors.blue.withOpacity(0.6),
strokeWidth: isHighlighted ? 3 : 1,
);
}).toSet();
}
void _checkCircleTap(LatLng tapPosition) {
// Find the closest location within a threshold
int? closestIndex;
double closestDistance = double.infinity;
for (int i = 0; i < locations.length; i++) {
final loc = locations[i];
final distance = _approximateDistance(
tapPosition.latitude,
tapPosition.longitude,
loc['lat'],
loc['lng'],
);
if (distance < 0.5 && distance < closestDistance) {
// ~0.5 degrees threshold
closestDistance = distance;
closestIndex = i;
}
}
setState(() {
highlightedIndex = closestIndex;
});
}
/// Simple approximate distance in degrees
double _approximateDistance(
double lat1, double lng1, double lat2, double lng2) {
final dLat = (lat1 - lat2);
final dLng = (lng1 - lng2);
return (dLat * dLat + dLng * dLng);
}
}Highlight Polygon Region on Tap
Tap on a polygon region to highlight it:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class PolygonTapHighlightScreen extends StatefulWidget {
@override
_PolygonTapHighlightScreenState createState() =>
_PolygonTapHighlightScreenState();
}
class _PolygonTapHighlightScreenState
extends State<PolygonTapHighlightScreen> {
MapMetricsController? mapController;
String? selectedRegion;
final Map<String, List<LatLng>> regions = {
'North': [
LatLng(49.0, 1.0),
LatLng(49.0, 4.0),
LatLng(51.0, 4.0),
LatLng(51.0, 1.0),
LatLng(49.0, 1.0),
],
'East': [
LatLng(47.0, 4.0),
LatLng(47.0, 8.0),
LatLng(49.0, 8.0),
LatLng(49.0, 4.0),
LatLng(47.0, 4.0),
],
'South': [
LatLng(43.0, 1.0),
LatLng(43.0, 4.0),
LatLng(45.0, 4.0),
LatLng(45.0, 1.0),
LatLng(43.0, 1.0),
],
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Polygon Tap Highlight')),
body: MapMetrics(
styleUrl:
'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (MapMetricsController controller) {
mapController = controller;
},
initialCameraPosition: CameraPosition(
target: LatLng(47.0, 4.0),
zoom: 5.0,
),
polygons: regions.entries.map((entry) {
final isSelected = entry.key == selectedRegion;
return Polygon(
polygonId: PolygonId(entry.key),
points: entry.value,
fillColor: isSelected
? Colors.blue.withOpacity(0.5)
: Colors.grey.withOpacity(0.2),
strokeColor: isSelected ? Colors.blue : Colors.grey,
strokeWidth: isSelected ? 3 : 1,
consumeTapEvents: true,
onTap: () {
setState(() {
selectedRegion =
selectedRegion == entry.key ? null : entry.key;
});
},
);
}).toSet(),
),
);
}
}Next Steps
- Popup on Click — Show popups when tapping features
- Show Polygon Info on Click — Display region details
- Filter Markers — Show/hide markers by category
Tip: On mobile, use tap instead of hover for interactivity. Combine highlighted state with setState() to redraw markers, circles, or polygons with different styles instantly.