Bitmap 和 Drawable

224 阅读8分钟

Bitmap 是什么

Bitmap 是位图信息的存储,即⼀个矩形图像每个像素的颜⾊信息的存储器。一个可能的bitmap的信息:

image:
width:640
height:400
pixel:
ffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOOffOOOO

Drawable 是什么

Drawable 是 Android 绘图系统中的“绘制工具”——本质上是一套“如何画”的规则或描述,而不是一张图片或者像素本身。

  • Drawable 内部存储的不是像素数据,而是绘制规则。这些规则既可以简单到只是一种颜色,也可以复杂到支持动画、渐变、图层叠加等效果。
  • 调用 Drawable.draw(Canvas) 时,Drawable 会把内容绘制到指定的 Canvas 上。
  • Drawable 本身不能独立显示,它必须被绘制到 Canvas(通常由 View 提供)上才会显示到屏幕。
  • 在调用 draw() 方法前,通常需要用 setBounds() 指定绘制区域,否则内容可能不会显示或显示不全。

动态绘制功能

  • Drawable 可以是静态的(比如只画一张图片),也可以是动态的,即根据状态或参数实时生成内容。

    • 例如,StateListDrawable 能根据 View 的不同状态(按下、选中、失焦等)切换不同的 Drawable。
    • ShapeDrawableGradientDrawable 能在运行时生成不同形状、不同渐变色的图形,无需准备多个图片资源。

示例:创建一个渐变背景并应用到 View

val drawable = GradientDrawable(
    GradientDrawable.Orientation.TOP_BOTTOM,
    intArrayOf(Color.RED, Color.BLUE)
)
drawable.cornerRadius = 10f
imageView.background = drawable

Drawable 的多样性

Drawable 的类型非常丰富,不只限于图片:

  • BitmapDrawable:直接包装 Bitmap 数据,显示静态图片。
  • ShapeDrawable:绘制几何图形(如矩形、圆形等)。
  • GradientDrawable:绘制渐变背景。
  • VectorDrawable:支持矢量图形(缩放无损)。
  • StateListDrawable:状态选择器,根据不同状态切换 Drawable。
  • LayerDrawable:可叠加多个 Drawable,形成复杂效果。
  • AnimationDrawable:支持帧动画,逐帧切换不同 Drawable。

这些类型为 Android UI 提供了强大的绘制和灵活性,开发者不必为每种状态准备一堆图片资源。


Drawable 的绘制机制

  • Drawable 的核心职责是负责如何把内容绘制到 Canvas
  • Android 的 View 在 onDraw(Canvas) 方法里,常常直接或间接调用 Drawable 的 draw() 方法来完成最终显示。
  • 开发者可以自定义 Drawable,扩展 Drawable 类,重写 draw(),以实现各种特殊绘制逻辑。

自定义 Drawable 示例

class CustomDrawable : Drawable() {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    override fun draw(canvas: Canvas) {
        paint.color = Color.RED
        canvas.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), 50f, paint)
    }

    override fun setAlpha(alpha: Int) {
        paint.alpha = alpha
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        paint.colorFilter = colorFilter
    }

    override fun getOpacity(): Int {
        return PixelFormat.TRANSLUCENT
    }
}

性能与资源优化

使用 Drawable 的优势不仅在于灵活、动态,性能和资源利用也优于直接使用 Bitmap 图片

  • 资源复用:一套绘制规则可供多处使用(例如多个 View 共享同一个 Drawable 实例),降低内存消耗。
  • 节省内存:矢量(如 VectorDrawable)、渐变(如 GradientDrawable)、图形(ShapeDrawable)等只需规则无需大图片,自动适配不同分辨率,节省大量内存。
  • 分辨率独立:矢量、形状和渐变 Drawable 可自动适配各种屏幕密度,无需准备不同尺寸的图片资源。
  • 灵活动态:可按需生成、修改、动画,而无需事先准备不同状态的图片。

小结

  • Drawable 是一种绘制规则,不是图片数据本身,其绘制逻辑可以非常丰富。
  • Drawable 类型多样,既可以表示图片,也可以表示动态生成的形状、渐变、动画等。
  • 它们是 Android 绘制系统的基础,效率高,适合做背景、图标、动态效果和自定义绘制。

