概述
一.效果介绍
- 设置四个圆角的展现和隐藏
- 控件继承
ImageView
,可以使用ImageView
属性的src
和scaleType
- 设置角度的x和y值,x==y 圆角,x!=y 椭圆角
- 设置边框的颜色,边框宽度
通过src设置的图片会被裁剪,设置准确大小下scaleType会生效
先来看下效果吧
图片角度有两种方式BitmapShader
(图片着色器)和PorterDuffXfermode
(图像叠加覆盖的规则)
通过对画笔Paint
设置shader
和xfermode
来实现图片的圆角效果。
二.ShapeShaderImageView
BitmapShader实现的圆角图片
用Bitmap
的像素来作为图片或文字的填充。给Paint
设置shder
来使用
bitMapPaint.shader = bitmapShader
自定义属性:
属性名 | 属性类型 | 含义 | 默认值 |
---|---|---|---|
shiv_bg_color | color/reference | 控件背景色 | Color.TRANSPARENT |
shiv_border_color | color/reference | 边框颜色 | Color.WHITE |
shiv_border_width | dimension/reference | 边框宽度 | 2dp |
shiv_radius | dimension/reference | 圆角正方形的边长 | 5dp |
shiv_radius_x | dimension/reference | 非圆角矩形的宽 | -1f |
shiv_radius_y | dimension/reference | 非圆角矩形的长 | -1f(同时设置x,y大于0 才有效) |
shiv_top_left | boolean | 左上是否有角度 | true |
shiv_top_right | boolean | 右上是否有角度 | true |
shiv_bottom_left | boolean | 左下是否有角度 | true |
shiv_bottom_right | boolean | 右下是否有角度 | true |
onDraw()重写 :
删除spuer.onDraw(),实现自己的圆角逻辑
override fun onDraw(canvas: Canvas?) {
canvas?.drawColor(bgColor)
// BitmapShader实现
canvas?.save()
val bitmap = (drawable as BitmapDrawable).bitmap
val bitmapShader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
val matrix = setBitmapMatrixAndPath(width.toFloat(), height.toFloat(), bitmap)
bitmapShader.setLocalMatrix(matrix)
bitMapPaint.shader = bitmapShader
canvas?.drawPath(clipPath, bitMapPaint)
canvas?.restore()
borderPaint.style = Paint.Style.STROKE
canvas?.drawPath(borderPath, borderPaint)
if (!cornerTopLeftAble) {
borderPaint.style = Paint.Style.FILL
canvas?.drawRect(suppleRectF, borderPaint)
}
}
- 获取
bitmap
对象,将drawable
转为BitmapDrawable
获取bitmap
对象 - 声明
BitmapShader
对象,需要设置bitmap
,以及端点之外的图片延伸模式TileMode
, 图片的matrix
- 给
paint
设置shader
后,用canvas
的drawXXX
方法画出想要的图形,必须使用设置了shader
的paint
- 设置边框,给
borderPaint
设置颜色,填充模式和描边宽度即可正常绘制。 ShapeBitmaoshaderImageView
继承AppCompatImageView
,支持一些ImageView
的属性设置,例如src
,scaleType
等。
注意:在实际测试中发现,绘制边框时,首尾衔接不上,需要在开始的点的左上位置绘制一个边长为边框宽度的一半,颜色为边框颜色的正方形用于补充空白部分。修补代码及效果:
if (!cornerTopLeftAble) {
borderPaint.style = Paint.Style.FILL
canvas?.drawRect(suppleRectF, borderPaint)
}
修补前 | 修补后 |
---|---|
setBitmapMatrixAndPath(w,h,bitmap) 设置图片缩放,平移
根据ScaleType
的枚举值,进行图片的缩放,平移以达到ImageView
的ScaleType
效果。
/**
* 设置图片变化的matrix, 裁剪路径,边框路径
*/
private fun setBitmapMatrixAndPath(w: Float, h: Float, bitmap: Bitmap): Matrix {
// 图片变化的matrix
val matrix = Matrix()
// 图片缩放比例
val scaleX: Float
val scaleY: Float
// 缩放后的图片宽高
val bw: Float
val bh: Float
// 移动圆点
var transX = 0f
var transY = 0f
if (isSetSize) {
when(scaleType) {
ScaleType.FIT_XY -> {
// 不管图片大小,填充整个view
scaleX = w / bitmap.width
scaleY = h / bitmap.height
matrix.setScale(scaleX, scaleY)
setPath(borderWidth, borderWidth, w - borderWidth, h - borderWidth)
}
ScaleType.FIT_CENTER -> {
// fitCenter图片按比例缩放至View的宽度或者高度(取宽和高的最小值),居中显示
val scale: Float
if (w < h) {
scale = w / bitmap.width
transY = (h - bitmap.height * scale) / 2
} else {
scale = h / bitmap.height
transX = (w - bitmap.width * scale) / 2
}
matrix.setScale(scale, scale)
matrix.postTranslate(transX, transY)
bw = bitmap.width * scale
bh = bitmap.height * scale
val left = if (transX < 0) borderWidth else transX + borderWidth
val top = if (transY < 0) borderWidth else transY + borderWidth
val right = if (transX < 0) w - borderWidth else transX + bw -borderWidth
val bottom = if (transY < 0) h - borderWidth else transY + bh - borderWidth
setPath(left, top, right, bottom)
}
ScaleType.FIT_START -> {
// 图片按比例缩放至View的宽度或者高度(取宽和高的最小值),然后居上或者居左显示
val scale = if (w < h) {
w / bitmap.width
} else {
h / bitmap.height
}
matrix.setScale(scale, scale)
bw = bitmap.width * scale
bh = bitmap.height * scale
val left = borderWidth
val top = borderWidth
val right = if (w < bw) w - borderWidth else bw - borderWidth
val bottom = if (h < bh) h - borderWidth else bh - borderWidth
setPath(left, top, right, bottom)
}
ScaleType.FIT_END -> {
// 图片按比例缩放至View的宽度或者高度(取宽和高的最小值),然后居下或者居右显示
val scale: Float
if (w < h) {
scale = w / bitmap.width
transY = h - bitmap.height * scale
} else {
scale = h / bitmap.height
transX = w - bitmap.width * scale
}
matrix.setScale(scale, scale)
matrix.postTranslate(transX, transY)
bw = bitmap.width * scale
bh = bitmap.height * scale
val left = if (transX < 0) borderWidth else transX + borderWidth
val top = if (transY < 0) borderWidth else transY + borderWidth
val right = if (transX < 0) w - borderWidth else transX + bw - borderWidth
val bottom = if (transY < 0) h - borderWidth else transY + bh - borderWidth
setPath(left, top, right, bottom)
}
ScaleType.CENTER -> {
// 按照图片原始大小,居中显示,多余部分裁剪
transX = (w - bitmap.width) / 2
transY = (h - bitmap.height) / 2
matrix.postTranslate(transX, transY)
setPath(if (transX < 0) borderWidth else transX + borderWidth,
if (transY < 0) borderWidth else transY + borderWidth,
if (transX < 0) w - borderWidth else transX + bitmap.width - borderWidth,
if (transY < 0) h - borderWidth else transY + bitmap.height - borderWidth)
}
ScaleType.CENTER_INSIDE -> {
// centerInside的目标是将原图完整的显示出来,故按比例缩放原图,居中显示
val scale: Float
if (w < h) {
scale = w / bitmap.width
transY = (h - bitmap.height * scale) / 2
} else {
scale = h / bitmap.height
transX = (w - bitmap.width * scale) / 2
}
matrix.setScale(scale, scale)
matrix.postTranslate(transX, transY)
bw = bitmap.width * scale
bh = bitmap.height * scale
val left = if (transX < 0) borderWidth else transX + borderWidth
val top = if (transY < 0) borderWidth else transY + borderWidth
val right = if (transX < 0) w - borderWidth else transX + bw - borderWidth
val bottom = if (transY < 0) h - borderWidth else transY + bh -borderWidth
setPath(left, top, right, bottom)
}
ScaleType.CENTER_CROP -> {
// centerCrop的目标是将ImageView填充满,故按比例缩放原图,居中显示
val scale: Float
if (w > h) {
scale = w / bitmap.width
transY = (h - bitmap.height * scale) / 2
} else {
scale = h / bitmap.height
transX = (w -bitmap.width * scale) / 2
}
matrix.setScale(scale, scale)
matrix.postTranslate(transX, transY)
bw = bitmap.width * scale
bh = bitmap.height * scale
val left = if (transX < 0) borderWidth else transX + borderWidth
val top = if (transY < 0) borderWidth else transY + borderWidth
val right = if (transX < 0) w - borderWidth else transX + bw - borderWidth
val bottom = if (transY < 0) h - borderWidth else transY + bh -borderWidth
setPath(left, top, right, bottom)
}
ScaleType.MATRIX -> {
// 按照原图大小从左上角绘制,多余部分裁剪
bw = if (w < bitmap.width) w else bitmap.width.toFloat()
bh = if (h < bitmap.height) h else bitmap.height.toFloat()
setPath(borderWidth, borderWidth, bw - borderWidth, bh - borderWidth)
}
else -> {}
}
} else {
scaleX = w / bitmap.width
scaleY = h / bitmap.height
matrix.setScale(scaleX, scaleY)
setPath(borderWidth, borderWidth, w - borderWidth, h -borderWidth)
}
return matrix
}
ScaleType简单说明:
ScaleType | 含义 |
---|---|
FIT_XY | 不管图片大小,填充整个view |
FIT_CENTER | 图片按比例缩放至View的宽度或者高度(取宽和高的最小值),居中显示 |
FIT_START | 图片按比例缩放至View的宽度或者高度(取宽和高的最小值),居上或者居左显示 |
FIT_END | 图片按比例缩放至View的宽度或者高度(取宽和高的最小值),居下或者居右显示 |
CENTER | 按照图片原始大小,居中显示,多余部分裁剪 |
CENTER_INSIDE | 目标是将原图完整的显示出来,故按比例缩放原图,居中显示 |
CENTER_CROP | 目标是将view填充满,故按比例缩放原图,居中显示 |
MATRIX | 按照原图大小从左上角绘制,多余部分裁剪 |
setPath(left, right, top, bottom) 设置裁剪路径和边框路径
setPath
主要根据传进来的裁剪矩形框的值进行裁剪路径和边框路径的合成。
边框四边 = 裁剪框四边向外扩张borderwidth大小;
边框角度值 = 裁剪框角度值 + borderwidth / 2
/**
* 设置裁剪路径和边框路径
* @param left 裁剪框的left
* @param top 裁剪框的top
* @param right 裁剪框的right
* @param bottom 裁剪框的bottom
*/
private fun setPath(left: Float, top: Float, right: Float, bottom: Float) {
clipPath.reset()
borderPath.reset()
val w = right - left
val h = bottom - top
setRadius(w, h)
val borderLeft = left - borderWidth / 2
val borderTop = top - borderWidth / 2
val borderRight = right + borderWidth / 2
val borderBottom = bottom + borderWidth / 2
val borderRadiusX = radiusX + borderWidth / 2
val borderRadiusY = radiusY + borderWidth / 2
val bw = borderRight - borderLeft
val bh = borderBottom - borderTop
suppleRectF.left = borderLeft - borderWidth / 2
suppleRectF.top = borderTop - borderWidth / 2
suppleRectF.right = borderLeft
suppleRectF.bottom = borderTop
// 圆角或椭圆角的矩形
val topLeftRectF = RectF()
val topRightRectF = RectF()
val bottomLeftRectF = RectF()
val bottomRightRectF = RectF()
if (radiusX <= 0 && radiusY <= 0) {
// 没有圆角
clipPath.addRect(left, top, right, bottom, Path.Direction.CW)
borderPath.addRect(borderLeft, borderTop, borderRight, borderBottom, Path.Direction.CW)
} else {
// 有圆角
if (cornerTopLeftAble) {
// 裁剪
// 左上角
topLeftRectF.left = left
topLeftRectF.top = top
topLeftRectF.right = left + radiusX * 2
topLeftRectF.bottom = top + radiusY * 2
clipPath.addArc(topLeftRectF, 180f, 90f)
// 边框
// 左上角
topLeftRectF.left = borderLeft
topLeftRectF.top = borderTop
topLeftRectF.right = borderLeft + borderRadiusX * 2
topLeftRectF.bottom = borderTop + borderRadiusY * 2
borderPath.moveTo(borderLeft, borderTop + borderRadiusY)
borderPath.addArc(topLeftRectF, 180f, 90f)
borderPath.moveTo(borderLeft + borderRadiusX, borderTop)
} else {
clipPath.moveTo(left, top)
borderPath.moveTo(borderLeft, borderTop)
}
clipPath.lineTo(if (cornerTopRightAble) right - radiusX else right , top)
if (bw != borderRadiusX * 2) {
borderPath.lineTo(if (cornerTopRightAble) borderRight - borderRadiusX else borderRight , borderTop)
}
if (cornerTopRightAble) {
// 右上角
topRightRectF.left = right - radiusX * 2
topRightRectF.top = top
topRightRectF.right = right
topRightRectF.bottom = top + radiusY * 2
clipPath.addArc(topRightRectF, 270f, 90f)
// 右上角
topRightRectF.left = borderRight - borderRadiusX * 2
topRightRectF.top = borderTop
topRightRectF.right = borderRight
topRightRectF.bottom = borderTop + borderRadiusY * 2
borderPath.addArc(topRightRectF, 270f, 90f)
borderPath.moveTo(borderRight, borderTop + borderRadiusY)
}
clipPath.lineTo(right, if (cornerBottomRightAble) bottom - radiusY else bottom)
if (bh != borderRadiusY * 2) {
borderPath.lineTo(borderRight, if (cornerBottomRightAble) borderBottom - borderRadiusY else borderBottom)
}
if (cornerBottomRightAble) {
// 右下角
bottomRightRectF.left = right - radiusX * 2
bottomRightRectF.top = bottom - radiusY * 2
bottomRightRectF.right = right
bottomRightRectF.bottom = bottom
clipPath.addArc(bottomRightRectF, 0f, 90f)
// 右下角
bottomRightRectF.left = borderRight - borderRadiusX * 2
bottomRightRectF.top = borderBottom - borderRadiusY * 2
bottomRightRectF.right = borderRight
bottomRightRectF.bottom = borderBottom
borderPath.addArc(bottomRightRectF, 0f, 90f)
borderPath.moveTo(borderRight - borderRadiusX ,borderBottom)
}
clipPath.lineTo(if (cornerBottomLeftAble) left + radiusX else left, bottom)
if (bw != borderRadiusX * 2) {
borderPath.lineTo(if (cornerBottomLeftAble) borderLeft + borderRadiusX else borderLeft, borderBottom)
}
if (cornerBottomLeftAble) {
// 左下角
bottomLeftRectF.left = left
bottomLeftRectF.top = bottom - radiusY * 2
bottomLeftRectF.right = left + radiusX * 2
bottomLeftRectF.bottom = bottom
clipPath.addArc(bottomLeftRectF, 90f, 90f)
// 左下角
bottomLeftRectF.left = borderLeft
bottomLeftRectF.top = borderBottom - borderRadiusY * 2
bottomLeftRectF.right = borderLeft + borderRadiusX * 2
bottomLeftRectF.bottom = borderBottom
borderPath.addArc(bottomLeftRectF, 90f, 90f)
borderPath.moveTo(borderLeft, borderBottom - borderRadiusY)
}
clipPath.lineTo(left, if (cornerTopLeftAble) top + radiusY else top)
if (cornerTopLeftAble) {
clipPath.lineTo(left, top + radiusY)
}
if (cornerTopRightAble) {
clipPath.lineTo(right - radiusX, top)
}
if (cornerBottomRightAble) {
clipPath.lineTo(right, bottom - radiusY)
}
if (cornerBottomLeftAble) {
clipPath.lineTo(left + radiusX, bottom)
}
if (bh != borderRadiusY * 2) {
borderPath.lineTo(borderLeft, if (cornerTopLeftAble) borderTop + borderRadiusY else borderTop)
}
}
}
setRadius(w,h) 设置圆角值
/**
* 设置圆角值
*/
private fun setRadius(w: Float, h: Float) {
if (radiusX < 0 || radiusY < 0) {
if (cornerRadius < 0) {
cornerRadius = 0f
}
radiusX = cornerRadius
radiusY = cornerRadius
}
if (radiusX > w / 2) {
radiusX = w / 2
}
if (radiusY > h / 2) {
radiusY = h / 2
}
}
如果设置了角度的x,y且大于等于0,则使用,否则使用圆角值(>=0)
三.ShapeXfermodeImageView
PorterDuffXfermode实现的圆角图片
PorterDuffXfermode
是指目标图片和源图片的叠加覆盖规则,给Paint
设置xfermode
来使用,需要注意PorterDuffXfermode
需要设置具体某种绘制规则(PorterDuff.Mode
)
bitMapPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
注意: xfermode和BitmapShader角度图片,裁剪路径和边框的设置是一样的。 不同的是xfermode需要用裁剪路径去生成源图,设置的图片用作目标图来进行绘制,而bitmapshader直接使用裁剪路径。
ShapeXfermodeImageView 自定义属性:
属性名 | 属性类型 | 含义 | 默认值 |
---|---|---|---|
sxiv_bg_color | color/reference | 控件背景色 | Color.TRANSPARENT |
sxiv_border_color | color/reference | 边框颜色 | Color.WHITE |
sxiv_border_width | dimension/reference | 边框宽度 | 2dp |
sxiv_radius | dimension/reference | 圆角正方形的边长 | 5dp |
sxiv_radius_x | dimension/reference | 非圆角矩形的宽 | -1f |
sxiv_radius_y | dimension/reference | 非圆角矩形的长 | -1f(同时设置x,y大于0 才有效) |
sxiv_top_left | boolean | 左上是否有角度 | true |
sxiv_top_right | boolean | 右上是否有角度 | true |
sxiv_bottom_left | boolean | 左下是否有角度 | true |
sxiv_bottom_right | boolean | 右下是否有角度 | true |
onDraw()重写:
删除spuer.onDraw(),实现自己的圆角逻辑
override fun onDraw(canvas: Canvas?) {
canvas?.drawColor(bgColor)
// BitmapShader实现
val saved = canvas?.saveLayer(null, null, Canvas.ALL_SAVE_FLAG)
val dstBitmap = (drawable as BitmapDrawable).bitmap
val matrix = setBitmapMatrixAndPath(width.toFloat(), height.toFloat(), dstBitmap)
val srcBitmap = createSrcBitmap(width, height)
canvas?.drawBitmap(dstBitmap, matrix, bitMapPaint)
bitMapPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
canvas?.drawBitmap(srcBitmap, 0f, 0f, bitMapPaint)
bitMapPaint.xfermode = null
canvas?.restoreToCount(saved?: 0)
borderPaint.style = Paint.Style.STROKE
canvas?.drawPath(borderPath, borderPaint)
if (!cornerTopLeftAble) {
borderPaint.style = Paint.Style.FILL
canvas?.drawRect(suppleRectF, borderPaint)
}
}
- 使用同一个
Paint
实例进行绘制 - 设置
xfermode
之前的drawBitmap
是绘制目标图,之后的是源图 - 这里的
PorterDuff.Mode
是DST_IN
值,保留目标图和源图的交集部分 - 调用
createSrcBitmap(w,h)
,根据裁剪路径绘制源图
PorterDuff.ModeJ简单说明:
createSrcBitmap(w,h)
根据裁剪路径绘制源图
/**
* 获取源图biatmap,用于截出形状图
*/
private fun createSrcBitmap(w: Int, h: Int): Bitmap {
val srcBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
srcBitmap.eraseColor(Color.TRANSPARENT)
val canvas = Canvas(srcBitmap)
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.WHITE
style = Paint.Style.FILL
}
canvas.drawPath(clipPath, paint)
return srcBitmap
}
setBitmapMatrixAndPath(), setPath(), setRadius() 和ShpaeShaderImageView的方法是一样的
以上就是BitmapShader
和Xfermode
实现角度图片的过程
最后
以上就是忧心月光为你收集整理的Android 在 Kotlin 中 圆角图片,椭圆角图片的实现一.效果介绍二.ShapeShaderImageView三.ShapeXfermodeImageView的全部内容,希望文章能够帮你解决Android 在 Kotlin 中 圆角图片,椭圆角图片的实现一.效果介绍二.ShapeShaderImageView三.ShapeXfermodeImageView所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复