Android绘制图片的几种方式
本文是 Android 音视频开发系列基础篇的第一篇,学习和总结 Android 绘制一张图片的几种方式,如果要问 Android 平台绘制一张图片一共有几种方式,从目前的知识图谱里找到了以下六种,相信随着知识的积累,以后还会学习到别的方法,下面逐一探讨这几种方式。
- 使用 ImageView 绘制一张图片
 
利用 Android 系统提供的 UI 组件 ImageView,能很方便的绘制出一张图片,无论在 xml 布局文件里声明 ImageView 标签, 还是在代码里动态创建 ImageView, 添加到布局文件,两者的使用都非常简单,这里就不再赘述。
- 使用自定义 View 绘制一张图片
 
当 Android 系统提供的 UI 组件不能满足需求时,使用自定义 View 就有了用武之地,通过创建自己的 view 子类,可以精确控制屏幕元素的外观和功能。要使用自定义 view 绘制内容,需要四个基本组件:
 –Bitmap,包含了要绘制内容的像素信息;
 –Canvas,向 bitmap 里写入像素信息;
 –绘图的图元(基本类型),例如一片矩形区域Rect,或者一个路径 Path,或者 text 文本、位图 Bitmap;
 –Paint,描述要绘制内容的颜色和样式。
再简单一点的说 android.graphics 框架将绘制分为两个方面:
    –绘制内容,由 Canvas 负责处理
    –绘制方式,由 Paint 负责处理。
  例如,Canvas 提供绘制线条的方法,Paint 则提供定义线条颜色的方法。Canvas 具有绘制矩形的方法,Paint 则定义是在矩形中填充颜色还是留空。简而言之,Canvas 定义可以在屏幕上绘制的形状,Paint 则定义绘制的每个形状的颜色、样式和字体等。
  这里创建一个类 CustomView 继承 View,复写 onDraw 方法,onDraw() 的参数是 Canvas 对象,使用 Canvas 对象绘制自身。Canvas 类定义了绘制文本、线条、位图和许多其他图形的方法,调用这些绘制方法前,需要先创建 Paint 对象,实现起来非常简单,如果要调整绘制图片的大小,可以使用 Matrix 对象对图片进行缩放。示例代码如下:
1 | class CustomView : View { | 
2 |     private val mPaint = Paint() | 
3 |     private lateinit var mBitmap: Bitmap | 
4 |     private val mMatrix = Matrix() | 
5 | |
6 |     constructor(context: Context) : super(context) { | 
7 |         initView(context) | 
8 |     } | 
9 | |
10 |     constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { | 
11 |         initView(context) | 
12 |     } | 
13 | |
14 |     constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( | 
15 |         context, | 
16 |         attrs, | 
17 |         defStyleAttr | 
18 |     ) { | 
19 |         initView(context) | 
20 |     } | 
21 | |
22 |     fun setBitmap(bitmap: Bitmap) { | 
23 |         mBitmap = bitmap | 
24 |     } | 
25 | |
26 |     private fun initView(context: Context) { | 
27 |         mPaint.style = Paint.Style.STROKE | 
28 |         mPaint.flags = Paint.ANTI_ALIAS_FLAG | 
29 |         mPaint.isAntiAlias = true | 
30 |     } | 
31 | |
32 |     override fun onDraw(canvas: Canvas) { | 
33 |         mMatrix.setScale(0.5f, 0.5f) | 
34 |         canvas.drawBitmap(mBitmap, mMatrix, mPaint) | 
35 |     } | 
36 | } | 
- 使用 SurfaceView 绘制一张图片
 
SurfaceView 是android 系统提供的一个 UI 组件,有关 SurfaceView 的介绍,足够单独写一篇文章,这里通过简单的对比 SurfaceView 和 View,来建立一个对 SurfaceView 的初步认识:
 –View在主线程中对页面进行刷新,而SurfaceView 则开启一个子线程来对页面进行刷新;
 –View在绘图时没有实现双缓冲机制,SurfaceView 在底层机制中就实现了双缓冲机制;
 –View适用于主动更新的情况,而 SurfaceView 适用于被动更新的情况,比如频繁刷新界面。
使用 SurfaceView 的时候,需要通过 SurfaceView 的 SurfaceHolder 注册一个接口回调,监听 SurfaceView 里Surface 的创建和销毁等操作,Surface 创建的时候 使用 Paint 和 Canvas 绘制图像,关键代码如下:
1 | fun drawWithSurfaceView(surfaceView: SurfaceView) { | 
2 |         surfaceView.setZOrderOnTop(true) | 
3 |         val matrix = Matrix() | 
4 |         val surfaceHolder = surfaceView.holder | 
5 |         surfaceHolder.setFormat(PixelFormat.TRANSPARENT) | 
6 | |
7 |         surfaceHolder.addCallback(object : SurfaceHolder.Callback { | 
8 |             override fun surfaceChanged(p0: SurfaceHolder?, p1: Int, p2: Int, p3: Int) {} | 
9 | |
10 |             override fun surfaceDestroyed(surfaceHolder: SurfaceHolder?) {} | 
11 | |
12 |             override fun surfaceCreated(surfaceHolder: SurfaceHolder?) { | 
13 |                 if (surfaceHolder == null) { | 
14 |                     return | 
15 |                 } | 
16 | |
17 |                 val paint = Paint() | 
18 |                 paint.isAntiAlias = true | 
19 |                 paint.style = Paint.Style.STROKE | 
20 |                 paint.flags = Paint.ANTI_ALIAS_FLAG | 
21 | |
22 |                 val canvas = surfaceHolder.lockCanvas() | 
23 |                 matrix.setScale(0.5f,0.5f) | 
24 |                 canvas.drawBitmap(getAssetsBitmap(), matrix, paint) | 
25 |                 surfaceHolder.unlockCanvasAndPost(canvas) | 
26 |             } | 
27 |         }) | 
28 |     } | 
29 | |
30 |     private fun getAssetsBitmap(): Bitmap { | 
31 |         val assets = context.resources.assets | 
32 |         val assetsInputStream = assets.open("hippo.jpg") | 
33 |         return BitmapFactory.decodeStream(assetsInputStream) | 
34 |     } | 
- 使用自定义 SurfaceView 绘制一张图片
 