Bitmap 和 Drawable 的互相转换

Bitmap 表示像素数据,是图片的最底层数据结构(像素矩阵);Drawable 是绘制规则的封装,是 Android 图形系统用来“如何画”的对象。二者互补,但不是“直接互转”,而是可以从一个创建另一个

1. Bitmap → Drawable

用 Bitmap 作为内容来构造 Drawable 的规则

  • BitmapDrawable 包装:

    val drawable = BitmapDrawable(resources, bitmap)
    
  • 然后你可以把 drawable 设置给 ImageView、Canvas、或者任何需要 Drawable 的地方。

2. Drawable → Bitmap

规则变成图片

  • 如果是 BitmapDrawable,直接拿出 bitmap:

    val bitmap = (drawable as BitmapDrawable).bitmap
    
  • 其它类型(比如 ShapeDrawable、VectorDrawable):

    • 创建一个和 drawable 一样大小的 Bitmap 和 Canvas
    • 设置 bounds
    • draw 到这个 Canvas 上
    val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    drawable.setBounds(0, 0, width, height)
    drawable.draw(canvas)
    

本质:

  • Bitmap 负责存储像素(内容)。
  • Drawable 负责描述如何绘制(规则)。
  • “转换”其实就是把内容和规则结合起来,按需生成对象。

Drawable 和 View 的区别

特性DrawableView
定义绘制规则封装(只管怎么画,不处理交互和布局)UI 组件(能画、能布局、能处理交互)
用途作为图标、背景、动画、形状等构建界面元素、控件容器、响应交互
事件响应不能响应(自身没事件分发能力)能直接响应触摸、点击等事件
生命周期只有 setBounds、draw(简单)onMeasure、onLayout、onDraw、事件等(复杂)
性能轻量、高效,不占 UI 层级较重,涉及测量、布局和事件,性能略低
依赖关系依赖 Canvas 或绑定到 View 上直接存在于 UI 层级
常见场景画 icon、shape、渐变、动画、View 背景各种 UI 控件、布局、交互

重点说明

  • Drawable 是“绘图规则”,没有自己的像素,也没有独立显示能力,不能处理事件,不能参与布局。
  • View 是“UI组件”,可以响应事件、参与布局,内部可通过 Drawable 进行绘制。
  • Drawable 画到屏幕上通常依赖 View 的 onDraw(Canvas) 或者直接用 Canvas。

自定义 Drawable 的用法

  1. 实现自定义 Drawable

    • 必须继承 Drawable,并重写下面几个方法:

      • draw(Canvas)
      • setAlpha()(如需透明度,建议顺便实现 getAlpha()
      • setColorFilter()(如支持颜色变化)
      • getOpacity()
  2. 举例:

    class CustomDrawable : Drawable() {
        private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    
        override fun draw(canvas: Canvas) {
            paint.color = Color.RED
            canvas.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), 50f, paint)
        }
    
        override fun setAlpha(alpha: Int) { paint.alpha = alpha }
        override fun setColorFilter(filter: ColorFilter?) { paint.colorFilter = filter }
        override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
    }
    
  3. 用途:

    • 需要在多个自定义 View 中复用一套绘制代码,直接把 Drawable 拿出来用,不用到处复制代码。
    • 比如:股票 K 线图、通用自定义控件的底层绘制。

Drawable 的动态特性与性能优势

  • 动态绘制:可以根据状态或数据实时改变内容(如 StateListDrawable、ShapeDrawable、GradientDrawable)。
  • 省内存:通过矢量图、代码绘制,减少 bitmap 资源消耗。
  • 分辨率自适应:VectorDrawable 不怕分辨率变化,适合多屏幕密度场景。

Bitmap 和 Drawable 用法总结

  • Bitmap 适合处理底层像素数据,比如图像编辑、像素操作。
  • Drawable 适合做 UI 绘制规则封装,比如 shape、动画、状态切换。
  • View 适合处理完整 UI 交互,包括布局、响应触摸、复用 Drawable 等。

小结

  • 需要“画什么”就用 Drawable(或者 Bitmap)。
  • 需要“能被点击/滑动/布局/管理子元素/复杂 UI”就用 View。
  • 二者各司其职,掌握各自的职责和局限性即可灵活运用。

学后测验

