Skip to content

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

  1. Visit MapMetrics Portal: Go to portal.mapmetrics.org
  2. Navigate to Styles: Click on the "Styles" section
  3. Create New Style: Click "New Style" and choose a template
  4. Customize Your Style: Use the visual editor to modify:
    • Colors and themes
    • Fonts and typography
    • Map features (roads, buildings, water, etc.)
    • Icons and symbols
  5. 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

  1. Consistency: Use consistent colors and fonts across your app and map
  2. Accessibility: Ensure sufficient contrast for text and important features
  3. Performance: Keep style complexity reasonable for smooth rendering
  4. Branding: Integrate your brand colors and elements naturally
  5. 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.).