🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 1. 前言 对于模式:PorterDuff.Mode.SRC_OUT; 计算方式为:\[Sa \* (1 - Da), Sc \* (1 - Da)\],表示如果相交处的目标色的alpha是完全不透明的,这时候源图像会完全被过滤掉,否则会受到相交处目标色 alpha 影响,呈现出对应色值。 也就是绘图效果取决于目标图的不透明度Da,如果源图像和目标图像不相交,那么就绘制源图,否则就根据公式计算出对应的图像值。 根据这个效果,我们就可以尝试做一个橡皮擦效果。也就是将要擦除的图像作为源图,将手指绘制的区域作为目标图,然后动态绘制手指的范围,根据公式,如果两图之间的目标图不透明度为1,那么就计算为0,可以达到清除的目的。 ~~~ class PorterDuffXfermodeDemo3 : View { constructor(context: Context?) : super(context) { init() } constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { init() } constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr ) { init() } private lateinit var mRectPaint: Paint private lateinit var mRect: Rect private val resBitmap = BitmapFactory.decodeResource(resources, R.drawable.logo) private val mPath = Path() private fun init() { mRect = Rect(200, 200, 400, 400) mRectPaint = Paint() mRectPaint.isAntiAlias = true mRectPaint.color = Color.RED mRectPaint.isDither = true mRectPaint.strokeWidth = 30f mRectPaint.style = Paint.Style.STROKE // 关闭硬件加速 setLayerType(LAYER_TYPE_SOFTWARE, null) } // 记录坐标 private var mX = 0f private var mY = 0f override fun onTouchEvent(event: MotionEvent?): Boolean { var flag = super.onTouchEvent(event) when(event?.action){ MotionEvent.ACTION_DOWN -> { mX = event.x mY = event.y mPath.moveTo(mX, mY) flag = true } MotionEvent.ACTION_MOVE -> { val cX = event.x val cY = event.y val midX = (cX + mX) / 2 val midY = (cY + mY) / 2 mPath.quadTo(midX, midY, cX, cY) mX = cX mY = cY invalidate() } MotionEvent.ACTION_UP -> { mX = event.x mY = event.y mPath.lineTo(mX, mY) } } return flag } override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) canvas?.apply { // this == canvas // 新建图层 val saveLayerId = saveLayer(0f, 0f, width.toFloat(), height.toFloat(), mRectPaint) // 绘制源图 canvas.drawBitmap(resBitmap, 100f, 100f, mRectPaint) // 设置图像混合模式 mRectPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT) // 绘制目标图像 drawPath(mPath, mRectPaint) // 清空图像混合模式 mRectPaint.xfermode = null restoreToCount(saveLayerId) } } } ~~~ 效果,比如在图上画个S: ![](https://img.kancloud.cn/79/5d/795d164cd22ae8e1b1ce0e0d6cf6ebc8_261x259.png) 当然,这里为了观察方便,可以为图层指定一个颜色: ![](https://img.kancloud.cn/c0/f5/c0f57c32133a24df36db70994a0d2786_281x264.png) 当然,可以为画笔指定一个圆角特效: ~~~ // 设置画笔圆角特效 mRectPaint.pathEffect = CornerPathEffect(100f) ~~~ 同样的,也可以指定线帽样式: ~~~ // 设置线帽样式为圆角 mRectPaint.strokeCap = Paint.Cap.ROUND ~~~ 效果: ![](https://img.kancloud.cn/13/13/1313379fd721c855dbcb5d49ccbc5331_270x252.png) 从上图中可以看出,如果我们擦除了图片那么,底层就是画布的背景颜色,所以我们如果可以在其后预先设置一个背景文字,就可以做到刮刮乐效果了。比如这里做简单修改onDraw: ~~~ drawColor(Color.GRAY) drawText("Hello", 200f, 200f, mRectPaint) ... ~~~ 然后调整一下字体大小、线条粗细和颜色: ~~~ mRectPaint.strokeWidth = 18f mRectPaint.textSize = 100f mRectPaint.color = Color.BLACK ~~~ 效果: ![](https://img.kancloud.cn/60/8c/608c6b8fd77774d1b6e404c216658e41_273x260.png)