安卓人机验证View

效果图:

1.添加自定义属性

在values的attrs.xml 中

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CaptchaView">
        <attr name="captchaLength" format="integer" />
        <attr name="captchaFontSize" format="dimension" />
    </declare-styleable>
</resources>

2.自定义View

Kotlin 复制代码
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import androidx.core.content.withStyledAttributes
import kotlin.random.Random

class CaptchaView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    companion object {
        private const val DEFAULT_CAPTCHA_LENGTH = 4
        private const val DEFAULT_WIDTH = 160
        private const val DEFAULT_HEIGHT = 58
        private const val DEFAULT_FONT_SIZE = 24f
        private val GRAY = Color.GRAY
    }

    private var captchaCode: String = ""
    private val textPaint = Paint()
    private val linePaint = Paint()
    private val random = Random.Default

    // 自定义属性
    var captchaLength: Int = DEFAULT_CAPTCHA_LENGTH
        set(value) {
            field = value
            generateNewCaptcha()
        }
    var captchaFontSize: Float = DEFAULT_FONT_SIZE
        set(value) {
            field = value
            textPaint.textSize = value
            invalidate()
        }

    init {
        // 从XML属性读取设置
        context.withStyledAttributes(
            attrs,
            R.styleable.CaptchaView,
            defStyleAttr,
            0
        ) {

            captchaLength = getInt(
                R.styleable.CaptchaView_captchaLength,
                DEFAULT_CAPTCHA_LENGTH
            )

            captchaFontSize = getDimension(
                R.styleable.CaptchaView_captchaFontSize,
                DEFAULT_FONT_SIZE
            )

        }

        // Initialize paints
        textPaint.apply {
            textSize = captchaFontSize
            textAlign = Paint.Align.CENTER
            isAntiAlias = true
        }

        linePaint.apply {
            color = GRAY
            strokeWidth = 1f
            isAntiAlias = true
        }

        generateNewCaptcha()
    }

    fun generateNewCaptcha() {
        captchaCode = generateCaptchaCode(captchaLength)
        invalidate() // Redraw the view
    }

    fun getCaptchaCode(): String = captchaCode

    private fun generateCaptchaCode(length: Int): String {
        return (1..length).joinToString("") { Random.nextInt(10).toString() }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // 获取XML中指定的宽高或使用默认值
        val width = resolveSize(DEFAULT_WIDTH, widthMeasureSpec)
        val height = resolveSize(DEFAULT_HEIGHT, heightMeasureSpec)
        setMeasuredDimension(width, height)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // Set the background color to white
        canvas.drawColor(Color.WHITE)

        // Calculate the base vertical center of the canvas for the text
        val baseTextYPos = (height / 2 - (textPaint.descent() + textPaint.ascent()) / 2)
        val maxVerticalOffset = (captchaFontSize * 0.5f).toInt()

        // Draw each character
        val charWidth = width / captchaCode.length.toFloat()
        captchaCode.forEachIndexed { index, char ->
            val xPos = (index + 0.5f) * charWidth
            val yOffset = random.nextInt(-maxVerticalOffset, maxVerticalOffset + 1)
            val currentYPos = baseTextYPos + yOffset

            textPaint.color = Color.rgb(
                random.nextInt(256),
                random.nextInt(256),
                random.nextInt(256)
            )

            canvas.drawText(char.toString(), xPos, currentYPos, textPaint)
        }

        // Draw some random lines
        for (i in 0..3) {
            val startX = random.nextInt(width)
            val startY = random.nextInt(height)
            val endX = random.nextInt(width)
            val endY = random.nextInt(height)
            canvas.drawLine(
                startX.toFloat(),
                startY.toFloat(),
                endX.toFloat(),
                endY.toFloat(),
                linePaint
            )
        }
    }
}

3.使用

XML 复制代码
  <包名.CaptchaView
     android:id="@+id/captchaContent"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     app:captchaFontSize="28sp"
     app:captchaLength="6" />
Kotlin 复制代码
//获取显示的code
val code = binding.captchaContent.getCaptchaCode()

//刷新
binding.captchaContent.generateNewCaptcha()
相关推荐
学长毕设1 小时前
基于SpringBoot的景点管理系统-附源码
java·spring boot·后端
deli0072 小时前
Redis 缓存穿透、击穿、雪崩:9 组流量实测数据库怎么被打穿
前端
sxlishaobin2 小时前
Maven Setting详解
java·学习·maven
江华森2 小时前
5Mbps 小水管也能扛:K3s 上 k6 压测 + HPA 弹性伸缩实测
前端
知守观2 小时前
一个方法两个触发口:反编译 XXL-Job 后,我看清了 inner/job 子包的裸奔现状
java·spring boot·微服务
用户3126874877202 小时前
CompletableFuture 链式编排:异步编程的优雅解法
java
shuangyueliao2 小时前
103 个小游戏的商店素材,我一张图都没手动做:截图 / 封面 / 打包 / 提审全自动
前端
ZhengEnCi2 小时前
前端项目为什么需要一份统一的 theme.css:用 CSS 变量管住全局样式与主题切换
前端·css·前端工程化
wordbaby2 小时前
一次微信小程序 `web-view` 跳转原生页回跳后 H5 白屏的排查与修复(Hash 路由污染)
前端
用户721880438352 小时前
密码骰子:结合AIUI在眼镜上生成一串16位密码
前端