Jetpack Compose中为BitmapDescriptor添加颜色滤镜
2025-03-23 08:35:15
Jetpack Compose 中给 BitmapDescriptor/Drawable 应用颜色滤镜
你在用 Jetpack Compose 画 Google 地图上的折线 (Polyline) 时,想给末端的 BitmapDescriptor
加个和折线一样的颜色,这可咋整?你发现以前用 Android Views 的那一套,像 setColorFilter
,在 Compose 里不好使了,对吧?别急,咱来捋捋这事儿。
咋就不能直接用老办法了?
问题的关键在于,BitmapDescriptorFactory.fromResource
返回的 BitmapDescriptor
对象,它本身并不直接提供修改颜色的方法。它不像 Android Views 里的 Drawable
那样有 setColorFilter
。 而且在Compose环境下, 使用旧有的方式处理位图与新的组合系统并不兼容。
几招搞定颜色滤镜
既然老路不通,咱就得找新路子。下面有几种办法,都能给你的 BitmapDescriptor
染上色:
1. 使用 ImageBitmap.imageResource
和 Canvas
这种方法,先把你的 Drawable 资源转成 ImageBitmap
,然后利用 Compose 提供的 Canvas
来操作。
原理和步骤:
- 加载 Drawable: 通过
ImageBitmap.imageResource
把资源文件(比如你的箭头R.drawable.arrow
)加载成ImageBitmap
。 - 创建新画布和位图: 新建一个和
ImageBitmap
等大的MutableImageBitmap
。并创建基于新位图的Canvas
。 - 设置颜色滤镜: 利用
DrawScope
的drawImage
方法, 并设置colorFilter
参数进行绘制,这个方法可以让你在绘制图片时加上颜色滤镜。 - 转成 BitmapDescriptor: 将
MutableImageBitmap
转为Bitmap
,最后通过BitmapDescriptorFactory.fromBitmap()
创建BitmapDescriptor
。
代码示例:
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.res.imageResource
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import android.graphics.Bitmap
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.asImageBitmap
fun getColoredBitmapDescriptor(resourceId: Int, color: Color, context: Context): BitmapDescriptor {
val imageBitmap = ImageBitmap.imageResource(context.resources, resourceId)
val mutableBitmap = Bitmap.createBitmap(
imageBitmap.width,
imageBitmap.height,
Bitmap.Config.ARGB_8888
).asImageBitmap()
val newCanvas = Canvas(mutableBitmap)
val paint = Paint().apply {
colorFilter = ColorFilter.tint(color)
}
newCanvas.drawImage(imageBitmap, paint)
return BitmapDescriptorFactory.fromBitmap(mutableBitmap.asAndroidBitmap())
}
// 在你的 Compose 代码里这样用:
val arrowBitmapDescriptor: BitmapDescriptor by remember(polylineColor) {
mutableStateOf(getColoredBitmapDescriptor(R.drawable.arrow, polylineColor, context))
}
Polyline(
points = polylinePoints,
endCap = CustomCap(arrowBitmapDescriptor, 30f),
color = polylineColor // 假设 polylineColor 是你想要的颜色
)
改进-扩展函数
你可以通过编写ImageBitmap
的扩展函数的方式简化流程:
fun ImageBitmap.tint(color: Color): BitmapDescriptor {
val tintedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).asImageBitmap()
Canvas(tintedBitmap).drawImage(
this,
androidx.compose.ui.graphics.Paint().apply {
colorFilter = ColorFilter.tint(color)
}
)
return BitmapDescriptorFactory.fromBitmap(tintedBitmap.asAndroidBitmap())
}
//使用
val originalBitmapDescriptor = ImageBitmap.imageResource(context.resources, R.drawable.arrow)
val arrowBitmapDescriptor: BitmapDescriptor by remember(polylineColor) {
mutableStateOf(originalBitmapDescriptor.tint(polylineColor))
}
2. 直接操作 Bitmap
像素 (进阶)
如果你对性能有极致追求,可以考虑更底层的操作.直接摆弄Bitmap
的像素值。
原理:
每个像素的颜色,实际上是用一个整数表示的,通常是 ARGB 格式(Alpha, Red, Green, Blue)。我们可以遍历 Bitmap
的每个像素,修改它的颜色值,实现颜色滤镜效果。
代码示例:
import android.graphics.Bitmap
import android.graphics.Color
import androidx.compose.ui.graphics.Color as ComposeColor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.BitmapDescriptor
fun colorizeBitmap(bitmap: Bitmap, color: ComposeColor): BitmapDescriptor {
val width = bitmap.width
val height = bitmap.height
val pixels = IntArray(width * height)
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
val targetColor = color.toArgb()
val targetRed = Color.red(targetColor)
val targetGreen = Color.green(targetColor)
val targetBlue = Color.blue(targetColor)
for (i in pixels.indices) {
val pixel = pixels[i]
//简单地将原始像素的RGB与目标颜色的RGB相乘
val red = (Color.red(pixel) * targetRed) / 255
val green = (Color.green(pixel) * targetGreen) / 255
val blue = (Color.blue(pixel) * targetBlue) / 255
pixels[i] = Color.argb(Color.alpha(pixel), red, green, blue)
}
val newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
newBitmap.setPixels(pixels, 0, width, 0, 0, width, height)
return BitmapDescriptorFactory.fromBitmap(newBitmap)
}
// 用法:
// 先从资源创建一个普通的 Bitmap
val originalBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.arrow)
val coloredArrow = colorizeBitmap(originalBitmap, polylineColor) // polylineColor 是你的目标颜色
注意:
直接操作像素比较耗费计算资源,适合小图片。大图片可能导致卡顿。而且不同的色彩混合模式算法不同. 上面的示例只是一个简单的颜色混合模式。
3. 利用现成的库(如果条件允许)
有些第三方库已经封装好了这些操作。如果你不介意引入新库,可以试试. 比如 Coil 库:
//添加依赖(build.gradle.kts (Module :app))
implementation("io.coil-kt:coil-compose:2.5.0") //检查最新版本!
//由于库本身不能直接输出BitmapDescriptor,仍然需要手动进行转换.
//需要和第一种或第二种方式结合
然后使用其提供的rememberAsyncImagePainter
配合colorFilter
:
Coil 自身无法处理将ImagePainter 转为 BitmapDescriptor
。
所以这种方法更多的是简化了图片加载部分,然后仍然使用方法1 或 2 中的方法将其转换.
方案选择
- 如果简单起见,推荐用第一种,代码可读性比较好, 也比较符合 Compose 的设计思想。
- 如果你对性能要求高,而且处理的是小图标,可以试试第二种。
- 如果能用库,且库本身有支持,当然是更省事的.
以上三种方法,都能实现给BitmapDescriptor
加颜色滤镜的需求。挑一个你顺手的用吧!