在业务开发中很多场景都会遇到遮罩。今天使用 PorterDuffXfermode 的XOR模式来绘制一个中间透明的遮罩。
先上一张图看看效果
分析一下:
1:后面的背景是黑色
2:中间还有一个不规则的透明部分 使中间透明就需要使用到 PorterDuff.Mode.XOR
3:还有一个边框
以上如何实现的呢?下面上代码
一:首先继承View
class MaskView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
}
二:初始化需要的参数
// 视图宽高
private var viewWith: Int = 0
private var viewHeight: Int = 0
// 画笔:默认画笔 背景画笔 前景(透明部分)画笔 边框画笔
private lateinit var defaultPaint: Paint
private lateinit var bgPaint: Paint
private lateinit var fgPaint: Paint
private lateinit var borderPaint: Paint
// 路径
private lateinit var fgPath: Path
private lateinit var borderPath: Path
// 前景上边线的margin值
private var fgMargin = 100f
// kotlin 初始化方法
init {
initPaint()
}
/**
* 初始化画笔
*/
private fun initPaint() {
defaultPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// 黑色背景画笔
bgPaint = Paint(defaultPaint).apply {
color = Color.BLACK
}
// 透明区域画笔
fgPaint = Paint(defaultPaint).apply {
xfermode = PorterDuffXfermode(PorterDuff.Mode.XOR)
}
// 边框画笔
borderPaint = Paint(defaultPaint).apply {
color = Color.parseColor("#00BECC")
style = Paint.Style.STROKE
strokeWidth = 10f
}
}
三:获取视图的宽高
/**
* 测量
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val w = MeasureSpec.getSize(widthMeasureSpec)
val h = MeasureSpec.getSize(heightMeasureSpec)
resetViewSize(w, h)
}
/**
* 大小发生改变
*/
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
resetViewSize(w, h)
}
/**
* 重新设定视图大小
*/
private fun resetViewSize(w: Int, h: Int) {
// 设置该view的大小
setMeasuredDimension(viewWith, viewHeight)
// 宽高保存为全局变量
viewWith = w
viewHeight = h
// 获取前景的path路径
fgPath = getPath(true)
borderPath = getPath(false)
// 重新绘制Canvas
invalidate()
}
四:获取路径
/**
* 获取绘制的Path路径
* @param isWinding 是否全填充
*/
private fun getPath(isWinding: Boolean) = Path().apply {
reset()
// 第一条线起点
moveTo(fgMargin, 10f)
// 第一条线的终点 (顶部)
lineTo(viewWith - fgMargin, 10f)
// 第二条线的结束点 (右边向中部走)
lineTo(viewWith.toFloat() - 10f, viewHeight / 2f)
// 第三条线 (右边中部向下走)
lineTo(viewWith - fgMargin, viewHeight.toFloat() - 10f)
// 第四条线 (底部)
lineTo(fgMargin, viewHeight.toFloat() - 10f)
// 第五条线 (左边底部)
lineTo(10f, viewHeight / 2f)
// 最后一条回到起点
lineTo(fgMargin, 10f)
// 闭合
close()
// 如果是背景那就全填充
if (isWinding) {
fillType = Path.FillType.WINDING
}
}
说明
这是path 绘制的路径,isWinding 来判断是否填充,因为使用了XOR模式的Paint 所以绘制出来就是透明的,边框也是用的是这个路径所以边框不需要填充
五:绘制
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// 保存之前的图层 !!!很重要
canvas?.saveLayer(0f, 0f, viewWith.toFloat(), viewHeight.toFloat(), defaultPaint)
// 绘制背景
canvas?.drawRect(0f, 0f, viewWith.toFloat(), viewHeight.toFloat(), bgPaint)
// 绘制前景(透明区域) 因为用到了XOR模式所以绘制的区域会变成透明的
canvas?.drawPath(fgPath, fgPaint)
// 保存图层
canvas?.saveLayer(0f, 0f, viewWith.toFloat(), viewHeight.toFloat(), defaultPaint)
// 绘制边框
canvas?.drawPath(borderPath, borderPaint)
}
注意:记得保存图层!
代码很简单,希望可以帮助到你。