Icons (Experimental)
The mapconductor-icons module provides custom-drawn marker icons with programmatic styling. This experimental module offers vector-style icons that can be customized with colors, sizes, and other properties at runtime.
⚠️ Experimental Module: This module is experimental and may change significantly in future versions. Use in production with caution.
Overview
Section titled “Overview”The icons module creates high-quality marker icons using Canvas drawing operations, providing:
- Scalable Vector Graphics: Icons scale smoothly at any size
- Runtime Customization: Change colors, sizes, and properties dynamically
- Optimized Caching: Automatic bitmap caching for performance
- Consistent Appearance: Same visual style across all map providers
Installation
Section titled “Installation”Add the icons module to your build.gradle:
dependencies { implementation "com.mapconductor:mapconductor-icons"
// Required: Bom module implementation "com.mapconductor:mapconductor-bom:$version" // Required: Core module implementation "com.mapconductor:core"
// Choose your map provider implementation "com.mapconductor:for-googlemaps"}Available Icons
Section titled “Available Icons”CircleIcon
Section titled “CircleIcon”A simple circular marker icon with customizable fill and stroke:
import com.mapconductor.icons.CircleIcon
// Basic circle iconval basicCircle = CircleIcon()
// Customized circle iconval customCircle = CircleIcon( fillColor = Color.Blue, strokeColor = Color.White, strokeWidth = 2.dp, scale = 1.2f, iconSize = 32.dp)CircleIcon Properties
Section titled “CircleIcon Properties”fillColor: Color: Interior color of the circle (default:Color.Red)strokeColor: Color: Border color (default:Color.White)strokeWidth: Dp: Border thickness (default: from Settings)scale: Float: Size multiplier (default:1.0f)iconSize: Dp: Base size of the icon (default: from Settings)debug: Boolean: Show debug outline (default:false)
FlagIcon
Section titled “FlagIcon”A flag-style marker icon with pole and customizable flag:
import com.mapconductor.icons.FlagIcon
// Basic flag iconval basicFlag = FlagIcon()
// Customized flag iconval customFlag = FlagIcon( fillColor = Color.Green, strokeColor = Color.Black, strokeWidth = 1.5.dp, scale = 1.0f, iconSize = 40.dp)FlagIcon Properties
Section titled “FlagIcon Properties”fillColor: Color: Color of the flag and pole (default:Color.Red)strokeColor: Color: Outline color (default:Color.White)strokeWidth: Dp: Outline thickness (default: from Settings)scale: Float: Size multiplier (default:1.0f)iconSize: Dp: Base size of the icon (default: from Settings)debug: Boolean: Show debug outline (default:false)
Basic Usage
Section titled “Basic Usage”Simple Icon Usage
Section titled “Simple Icon Usage”@Composablefun BasicIconExample() { val circleIcon = CircleIcon( fillColor = Color.Blue, strokeColor = Color.White )
val flagIcon = FlagIcon( fillColor = Color.Red, strokeColor = Color.Black )
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { Marker( position = GeoPoint.fromLatLong(37.7749, -122.4194), icon = circleIcon )
Marker( position = GeoPoint.fromLatLong(37.7849, -122.4094), icon = flagIcon ) }}Dynamic Icon Customization
Section titled “Dynamic Icon Customization”@Composablefun DynamicIconExample() { var iconColor by remember { mutableStateOf(Color.Red) } var iconSize by remember { mutableStateOf(32.dp) }
val dynamicIcon = CircleIcon( fillColor = iconColor, strokeColor = Color.White, iconSize = iconSize )
Column { // Color picker Row { Button(onClick = { iconColor = Color.Red }) { Text("Red") } Button(onClick = { iconColor = Color.Blue }) { Text("Blue") } Button(onClick = { iconColor = Color.Green }) { Text("Green") } }
// Size slider Slider( value = iconSize.value, onValueChange = { iconSize = it.dp }, valueRange = 16f..64f ) Text("Size: ${iconSize.value.toInt()}dp")
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { Marker( position = GeoPoint.fromLatLong(37.7749, -122.4194), icon = dynamicIcon ) } }}Advanced Usage
Section titled “Advanced Usage”Category-Based Icons
Section titled “Category-Based Icons”@Composablefun CategoryIconExample() { data class POI( val name: String, val category: String, val position: GeoPointInterface ) : java.io.Serializable
val pois = listOf( POI("Restaurant", "food", GeoPoint.fromLatLong(37.7749, -122.4194)), POI("Hotel", "lodging", GeoPoint.fromLatLong(37.7849, -122.4094)), POI("Gas Station", "fuel", GeoPoint.fromLatLong(37.7649, -122.4294)) )
fun getIconForCategory(category: String) = when (category) { "food" -> CircleIcon(fillColor = Color.Red, strokeColor = Color.White) "lodging" -> FlagIcon(fillColor = Color.Blue, strokeColor = Color.White) "fuel" -> CircleIcon(fillColor = Color.Yellow, strokeColor = Color.Black) else -> CircleIcon(fillColor = Color.Gray, strokeColor = Color.White) }
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { pois.forEach { poi -> Marker( position = poi.position, icon = getIconForCategory(poi.category), extra = poi.name ) } }}Icon Theming
Section titled “Icon Theming”object IconTheme { data class Theme( val primaryColor: Color, val secondaryColor: Color, val strokeColor: Color, val strokeWidth: Dp ) : java.io.Serializable
val light = Theme( primaryColor = Color(0xFF2196F3), secondaryColor = Color(0xFFFFFFFF), strokeColor = Color(0xFF000000), strokeWidth = 1.dp )
val dark = Theme( primaryColor = Color(0xFF1976D2), secondaryColor = Color(0xFF424242), strokeColor = Color(0xFFFFFFFF), strokeWidth = 1.dp )}@Composablefun ThemedIconExample() { val isDarkTheme = isSystemInDarkTheme() val theme = if (isDarkTheme) IconTheme.dark else IconTheme.light
val themedCircle = CircleIcon( fillColor = theme.primaryColor, strokeColor = theme.strokeColor, strokeWidth = theme.strokeWidth )
val themedFlag = FlagIcon( fillColor = theme.primaryColor, strokeColor = theme.strokeColor, strokeWidth = theme.strokeWidth )
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { Marker( position = GeoPoint.fromLatLong(37.7749, -122.4194), icon = themedCircle )
Marker( position = GeoPoint.fromLatLong(37.7849, -122.4094), icon = themedFlag ) }}Icon Animation
Section titled “Icon Animation”@Composablefun AnimatedIconExample() { var scale by remember { mutableStateOf(1.0f) } var color by remember { mutableStateOf(Color.Red) }
// Animate scale LaunchedEffect(Unit) { while (true) { animate( initialValue = 1.0f, targetValue = 1.5f, animationSpec = tween(1000) ) { value, _ -> scale = value }
animate( initialValue = 1.5f, targetValue = 1.0f, animationSpec = tween(1000) ) { value, _ -> scale = value } } }
// Animate color LaunchedEffect(Unit) { val colors = listOf(Color.Red, Color.Blue, Color.Green, Color.Yellow) var index = 0 while (true) { delay(2000) index = (index + 1) % colors.size color = colors[index] } }
val animatedIcon = CircleIcon( fillColor = color, strokeColor = Color.White, scale = scale )
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { Marker( position = GeoPoint.fromLatLong(37.7749, -122.4194), icon = animatedIcon ) }}Icon Properties and Behavior
Section titled “Icon Properties and Behavior”Anchor Points
Section titled “Anchor Points”Icons have specific anchor points that determine how they’re positioned relative to the geographic coordinate:
// CircleIcon: anchored at left-center (0.0, 0.5)// FlagIcon: anchored near the base of the pole (0.176, 0.91)Info Window Anchors
Section titled “Info Window Anchors”Info windows (if supported by the provider) anchor to different points:
// CircleIcon info window anchor: center of the circle (0.5, 0.5)// FlagIcon info window anchor: top of the flag (0.5, 0.0)Performance Considerations
Section titled “Performance Considerations”Bitmap Caching
Section titled “Bitmap Caching”Icons automatically cache their rendered bitmaps based on property hash:
// These will share the same cached bitmapval icon1 = CircleIcon(fillColor = Color.Red, strokeColor = Color.White)val icon2 = CircleIcon(fillColor = Color.Red, strokeColor = Color.White)
// This will create a new cached bitmapval icon3 = CircleIcon(fillColor = Color.Blue, strokeColor = Color.White)Memory Management
Section titled “Memory Management”- Cached bitmaps are automatically managed
- Icons with identical properties share bitmap instances
- Large icons use more memory - use appropriate sizes
Debugging Icons
Section titled “Debugging Icons”Enable debug mode to visualize icon boundaries and anchor points:
@Composablefun DebugIconExample() { val debugIcon = CircleIcon( fillColor = Color.Red, strokeColor = Color.White, debug = true // Show debug outline and crosshairs )
// Replace MapView with your chosen map provider, such as GoogleMapView, MapboxMapView MapView(state = mapViewState) { Marker( position = GeoPoint.fromLatLong(37.7749, -122.4194), icon = debugIcon ) }}Debug mode shows:
- Icon bounding rectangle (black outline)
- Center crosshairs (black lines)
- Actual drawn content
Custom Icon Development
Section titled “Custom Icon Development”Extending AbstractMarkerIcon
Section titled “Extending AbstractMarkerIcon”To create custom icons, extend AbstractMarkerIcon:
class CustomIcon( private val fillColor: Color = Color.Blue, override val scale: Float = 1.0f, override val iconSize: Dp = 32.dp, override val debug: Boolean = false) : AbstractMarkerIcon() {
override val anchor: Offset = Offset(0.5f, 0.5f) override val infoAnchor: Offset = Offset(0.5f, 0.0f)
override fun toBitmapIcon(): BitmapIcon { val id = "custom_icon_${hashCode()}".hashCode() BitmapIconCache.get(id)?.let { return it }
val canvasSize = ResourceProvider.dpToPx(iconSize.value * scale) val bitmap = createBitmap(canvasSize.toInt(), canvasSize.toInt()) val canvas = Canvas(bitmap)
// Custom drawing code here val paint = Paint().apply { color = fillColor.toArgb() style = Paint.Style.FILL isAntiAlias = true }
canvas.drawRect(0f, 0f, canvasSize.toFloat(), canvasSize.toFloat(), paint)
val result = BitmapIcon( bitmap = bitmap, anchor = anchor, size = Size(canvasSize.toFloat(), canvasSize.toFloat()) ) BitmapIconCache.put(id, result) return result }}Best Practices
Section titled “Best Practices”- Consistent Sizing: Use consistent icon sizes for similar marker types
- Color Accessibility: Ensure sufficient contrast between fill and stroke colors
- Performance: Reuse identical icon instances to benefit from caching
- Scale Appropriately: Consider map zoom levels when choosing icon sizes
- Test Across Providers: Verify icon appearance on all target map providers
Limitations
Section titled “Limitations”- Limited Icon Set: Currently only CircleIcon and FlagIcon are available
- Static Shapes: Icons are drawn programmatically, not from vector files
- Provider Differences: Minor rendering differences may occur between map providers
- Memory Usage: Large icons or many unique icon variations consume more memory
Migration and Compatibility
Section titled “Migration and Compatibility”This module is experimental and APIs may change. When migrating:
- Test thoroughly with your specific use cases
- Monitor memory usage with large numbers of unique icons
- Have fallback options for critical functionality
- Report issues to help improve the module
The icons module provides a foundation for custom marker styling while maintaining the unified MapConductor API across providers.