安卓人机验证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()
相关推荐
观望过往2 小时前
【Java SE 运算符】全面解析与实践指南
java
没有bug.的程序员2 小时前
分布式架构初识:为什么需要分布式
java·分布式·架构·php
闲人编程2 小时前
2025年,如何选择Python Web框架:Django, Flask还是FastAPI?
前端·后端·python·django·flask·fastapi·web
郑州光合科技余经理2 小时前
微服务架构:基于Spring Cloud ,构建同城生活服务平台
java·spring cloud·微服务·小程序·架构·uni-app
光影少年2 小时前
react打包优化和配置优化都有哪些?
前端·react.js·掘金·金石计划
ajassi20002 小时前
开源 java android app 开发(十七)封库--混淆源码
android·java·开源
Aoda2 小时前
企业级项目结构设计的思考与实践 —— 以 PawHaven 为例
前端·架构
海梨花3 小时前
关于Java的几个小问题
java·面试
若无_3 小时前
深入理解 Vue 中的 reactive 与 ref:响应式数据的两种核心实现
前端·vue.js