Android自定义View扫码ui控件效果

“我正在参加「掘金·启航计划」”

前段时间做了一个圆形物品扫码相关的需求,要求做一个扫描动画,扫描线只在圆形区域显示
效果图如下:

image.png

image.png

思路如下:

看到这东西,第一反应就是使用自定义view

  1. 画出一个蒙层,该蒙层可以使用 PorterDuff.Mode的clear
  2. 画一个圆形白色圆框
  3. 绘制中间的扫描线(扫描线使用的是bitmap图片),给扫描线做动画使其上下移动
  4. 因为扫描线是矩形图,如何使扫描线只在白色圆框中显示,就又要使用PorterDuff.Mode利用他的颜色图层的规则,关于图层交互规则可查看文档:Google 的官方文档,这里根据文档的规则我使用了PorterDuff.Mode.DST_IN,//只在源图像和目标图像相交的地方绘制目标图像
  5. 这里所以我画了一个特殊的形状的bitmap,可查看下面方法createCircularBitmap,白色圆形上面是一个矩形,防止我的扫描线bitmap超出我的绘制区域。
  6. 需要注意的是bitmap必须要有内容填充,因为PorterDuff.Mode其实是对图像内容的处理

源码如下:注释写的很清楚


/**
 * 识别蒙层
 * 参考:https://www.jianshu.com/p/2feac6a535a3
 * https://www.jianshu.com/p/134cd2dbb43b
 */
class CoinScanMaskView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    private var circleRadius = 0f
    private var width = 0f
    private var height = 0f
    private var circleDrawLeft = 0f
    private var circleDrawTop = 0f

    private val mPaint = Paint()

    private val mTextPaint = Paint().apply {
        color = resources.getColor(R.color.white)
        textSize = 14.dp.toFloat()
        isAntiAlias = true
        isDither = true
        style = Paint.Style.FILL_AND_STROKE
        textAlign = Paint.Align.CENTER
    }
    private val clearMode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
    private val circleBitmap: Bitmap =
        BitmapFactory.decodeResource(resources, R.drawable.ic_coin_recognition_circle)
    private var tipText = "Ready in a moment…"

    // 扫描线
    private val scanBitmap: Bitmap =
        BitmapFactory.decodeResource(resources, R.drawable.identify_ic_camera_scan_line)
    private var mScanLineTop = 0f
    private val mScanLineRectF = RectF()
    private val clipScanClearMode =
        PorterDuffXfermode(PorterDuff.Mode.DST_IN)//只在源图像和目标图像相交的地方绘制目标图像
    private var clipBitmap: Bitmap
    private var needCanvasScan = false


    init {
        clipBitmap = createCircularBitmap()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        circleRadius = ((circleBitmap.width / 2)).toFloat()
        width = measuredWidth.toFloat()
        height = measuredHeight.toFloat()
        circleDrawLeft = (width - circleRadius * 2) / 2
        circleDrawTop = (height - circleRadius * 2) / 2

    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        mPaint.reset()
        val layerID = canvas.saveLayer(0f, 0f, width, height, mPaint)
        // 蒙层背景
        mPaint.color = 0x66000000
        canvas.drawRect(0f, 0f, width, height, mPaint)
        // 中间透明区域
        mPaint.xfermode = clearMode
        mPaint.color = 0x00000000
        canvas.drawCircle(
            width / 2, height / 2, circleRadius,
            mPaint
        )
        canvas.restoreToCount(layerID)
        mPaint.reset()

        // 提示文字
        canvas.drawText(
            tipText,
            (width / 2).toFloat(),
            height / 2 + circleRadius + 32.dp,
            mTextPaint
        )

        //圆形边框
        canvas.drawBitmap(
            circleBitmap,
            circleDrawLeft,
            circleDrawTop,
            mPaint
        )

        //放开该行代码可查看自己绘制的 形状,与下面的scanBitmap扫描的相交
//        canvas.drawBitmap(
//            clipBitmap, circleDrawLeft,
//            circleDrawTop - scanBitmap.height, mPaint
//        )
        //再画上结果
        if (needCanvasScan) {
            val layerScanId =
                canvas.saveLayer(0f, 0f, width, height, null, Canvas.ALL_SAVE_FLAG)
            mScanLineRectF.set(
                circleDrawLeft, circleDrawTop + mScanLineTop - scanBitmap.height,
                circleDrawLeft + clipBitmap.width,
                circleDrawTop + mScanLineTop
            )
            //绘制第一层,目标图像,在下层
            canvas.drawBitmap(
                scanBitmap,
                null,
                mScanLineRectF,
                mPaint
            )
            mPaint.xfermode = clipScanClearMode
            //在圆形上面绘制一个画布,防止扫描线bitmap透出显示,源图像,第二层盖在了目标图像上
            canvas.drawBitmap(
                clipBitmap, circleDrawLeft,
                circleDrawTop - scanBitmap.height, mPaint
            )
            mPaint.xfermode = null
            canvas.restoreToCount(layerScanId)
            moveScanLine()
        }
    }


    /**
     * 移动扫描线
     */
    private fun moveScanLine() {
        mScanLineTop += 2.dp
        if (mScanLineTop > circleBitmap.height) {
            mScanLineTop = 0f
        }
        postInvalidateDelayed(16, 0, 0, measuredWidth, measuredHeight)
    }

    private fun createCircularBitmap(): Bitmap {
        // 创建一个长方形的 Bitmap
        val size = 190.dp
        // 设置 Bitmap 大小
        val bitmap = Bitmap.createBitmap(size, size + scanBitmap.height, Bitmap.Config.ARGB_8888)

        // 创建一个 Canvas 对象,将 bitmap 作为绘制目标
        val canvas = Canvas(bitmap)

        // 创建一个 Paint 对象,用于设置绘制属性
        val paint = Paint().apply {
            color = Color.RED // 设置圆形区域的颜色,必须要有颜色不然合成的时候无效
        }

        // 创建一个圆形路径
        val centerX = size / 2f
        val centerY = size / 2f
        val radius = size / 2f
        //绘制一个圆形,因为只想要圆形区域,所以只绘制圆形区域
        val path = Path().apply {
            addCircle(centerX, centerY + scanBitmap.height, radius, Path.Direction.CW)
        }
        //在圆形上面绘制一个长方形,防止扫描线bitmap透出显示
        //放开该行代码可查看自己绘制的 长方形形状,与下面的scanBitmap扫描的相交
//        path.addRect(0f, 0f, size.toFloat(), scanBitmap.height.toFloat(), Path.Direction.CW)

        // 在 Canvas 上绘制圆形路径
        canvas.drawPath(path, paint)
        return bitmap
    }

    override fun onVisibilityChanged(changedView: View, visibility: Int) {
        super.onVisibilityChanged(changedView, visibility)
        needCanvasScan = visibility == View.VISIBLE
        if (needCanvasScan) {
            mScanLineTop = 0f
        }
    }

}

参考:www.jianshu.com/p/134cd2dbb…

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYrZ6QMM' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片