Android 中实现自定义形状图片

最近因为需求需要用到圆形图片,因为不能去引用一些三方库,并且也不是什么特别难的自定义View,因此自己动手y用 BitmapShader 实现了一个,在 Android 上实现自定义形状图片还有其他几种方式,特此记录一下~

ClipPath

这种方式应该是实现自定义形状图片最简单的方式了,只需要继承 ImageView,并在 onDraw 方法中,在执行 super.onDraw() 之前,调用clip方法对画布进行裁剪即可,需要注意的是,在 API 18 以下,需要关闭硬件加速。

1
2
3
4
5
 override fun onDraw(canvas: Canvas) {
mPath.addCircle(mWidth / 2, mHeight /2 , mRadius, Path.Direction.CW)
canvas.clipPath(mPath)
super.onDraw(canvas)
}

这种方式实现虽然简单,但缺点就是裁剪出的图片仔细看的话,会发现有锯齿,而且也无法添加阴影或其他扩展,因为画布的大小已经是圆形区域这么大了。

BitmapShader

关于 BitmapShader ,可以看看爱哥的自定义View博客的第四篇 自定义控件其实很简单1/3
Github 上一个著名的圆形图片库 CircleImageView 就是使用这种方式来实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private fun drawableToBitmap(drawable: Drawable) : Bitmap {
if(drawable is BitmapDrawable) {
return drawable.bitmap
}
val bitmap : Bitmap = Bitmap.createBitmap(drawable.intrinsicWidth,
drawable.intrinsicHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0,0,drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable.draw(canvas)
return bitmap
}

fun initPaint() {
mPaint.shader = BitmapShader(drawableToBitmap(drawable), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
}

override fun onDraw(canvas: Canvas) {
canvas.drawCircle(mWidth / 2, mHeight /2 , mRadius, mPaint)
}

PorterDuffXfermode

关于混合模式的相关概念可参考 自定义控件其实很简单1/6
这里只需要关注 SRC_INDST_IN 两种模式即可,其实主要就是理解这张图:

因此实现方式也是很简单,这里比如说使用 SRC_IN 模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
fun initPaint() {
mBitmap = drawableToBitmap(drawable)
mXfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
}

override fun onDraw(canvas: Canvas) {
val layerCount = canvas.saveCount
canvas.drawCircle(mWidth / 2, mHeight /2 , mRadius, mPaint)
mPaint.xfermode = mXfermode
canvas.drawBitmap(mBitmap,0f,0f, mPaint)
mPaint.xfermode = null
canvas.restoreToCount(layerCount)
}

这里需要注意的是,要在绘制 bitmap 完成后,即使将 Paint.xfermode 设置为 null 来清除混合模式。

阴影

有些时候我们可能需要给 View 加个背景阴影来使 View 显得立体一点,此时就需要用到 Paint 的 setShadowLayer() 这个方法了,其中,radius 是阴影半径,越大阴影越发散,为0时不显示,dx 和 dy 分别为 X 轴和 Y 轴的偏移量,shadowColor 就不用说了,阴影的颜色嘛~

1
public void setShadowLayer(float radius, float dx, float dy, int shadowColor)

比较

总体而言,使用 ClipPath 来实现最为简单,只需要继承 ImageView 即可,但缺点就是前面说的锯齿问题,其他两种方式,虽然是继承 ImageView 来实现,但具体绘制还是要自己来实现,而且还得自己去支持 ScaleType,如果是需要用到占位图,那还得根据占位图是否需要进行裁剪来做相应的操作,还有就是需要对 drawable 做适配,因为占位图有可能不是 bitmap,如 vector等。