使用自定义 View 绘制图片和使用自定义 SurfaceView 绘制图片不同的地方在于,自定义View 在 onDraw 函数中执行绘制图像的操作,而自定义 SurfaceView 分别继承 SurfaceView 与 SurfaceHolder.Callback, 在函数 surfaceCreated 中执行绘制图片的操作:
1 | class CustomSurfaceView : SurfaceView, SurfaceHolder.Callback { | 
2 |     private var mCanvas: Canvas? = null | 
3 |     private lateinit var mSurfaceHolder: SurfaceHolder | 
4 |     private lateinit var mPaint: Paint | 
5 |     private val mMatrix = Matrix() | 
6 | |
7 |     constructor(context: Context) : super(context) { | 
8 |         initView(context) | 
9 |     } | 
10 | |
11 |     constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { | 
12 |         initView(context) | 
13 |     } | 
14 | |
15 |     constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( | 
16 |         context, | 
17 |         attrs, | 
18 |         defStyleAttr | 
19 |     ) { | 
20 |         initView(context) | 
21 |     } | 
22 | |
23 |     private fun initView(context: Context) { | 
24 |         setZOrderOnTop(true) | 
25 |         mSurfaceHolder = holder | 
26 |         mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT) | 
27 |         mSurfaceHolder.addCallback(this) | 
28 |          | 
29 |         mPaint = Paint() | 
30 |         mPaint.style = Paint.Style.STROKE | 
31 |         mPaint.flags = Paint.ANTI_ALIAS_FLAG | 
32 |         mPaint.isAntiAlias = true | 
33 |     } | 
34 | |
35 |     override fun surfaceCreated(surfaceHolder: SurfaceHolder?) { | 
36 |         drawImage() | 
37 |     } | 
38 | |
39 |     override fun surfaceChanged(p0: SurfaceHolder?, p1: Int, p2: Int, p3: Int) {} | 
40 | |
41 |     override fun surfaceDestroyed(p0: SurfaceHolder?) {} | 
42 | |
43 |     private fun drawImage() { | 
44 |         mCanvas = mSurfaceHolder.lockCanvas() | 
45 |         mMatrix.setScale(0.5f, 0.5f) | 
46 |         mCanvas?.drawBitmap(getBitmap(), mMatrix, mPaint) | 
47 |         mSurfaceHolder.unlockCanvasAndPost(mCanvas) | 
48 |     } | 
49 | |
50 |     private fun getBitmap(): Bitmap { | 
51 |         val assets = context.resources.assets | 
52 |         val assetsInputStream = assets.open("hippo.jpg") | 
53 |         return BitmapFactory.decodeStream(assetsInputStream) | 
54 |     } | 
55 | } | 
- 使用 TextureView 绘制一张图片
 
TextureView 是一个结合了 View 和 SurfaceTexture 的 View 对象。既可用于显示图片,也用于显示视频流。
TextureView 和 SurfaceView 扮演的角色类似,且都是 View 层次结构的组成部分。不过,SurfaceView 和 TextureView 拥有截然不同的实现。与 SurfaceView 不同,TextureView 不会创建单独的窗口,而是充当常规 View。 这个关键区别允许对 TextureView 进行移动,变形,设置动画等。
使用 TextureView 很简单,获取到它的 SurfaceTexture,然后可以使用SurfaceTexture 渲染内容。以下代码展示了使用 TextureView 绘制图片的方法:
1 |  | 
2 | fun withTextureView(textureView: TextureView) { | 
3 |     val matrix = Matrix() | 
4 | |
5 |     textureView.surfaceTextureListener = object : SurfaceTextureListener{ | 
6 |         override fun onSurfaceTextureSizeChanged(p0: SurfaceTexture?, p1: Int, p2: Int) { | 
7 | |
8 |         } | 
9 | |
10 |         override fun onSurfaceTextureUpdated(p0: SurfaceTexture?) { | 
11 |         } | 
12 | |
13 |         override fun onSurfaceTextureDestroyed(p0: SurfaceTexture?): Boolean { | 
14 |             return true | 
15 |         } | 
16 | |
17 |         override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, p1: Int, p2: Int) { | 
18 |             val paint = Paint() | 
19 |             paint.isAntiAlias = true | 
20 |             paint.style = Paint.Style.STROKE | 
21 |             paint.flags = Paint.ANTI_ALIAS_FLAG | 
22 | |
23 |             val canvas = textureView.lockCanvas() | 
24 |             matrix.setScale(0.5f,0.5f) | 
25 |             canvas.drawBitmap(getAssetsBitmap(), matrix, paint) | 
26 | |
27 |             textureView.unlockCanvasAndPost(canvas) | 
28 |         } | 
29 |     } | 
30 | } | 
- 使用自定义 TextureView 绘制一张图片
 
通过自定义 TextureView 绘制图片的方法和使用 TextureView 绘制的方法基本一致,通过 TextureView.surfaceTextureListener,等到 SurfaceTexture 可用后使用它进行绘制。
Android 绘制图片的几种方式就介绍到这里,源码请移步:github-StreamTour