Android 自定义View,实现刮刮乐效果

发布于:2025-03-09 ⋅ 阅读:(102) ⋅ 点赞:(0)

先上效果:

刮刮乐

class EraseView : View {
    private val mPaint by lazy {
        Paint().apply {
            color = Color.RED
            isAntiAlias = true
            strokeWidth = 50f // 设置画笔粗细
            style = Paint.Style.STROKE // 设置画笔样式为描边
            strokeCap = Paint.Cap.ROUND // 设置画笔端点为圆形
            strokeJoin = Paint.Join.ROUND // 设置拐角为圆形
        }
    }

    private val mTextPaint by lazy {
        Paint().apply {
            isAntiAlias = true
            color = Color.RED // 设置擦除画笔为红色
            style = Paint.Style.FILL // 设置画笔样式为描边
            strokeWidth = 50f// 设置画笔粗细
            strokeCap = Paint.Cap.ROUND // 设置画笔端点为圆形
            strokeJoin = Paint.Join.ROUND // 设置拐角为圆形
            textSize = 44f
        }
    }

    private val mPath = Path()

    // 源图像
    private lateinit var srcBitmap: Bitmap

    // 目标图层
    private lateinit var dstBitmap: Bitmap
    private var showText = ""//文案
    private var isShowResultDirect = false//是否直接展示结果

    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 {
        showText = getRandomStr()
    }
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        // 初始化图片资源
        val originBitmap = BitmapFactory.decodeResource(resources, R.drawable.image_convor)
        srcBitmap = Bitmap.createScaledBitmap(originBitmap, w, h, false)
        dstBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
    }

    @SuppressLint("DrawAllocation")
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        if (!this::srcBitmap.isInitialized || !this::dstBitmap.isInitialized) return

        kotlin.runCatching {
            canvas.drawColor(Color.parseColor("#BBBBBB"))
        }
        val textWidth = mTextPaint.measureText(showText) //文本宽度
        val fontMetrics = mTextPaint.fontMetrics
        val baselineOffset = (fontMetrics.bottom + fontMetrics.top) / 2
        //显示的文案居中绘制
        canvas.drawText(
            showText,
            (width - textWidth) / 2f,
            height / 2f - baselineOffset,
            mTextPaint
        )

        val layerId = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null)
        // 1. 在画布上绘制擦除路径
        canvas.drawPath(mPath, mPaint)
        // 2. 绘制背景图像(目标图像)
        canvas.drawBitmap(dstBitmap, 0f, 0f, mPaint)
        // 3. 手动擦除,设置混合模式为 SRC_OUT,实现擦除效果;直接展示结果,用DST_OUT
        mPaint.xfermode =
            PorterDuffXfermode(if (isShowResultDirect) PorterDuff.Mode.DST_OUT else PorterDuff.Mode.SRC_OUT)
        canvas.drawBitmap(srcBitmap, 0f, 0f, mPaint)
        mPaint.xfermode = null
        canvas.restoreToCount(layerId)
    }

    // 处理触摸事件,动态更新擦除路径
    override fun onTouchEvent(event: MotionEvent): Boolean {
        val x = event.x
        val y = event.y
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                // 请求父视图不要拦截事件
                parent.requestDisallowInterceptTouchEvent(true)
                mPath.moveTo(x, y) // 路径起点
            }

            MotionEvent.ACTION_MOVE -> {
                mPath.lineTo(x, y) // 路径延续
                invalidate() // 重新绘制
            }

            MotionEvent.ACTION_UP -> {
                parent.requestDisallowInterceptTouchEvent(false)
            }
        }
        return true
    }

    /**
     * 重置文案
     */
    private fun getRandomStr(): String {
        return listOf("谢谢惠顾", "特等奖", "一等奖", "二等奖", "三等奖").random()
    }

    /**
     * 重置状态,再刮一次
     */
    fun resetState() {
        mPath.reset()
        isShowResultDirect = false
        showText = getRandomStr()
        invalidate()
    }

    /**
     * 直接展示结果
     */
    fun showResultDirect() {
        isShowResultDirect = true
        invalidate()
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到