Android Kotlin实现MapBox自定义聚类图标:多色边框+数量显示
在Android Kotlin中实现MapBox自定义分色边框聚类图标
核心思路
要实现带分色边框和数量显示的自定义聚类图标,需要替换MapBox默认的聚类渲染逻辑,通过Canvas手动绘制图标,并利用ClusterIconProvider接口将自定义图标注入到聚类系统中。
步骤1:创建聚类图标生成工具类
这个类负责绘制带分色边框、深色背景和数量文本的Bitmap图标:
class ClusterIconGenerator(private val context: Context) { private val paint = Paint(Paint.ANTI_ALIAS_FLAG) private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.WHITE textSize = context.resources.getDimension(R.dimen.cluster_text_size) textAlign = Paint.Align.CENTER typeface = Typeface.DEFAULT_BOLD } private val iconSize = context.resources.getDimensionPixelSize(R.dimen.cluster_icon_size) private val borderWidth = context.resources.getDimensionPixelSize(R.dimen.cluster_border_width) private val innerRadius = (iconSize / 2) - borderWidth fun generateClusterIcon(colors: List<Int>, count: Int): Bitmap { val bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) // 绘制分色边框 drawSegmentedBorder(canvas, colors) // 绘制内部深色背景 paint.color = Color.parseColor("#333333") canvas.drawCircle(iconSize/2f, iconSize/2f, innerRadius.toFloat(), paint) // 绘制数量文本 val text = count.toString() val textBounds = Rect() textPaint.getTextBounds(text, 0, text.length, textBounds) val textY = (iconSize / 2) + (textBounds.height() / 2) canvas.drawText(text, iconSize/2f, textY, textPaint) return bitmap } private fun drawSegmentedBorder(canvas: Canvas, colors: List<Int>) { if (colors.isEmpty()) return val segmentAngle = 360f / colors.size val outerRadius = iconSize / 2f colors.forEachIndexed { index, color -> paint.color = color val startAngle = index * segmentAngle // 绘制扇形边框段 canvas.drawArc( borderWidth.toFloat(), borderWidth.toFloat(), iconSize - borderWidth.toFloat(), iconSize - borderWidth.toFloat(), startAngle, segmentAngle, true, paint.apply { style = Paint.Style.STROKE; strokeWidth = borderWidth.toFloat() } ) } } }
步骤2:实现自定义ClusterIconProvider
这个接口是MapBox聚类系统和自定义图标之间的桥梁:
class CustomClusterIconProvider(private val context: Context) : ClusterIconProvider { private val iconGenerator = ClusterIconGenerator(context) override fun getIcon(cluster: Cluster): Bitmap { // 提取聚类中所有标注的颜色 val colors = cluster.members.mapNotNull { member -> (member as? PointAnnotation)?.getStringProperty("color")?.let { colorStr -> try { Color.parseColor(colorStr) } catch (e: IllegalArgumentException) { Color.GRAY // 颜色解析失败时用默认灰色 } } } return iconGenerator.generateClusterIcon(colors, cluster.pointCount) } }
步骤3:修改PointAnnotationManager配置
禁用MapBox默认的聚类样式,替换为自定义图标提供器:
private fun onMapReady() { if (username.isNotEmpty()) { val annotationPlugin = binding.mapView.annotations // 禁用默认聚类的circle和text渲染,避免和自定义图标冲突 val clusterOptions = ClusterOptions( circleColor = Color.TRANSPARENT, textColor = Color.TRANSPARENT ) val annotationConfig = AnnotationConfig( annotationSourceOptions = AnnotationSourceOptions( clusterOptions = clusterOptions ), // 设置自定义聚类图标提供器 clusterIconProvider = CustomClusterIconProvider(requireContext()) ) pointAnnotationManager = annotationPlugin.createPointAnnotationManager(annotationConfig) val destinations = listOf( Point.fromLngLat(77.6396767, 12.9754017), Point.fromLngLat(77.5912, 12.9797), Point.fromLngLat(77.088590, 13.339040), Point.fromLngLat(77.6212, 12.9797) ) viewModel.getDeviceList().observe(viewLifecycleOwner) { cariTagDevices -> if (cariTagDevices.isNotEmpty()) { savedDeviceList = cariTagDevices for (i in cariTagDevices.indices) { val device = cariTagDevices[i] if (i < destinations.size) { val iconBitmap = CariTagUtils.getMarkerBitmapFromView( requireContext(), CariTagUtils.getIcon(device.iconId), device.colorValue ) if (iconBitmap != null) { prepareAnnotationMarker(iconBitmap, device, destinations[i]) } } } } } mapboxMap.loadStyleUri(Style.MAPBOX_STREETS) { viewAnnotationManager = binding.mapView.viewAnnotationManager circleAnnotationManager = mapView.annotations.createCircleAnnotationManager() polylineAnnotationManager = mapView.annotations.createPolylineAnnotationManager() } } }
步骤4:修改标注创建逻辑,存储颜色信息
确保每个PointAnnotation携带对应设备的颜色属性,方便聚类时提取:
private fun prepareAnnotationMarker(iconBitmap: Bitmap, device: CariTagDevice, point: Point) { val pointAnnotation = PointAnnotation().apply { geometry = point icon = IconFactory.getInstance(requireContext()).fromBitmap(iconBitmap) // 将设备颜色存储到标注属性中,格式为十六进制字符串(如"#FF0000") setStringProperty("color", device.colorValue) } pointAnnotationManager.create(pointAnnotation) }
步骤5:添加尺寸资源
在res/values/dimens.xml中定义图标相关尺寸:
<dimen name="cluster_icon_size">50dp</dimen> <dimen name="cluster_border_width">6dp</dimen> <dimen name="cluster_text_size">18sp</dimen>
关键说明
- 分色边框会根据聚类内标注的数量自动均分角度,每个角度段对应一个标注的颜色
- 自定义图标完全替代了MapBox默认的聚类圆圈和文本,避免样式冲突
- 标注的颜色通过属性存储,聚类时动态提取,确保边框颜色和原始标注一致
内容的提问来源于stack exchange,提问作者megha venkat
相关产品推荐
相关产品推荐