一、单选题

  1. 下列关于 Bitmap 的描述,哪一项是正确的?

    • A. Bitmap 仅仅存储绘制规则
    • B. Bitmap 可以直接响应用户事件
    • C. Bitmap 是像素矩阵,存储具体的颜色数据
    • D. Bitmap 一定是矢量图形

    答案:C
    解析:Bitmap 是像素矩阵,直接记录每个像素的颜色,其他选项均错误。

  2. 关于 Drawable 的本质,下列哪项描述最准确?

    • A. Drawable 只能用来绘制 Bitmap
    • B. Drawable 本质是绘制规则,可以动态生成内容
    • C. Drawable 必须有具体像素信息
    • D. Drawable 只能用作背景

    答案:B
    解析:Drawable 代表一套绘制规则,不一定绑定具体像素,也可动态生成。


二、多选题

  1. 下列哪些属于 Drawable 的典型类型?(多选)

    • A. BitmapDrawable
    • B. GradientDrawable
    • C. StateListDrawable
    • D. AnimationDrawable
    • E. PaintDrawable

    答案:A、B、C、D
    解析:E 不是标准 Android Drawable,其他都是常用 Drawable 类型。

  2. 关于 Drawable 和 View 的区别,下列哪些说法是正确的?(多选)

    • A. Drawable 只负责绘制,不负责事件分发
    • B. View 既能绘制内容也能响应用户交互
    • C. Drawable 有独立的布局和测量逻辑
    • D. View 作为 UI 组件,参与布局系统

    答案:A、B、D
    解析:C 错,Drawable 不参与布局和测量。


三、判断题

  1. ( ) 你可以通过 BitmapDrawable.getBitmap() 方法,将任何 Drawable 转换成 Bitmap。

    答案:错
    解析:只有 BitmapDrawable 支持直接 getBitmap(),其他类型 Drawable 需要手动用 Canvas 绘制到 Bitmap。

  2. ( ) VectorDrawable 能够自适应不同屏幕密度,通常比 BitmapDrawable 更节省内存。

    答案:对
    解析:矢量图形天然分辨率无关、内存占用低。

  3. ( ) Drawable 必须绑定到 View 上才能绘制到屏幕。

    答案:错
    解析:Drawable 可以直接 draw 到 Canvas(比如自定义 View 的 onDraw 内部)。


四、简答题

  1. 简述 Drawable 转 Bitmap 的常见实现方法,以及为什么有些 Drawable 不能直接 getBitmap()。

    参考答案
    常用做法是:

      1. 创建一个 Bitmap 对象和对应的 Canvas。
      1. 调用 Drawable.setBounds() 设置绘制区域。
      1. 使用 Drawable.draw(Canvas) 方法把内容绘制到 Canvas(即 Bitmap)。
      1. 返回得到的 Bitmap。
        只有 BitmapDrawable 内部有 bitmap 字段,其他 Drawable(如 ShapeDrawable、GradientDrawable)是“规则”,不是像素数组,不能 getBitmap(),必须通过绘制获取。

五、编程题

  1. 请写出一段 Kotlin 代码,将任意 Drawable 转换为指定宽高的 Bitmap。请简要注释关键步骤。

    参考答案

    fun drawableToBitmap(drawable: Drawable, width: Int, height: Int): Bitmap {
        // 1. 新建指定宽高的 Bitmap
        val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
        // 2. 用 Bitmap 创建 Canvas
        val canvas = Canvas(bitmap)
        // 3. 设置 Drawable 的绘制区域
        drawable.setBounds(0, 0, width, height)
        // 4. 绘制到 Canvas(即 Bitmap)
        drawable.draw(canvas)
        // 5. 返回 Bitmap
        return bitmap
    }
    

    解析:通过 setBounds、draw,支持所有 Drawable 类型转换成 Bitmap。


六、综合题

  1. 简述自定义 Drawable 的主要应用场景,以及和自定义 View 相比的优劣。

    参考答案
    主要用于:

    • 代码复用(多个 View 共享同一套绘制逻辑)
    • 动态生成形状、图案、渐变、动画
    • 状态响应(如 StateListDrawable)
      优点:轻量、高效、不参与布局、便于复用
      缺点:不能独立响应用户交互,不参与布局系统