零、概念
Bitmap(位图) :它是极其具体的像素数据。它就像一张冲洗出来的实体照片,实实在在地占据着内存(宽 × 高 × 每个像素的字节数)。Drawable(可绘制对象) :它是高度抽象的"绘画指令" 。它代表"任何可以被画到 Canvas 上的东西"。它可以是一张 Bitmap(BitmapDrawable),也可以是一堆适量路径(VectorDrawable),还可以是一个纯色色块(ColorDrawable)或者一个带圆角的矩形(ShapeDrawable)。
一、 Bitmap 转换为 Drawable (具体 -> 抽象)
这个过程非常简单。既然 Drawable 是一个宽泛的概念,我们只需要找一个专门负责包装 Bitmap 的 Drawable 子类------BitmapDrawable,把它包起来就可以了。
代码实现(Kotlin):
Kotlin
// 假设你已经有了一个 bitmap 对象
val bitmap: Bitmap = ...
// 转换:使用 Resources 主要是为了让 Drawable 知道当前的屏幕密度(dpi),从而正确缩放
val drawable: Drawable = BitmapDrawable(context.resources, bitmap)
底层逻辑:
这只是包了一层壳,BitmapDrawable 的 draw(Canvas) 方法底层做的就是调用 Canvas.drawBitmap()。
二、 Drawable 转换为 Bitmap (抽象 -> 具体)
这个过程就相对复杂了。因为你要把一个"抽象的绘画指令"强制变成"实实在在的像素矩阵"。
我们分为两种情况来处理:
1. 它是 BitmapDrawable 时的捷径
如果这个 Drawable 的真身本来就是一张位图(比如你从 ImageView 里拿出来的 jpg/png 图片),我们直接把外壳剥掉就行了。
Kotlin
if (drawable is BitmapDrawable) {
val bitmap: Bitmap? = drawable.bitmap
if (bitmap != null) {
// 直接拿到,完事!
return bitmap
}
}
2. 通用转换:万物皆可 Canvas(强转像素)
如果它是一个矢量图(VectorDrawable)或者一个纯色(ColorDrawable),它本身是不包含像素矩阵的。
这时候,我们需要准备一张"空白的画布和画纸",让这个 Drawable 自己在上面画一遍,画完之后,这张"纸"就是我们要的 Bitmap。
传统的手写代码实现:
Kotlin
fun drawableToBitmap(drawable: Drawable): Bitmap {
// 1. 如果是 BitmapDrawable,走捷径
if (drawable is BitmapDrawable && drawable.bitmap != null) {
return drawable.bitmap
}
// 2. 处理宽高。有些 Drawable(比如纯色)是没有内在宽高的,必须给个保底值(比如 1x1)
val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else 1
val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else 1
// 3. 准备"画纸"(创建一张空白的 Bitmap)
// ARGB_8888 意味着支持透明通道,质量最好
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
// 4. 准备"画板"(将 Bitmap 绑定到 Canvas 上)
val canvas = Canvas(bitmap)
// 5. 规定绘制范围(极其重要!不设置 bounds Drawable 默认画在 0,0,0,0 上,什么都看不到)
drawable.setBounds(0, 0, canvas.width, canvas.height)
// 6. 让 Drawable 开始在我们的画板上作画
drawable.draw(canvas)
// 7. 画作完成,返回画纸
return bitmap
}
现代 Android 开发的"作弊代码" (推荐用法)
如果你正在使用 Kotlin,并且项目中引入了 androidx.core:core-ktx 库(现代 Android 项目几乎默认都会引入),那么 AndroidX 官方已经利用扩展函数帮你把上述极其繁琐的代码全部封装好了。
你只需要一行代码:
Kotlin
import androidx.core.graphics.drawable.toBitmap
// 将 Drawable 转换为 Bitmap,底层逻辑就是上面讲的 Canvas 绘制逻辑
val bitmap = drawable.toBitmap()
// 你甚至可以顺便指定转换后的大小和配置
val scaledBitmap = drawable.toBitmap(width = 100, height = 100, config = Bitmap.Config.ARGB_8888)
总结:
日常开发中,直接使用 AndroidX 提供的 .toBitmap() 扩展函数是最高效、最不易出错的方式。但理解"准备空白 Bitmap -> 绑定 Canvas -> 调用 draw"这套底层第一性原理,对于你未来去实现自定义 View、做离屏渲染(我们之前聊过的 saveLayer 也是类似的开辟空白画板的思想)都是极其有帮助的。