Kotlin实战练习——自定义圆形图片三种实现方式
前言
如今Kotlin越来越重要,本人也开始了Kotlin的学习。为了检测学习效果,加深学习印象,同时回顾一下以前的一些知识点,决定从写一个自定义圆形图片开始入手。圆形图片写法有很多,这里介绍最主要的三种方式,如果能掌握这三种方式,那么其他的自定义控件应该也都很好实现了。
1. XferMode方式
知识点
1. 关于XferMode
XferMode
是一种图片重叠时的处理方式,主要是在图片重叠时,由用户来选取如何对重叠的图片进行处理,XferMode
的模式有如下几种:
目前,XferMode
有三个子类:AvoidXfermode
, PixelXorXfermode
和PorterDuffXfermode
,其中前两个已过时,现在一般用PorterDuffXfermode
。
2. Canvas和Bitmap的关系
Canvas
类似于画板,Bitmap
类似于画布,最终我们呈现给别人看的东西,是画布上的内容,也就是Bitmap
,但是我们要在Bitmap
上画东西,则需要画板的支撑。
3. drawable
drawable
主要作用是提供一个可绘制区域和draw
函数用来绘制需要的图像、颜色等,在ImageView
内,我们只需要关心它的两个方法:
- setBounds: 可绘制区域;
- draw: 将内容绘制到画板上的画布中。
实现思路
先获取一个圆形的
Bitmap
对象;将这个圆形的
Bitmap
对象覆盖在图片上;通过
XferMode
中的DST_IN
模式,选取圆形Bitmap
和图片的相交部分。
实现代码
1.初始化画笔、圆形图片半径和XferMode
private var mPaint: Paint? = null
private var mWidth: Int = 0
private var mCircleBitmap: Bitmap? = null
private var xferMode : PorterDuffXfermode? = null
constructor(context: Context?) : this(context, null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
init(context)
}
fun init(context: Context?) {
mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
xferMode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
mWidth = if(width > height) height else width
setMeasuredDimension(mWidth, mWidth)
}
2.获取一个圆形图片
// 1.先获取一个圆形图片
if(mCircleBitmap == null) {
mCircleBitmap = Bitmap.createBitmap(mWidth, mWidth, Bitmap.Config.ARGB_8888)
var circleCanvas = Canvas(mCircleBitmap)
mPaint?.reset()
mPaint?.style = Paint.Style.FILL
circleCanvas.drawCircle(mWidth/2f, mWidth/2f, mWidth/2f, mPaint)
}
3.将圆形Bitmap
覆盖到图片上
// 2.将圆形图片和源图片相交,截取相交部分
var drawableHeight = drawable?.intrinsicHeight ?: 0
var drawableWidth = drawable?.intrinsicWidth ?: 0
var drawableBitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888)
var drawableCanvas = Canvas(drawableBitmap)
drawable?.setBounds(0, 0, mWidth, maxHeight)
drawable?.draw(drawableCanvas)
mPaint?.reset()
mPaint?.xfermode = xferMode
drawableCanvas.drawBitmap(mCircleBitmap, 0f, 0f, mPaint)
mPaint?.xfermode = null
4.将合成后的圆形图片显示到控件中
// 3.将合成后的圆形图片显示到控件中
canvas?.drawBitmap(drawableBitmap, 0f, 0f, mPaint)
2. BitmapShader方式
知识点
1. BitmapShader介绍
bitmapshader
是用来给Paint
施加一个渲染效果,它是shader
的子类。初始化的时候,需要对它设置TileMode
,一共有三种模式:
- CLAMP: 拉伸。若图片没有填充满布局,会将x,y轴的最后一个像素拉伸到布局边缘;
- REPEAT: 重复。若图片没有填充满布局,会将图片进行x/y轴的重复;
- MIRROR: 镜像。若图片没有填充满布局,则会将图片进行镜像复制。
BitmapShader是给画笔的渲染,它是从控件左上角开始渲染的,而不是从绘画开始的地方进行渲染的!
关于Shader
和BitmapShader
的更多介绍,请自行百度。
2. Matrix
Matrix有多种用法,我们这里仅仅是用它的缩放功能,目的是让图片的大小大于或等于控件的大小,以免图片填充不满产生拉伸效果!
实现思路
原理: 给画笔设置一个BitmapShader
,就好像画布上已经存在一张隐藏的Bitmap
,画笔画哪个地方,哪个地方的画面就显示出来。
将
drawable
转换成Bitmap
;新建
BitmapShader
,将Bitmap
设置进去,并通过Matrix
将Bitmap
缩放到和控件大小一样(不能小于控件大小),以免产生拉伸效果;画圆形图片,将"隐藏的"图片显示出来。
实现代码
1.初始化
同方法一
2.将drawable转换成bitmap
if(mShaderBitmap == null) {
if(drawable is BitmapDrawable) {
var d = drawable as? BitmapDrawable
mShaderBitmap = d?.bitmap
} else {
var drawableHeight = drawable.intrinsicHeight
var drawableWidth = drawable.intrinsicWidth
mShaderBitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888)
var drawableCanvas = Canvas(mShaderBitmap);
drawable.setBounds(0, 0, drawableWidth, drawableHeight)
drawable.draw(drawableCanvas);
}
}
3.设置BitmapShader
if(mBitmapShader == null) {
mBitmapShader = BitmapShader(mShaderBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
// 设置缩放大小,保证bitmap的大小要大于控件的大小(以免产生拉伸效果!)
var bitmapWidth = Math.min(mShaderBitmap!!.width, mShaderBitmap!!.height)
var scale = mWidth * 1.0f / bitmapWidth
mMatrix = Matrix()
mMatrix?.setScale(scale, scale)
mBitmapShader?.setLocalMatrix(mMatrix);
}
4.给画笔设置BitmapShader
mPaint?.shader = mBitmapShader;
5.将圆形图片画在画板上
canvas?.drawCircle(mWidth / 2.0f, mWidth / 2.0f, mWidth / 2.0f, mPaint)
3.ClipPath方式
知识点
1.canvas.clipPath()
裁剪功能,设置canvas的绘画区域。
重要: ClipPath()方法不支持硬件加速!!!
实现思路
先将
drawable
转换成bitmap
;给画板设置绘图区域为圆形;
绘画
实现代码
1. 先将drawable
转换成bitmap
if(mClipPathBitmap == null) {
if(drawable is BitmapDrawable) {
var d = drawable as? BitmapDrawable
mClipPathBitmap = d?.bitmap
} else {
var h = drawable.intrinsicHeight
var w = drawable.intrinsicWidth
mClipPathBitmap = Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888)
var drawableCanvas = Canvas(mClipPathBitmap);
drawable.setBounds(0 ,0, mWidth, mWidth)
drawable.draw(drawableCanvas)
}
}
2. 给画板设置绘图区域为圆形
var path = Path()
path.addCircle(mWidth / 2.0f, mWidth / 2.0f,mWidth / 2.0f,Path.Direction.CCW)
3.绘图
canvas?.save()
canvas?.clipPath(path)
canvas?.drawBitmap(mClipPathBitmap, 0f, 0f, mPaint);
canvas?.restore()