如今Kotlin越来越重要,本人也开始了Kotlin的学习。为了检测学习效果,加深学习印象,同时回顾一下以前的一些知识点,决定从写一个自定义圆形图片开始入手。圆形图片写法有很多,这里介绍最主要的三种方式,如果能掌握这三种方式,那么其他的自定义控件应该也都很好实现了。
XferMode
XferMode
是一种图片重叠时的处理方式,主要是在图片重叠时,由用户来选取如何对重叠的图片进行处理,XferMode
的模式有如下几种:
目前,XferMode
有三个子类:AvoidXfermode
, PixelXorXfermode
和PorterDuffXfermode
,其中前两个已过时,现在一般用PorterDuffXfermode
。
Canvas
类似于画板,Bitmap
类似于画布,最终我们呈现给别人看的东西,是画布上的内容,也就是Bitmap
,但是我们要在Bitmap
上画东西,则需要画板的支撑。
drawable
主要作用是提供一个可绘制区域和draw
函数用来绘制需要的图像、颜色等,在ImageView
内,我们只需要关心它的两个方法:
先获取一个圆形的Bitmap
对象;
将这个圆形的Bitmap
对象覆盖在图片上;
通过XferMode
中的DST_IN
模式,选取圆形Bitmap
和图片的相交部分。
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)
}
// 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)
}
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
// 3.将合成后的圆形图片显示到控件中
canvas?.drawBitmap(drawableBitmap, 0f, 0f, mPaint)
bitmapshader
是用来给Paint
施加一个渲染效果,它是shader
的子类。初始化的时候,需要对它设置TileMode
,一共有三种模式:
BitmapShader是给画笔的渲染,它是从控件左上角开始渲染的,而不是从绘画开始的地方进行渲染的!
关于Shader
和BitmapShader
的更多介绍,请自行百度。
Matrix有多种用法,我们这里仅仅是用它的缩放功能,目的是让图片的大小大于或等于控件的大小,以免图片填充不满产生拉伸效果!
原理: 给画笔设置一个BitmapShader
,就好像画布上已经存在一张隐藏的Bitmap
,画笔画哪个地方,哪个地方的画面就显示出来。
将drawable
转换成Bitmap
;
新建BitmapShader
,将Bitmap
设置进去,并通过Matrix
将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);
}
}
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);
}
mPaint?.shader = mBitmapShader;
canvas?.drawCircle(mWidth / 2.0f, mWidth / 2.0f, mWidth / 2.0f, mPaint)
裁剪功能,设置canvas的绘画区域。
重要: ClipPath()方法不支持硬件加速!!!
先将drawable
转换成bitmap
;
给画板设置绘图区域为圆形;
绘画
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)
}
}
var path = Path()
path.addCircle(mWidth / 2.0f, mWidth / 2.0f,mWidth / 2.0f,Path.Direction.CCW)
canvas?.save()
canvas?.clipPath(path)
canvas?.drawBitmap(mClipPathBitmap, 0f, 0f, mPaint);
canvas?.restore()