Custom Map Styling with Flutter and MapMetrics
This tutorial will show you how to create custom map styles and integrate them with your Flutter MapMetrics applications.
Using MapMetrics Portal for Custom Styles
The MapMetrics Portal provides an intuitive interface for creating custom map styles that work seamlessly with Flutter applications.
Step 1: Create a Custom Style
- Visit MapMetrics Portal: Go to portal.mapmetrics.org
- Navigate to Styles: Click on the "Styles" section
- Create New Style: Click "New Style" and choose a template
- Customize Your Style: Use the visual editor to modify:
- Colors and themes
- Fonts and typography
- Map features (roads, buildings, water, etc.)
- Icons and symbols
- Save and Get URL: Save your style and copy the style URL
Step 2: Use Custom Style in Flutter
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class CustomStyledMapScreen extends StatefulWidget {
@override
_CustomStyledMapScreenState createState() => _CustomStyledMapScreenState();
}
class _CustomStyledMapScreenState extends State<CustomStyledMapScreen> {
MapMetricsController? mapController;
String currentStyleUrl = '';
// Different style URLs from MapMetrics Portal
final Map<String, String> styleOptions = {
'Dark Theme': 'https://gateway.mapmetrics.org/styles/YOUR_DARK_STYLE_ID?token=YOUR_API_KEY',
'Light Theme': 'https://gateway.mapmetrics.org/styles/YOUR_LIGHT_STYLE_ID?token=YOUR_API_KEY',
'Satellite': 'https://gateway.mapmetrics.org/styles/YOUR_SATELLITE_STYLE_ID?token=YOUR_API_KEY',
'Custom Brand': 'https://gateway.mapmetrics.org/styles/YOUR_CUSTOM_STYLE_ID?token=YOUR_API_KEY',
};
@override
void initState() {
super.initState();
currentStyleUrl = styleOptions.values.first;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Custom Styled Map'),
actions: [
PopupMenuButton<String>(
onSelected: _changeStyle,
itemBuilder: (context) => styleOptions.keys.map((String key) {
return PopupMenuItem<String>(
value: key,
child: Text(key),
);
}).toList(),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Icon(Icons.style),
),
),
],
),
body: MapMetrics(
styleUrl: currentStyleUrl,
onMapCreated: (MapMetricsController controller) {
setState(() {
mapController = controller;
});
},
onStyleLoaded: () {
print('Custom style loaded successfully!');
},
initialCameraPosition: CameraPosition(
target: LatLng(40.7128, -74.0060),
zoom: 12.0,
),
),
);
}
void _changeStyle(String styleName) {
setState(() {
currentStyleUrl = styleOptions[styleName] ?? currentStyleUrl;
});
}
}
Dynamic Style Switching
Smooth Style Transitions
dart
class DynamicStyleScreen extends StatefulWidget {
@override
_DynamicStyleScreenState createState() => _DynamicStyleScreenState();
}
class _DynamicStyleScreenState extends State<DynamicStyleScreen> {
MapMetricsController? mapController;
bool isDarkMode = false;
String get currentStyleUrl => isDarkMode
? 'https://gateway.mapmetrics.org/styles/YOUR_DARK_STYLE_ID?token=YOUR_API_KEY'
: 'https://gateway.mapmetrics.org/styles/YOUR_LIGHT_STYLE_ID?token=YOUR_API_KEY';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Dynamic Style Switching'),
actions: [
Switch(
value: isDarkMode,
onChanged: (value) {
setState(() {
isDarkMode = value;
});
_updateMapStyle();
},
),
],
),
body: MapMetrics(
styleUrl: currentStyleUrl,
onMapCreated: (controller) => mapController = controller,
onStyleLoaded: () => print('Style loaded'),
initialCameraPosition: CameraPosition(
target: LatLng(40.7128, -74.0060),
zoom: 12.0,
),
),
);
}
void _updateMapStyle() {
mapController?.setStyleString(currentStyleUrl);
}
}
Custom Map Layers
Adding Custom Overlays
dart
class CustomLayersScreen extends StatefulWidget {
@override
_CustomLayersScreenState createState() => _CustomLayersScreenState();
}
class _CustomLayersScreenState extends State<CustomLayersScreen> {
MapMetricsController? mapController;
Set<Circle> customLayers = {};
@override
void initState() {
super.initState();
_initializeCustomLayers();
}
void _initializeCustomLayers() {
customLayers = {
Circle(
circleId: CircleId('highlight_area'),
center: LatLng(40.7128, -74.0060),
radius: 2000,
strokeWidth: 3,
strokeColor: Colors.blue,
fillColor: Colors.blue.withOpacity(0.1),
),
Circle(
circleId: CircleId('restricted_zone'),
center: LatLng(40.7589, -73.9851),
radius: 1000,
strokeWidth: 2,
strokeColor: Colors.red,
fillColor: Colors.red.withOpacity(0.2),
),
};
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Custom Layers')),
body: MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: LatLng(40.7128, -74.0060),
zoom: 12.0,
),
circles: customLayers,
),
);
}
}
Branded Map Styles
Creating Brand-Consistent Maps
dart
class BrandedMapScreen extends StatefulWidget {
@override
_BrandedMapScreenState createState() => _BrandedMapScreenState();
}
class _BrandedMapScreenState extends State<BrandedMapScreen> {
MapMetricsController? mapController;
// Brand colors
final Color primaryColor = Color(0xFF1E88E5);
final Color secondaryColor = Color(0xFF42A5F5);
final Color accentColor = Color(0xFFFF5722);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Branded Map'),
backgroundColor: primaryColor,
),
body: Stack(
children: [
MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_BRANDED_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: LatLng(40.7128, -74.0060),
zoom: 12.0,
),
),
// Custom branded overlay
Positioned(
top: 20,
right: 20,
child: Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: primaryColor.withOpacity(0.9),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Text(
'Your Brand',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
);
}
}
Style Configuration Options
Advanced Style Settings
dart
class AdvancedStyleScreen extends StatefulWidget {
@override
_AdvancedStyleScreenState createState() => _AdvancedStyleScreenState();
}
class _AdvancedStyleScreenState extends State<AdvancedStyleScreen> {
MapMetricsController? mapController;
bool showBuildings = true;
bool showLabels = true;
bool showRoads = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Advanced Style Options')),
body: Column(
children: [
// Style controls
Container(
padding: EdgeInsets.all(16),
color: Colors.grey[100],
child: Column(
children: [
SwitchListTile(
title: Text('Show Buildings'),
value: showBuildings,
onChanged: (value) {
setState(() {
showBuildings = value;
});
_updateMapStyle();
},
),
SwitchListTile(
title: Text('Show Labels'),
value: showLabels,
onChanged: (value) {
setState(() {
showLabels = value;
});
_updateMapStyle();
},
),
SwitchListTile(
title: Text('Show Roads'),
value: showRoads,
onChanged: (value) {
setState(() {
showRoads = value;
});
_updateMapStyle();
},
),
],
),
),
// Map
Expanded(
child: MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: LatLng(40.7128, -74.0060),
zoom: 15.0,
),
),
),
],
),
);
}
void _updateMapStyle() {
// You can programmatically modify style layers here
// This would require additional MapMetrics GL JS functionality
print('Style updated: Buildings=$showBuildings, Labels=$showLabels, Roads=$showRoads');
}
}
Performance Optimization
Style Loading Optimization
dart
class OptimizedStyleScreen extends StatefulWidget {
@override
_OptimizedStyleScreenState createState() => _OptimizedStyleScreenState();
}
class _OptimizedStyleScreenState extends State<OptimizedStyleScreen> {
MapMetricsController? mapController;
bool isStyleLoaded = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Optimized Style Loading')),
body: Stack(
children: [
MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
onStyleLoaded: () {
setState(() {
isStyleLoaded = true;
});
},
initialCameraPosition: CameraPosition(
target: LatLng(40.7128, -74.0060),
zoom: 12.0,
),
),
// Loading indicator
if (!isStyleLoaded)
Container(
color: Colors.white,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Loading custom map style...'),
],
),
),
),
],
),
);
}
}
Best Practices
Style Design Tips
- Consistency: Use consistent colors and fonts across your app and map
- Accessibility: Ensure sufficient contrast for text and important features
- Performance: Keep style complexity reasonable for smooth rendering
- Branding: Integrate your brand colors and elements naturally
- Testing: Test your styles on different devices and screen sizes
Style Management
dart
class StyleManager {
static const Map<String, String> predefinedStyles = {
'default': 'https://gateway.mapmetrics.org/styles/DEFAULT_STYLE_ID?token=YOUR_API_KEY',
'dark': 'https://gateway.mapmetrics.org/styles/DARK_STYLE_ID?token=YOUR_API_KEY',
'satellite': 'https://gateway.mapmetrics.org/styles/SATELLITE_STYLE_ID?token=YOUR_API_KEY',
'minimal': 'https://gateway.mapmetrics.org/styles/MINIMAL_STYLE_ID?token=YOUR_API_KEY',
};
static String getStyleUrl(String styleName) {
return predefinedStyles[styleName] ?? predefinedStyles['default']!;
}
static bool isValidStyleUrl(String url) {
return url.startsWith('https://gateway.mapmetrics.org/styles/') &&
url.contains('token=');
}
}
Next Steps
Now that you understand custom styling, try:
Pro Tip: Use the MapMetrics Portal's style editor to create multiple variations of your map style for different use cases (dark mode, minimal view, detailed view, etc.).