验证码自定义控件

文章目录

      • 效果图
      • 控件源代码
      • Style
      • Xml
      • Code

效果图

在这里插入图片描述
自定义边框
在这里插入图片描述
内部控制倒计时
在这里插入图片描述
居中/宽度自适应
在这里插入图片描述

控件源代码


class VerificationCodeView : View {
    val mContext: Context
    var enableStr: String
    var disableStr: String
    var totalCountdownTime: Int
    var mTickCount: Long = 0
    var enablColor: Int
    var disablColor: Int
    var boardColor: Int
    var showBoardColor = false
    var mEnable = true
    var textsize: Float = 0F
    var padding: Float = 0F
    var strokeWidth: Float = 0F
    var radiusSize: Float = 0F

    var fatherHeight: Float = 0F
    var fatherWidth: Float = 0F

    lateinit var fatherRectF: RectF
    lateinit var textPaint: TextPaint
    lateinit var boardPaint: Paint

    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
    ) {
        mContext = context
        val a = context.obtainStyledAttributes(attrs, R.styleable.VerificationCodeView)
        totalCountdownTime = a.getInteger(R.styleable.VerificationCodeView_totalCountdownTime, 60)
        textsize = a.getDimension(R.styleable.VerificationCodeView_codeTextSize, dp2px(14f))
        showBoardColor = a.getBoolean(R.styleable.VerificationCodeView_showBoardColor, false)
        mEnable = a.getBoolean(R.styleable.VerificationCodeView_enable, true)
        enableStr = a.getString(R.styleable.VerificationCodeView_enableStr) ?: mContext.getString(
            com.guide.strings.R.string.userinfo_get_code
        )
        disableStr = a.getString(R.styleable.VerificationCodeView_disableStr) ?: "%dS"
        enablColor = a.getColor(
            R.styleable.VerificationCodeView_enableColor, ContextCompat.getColor(
                mContext, color.lib_common_selected
            )
        )
        disablColor = a.getColor(
            R.styleable.VerificationCodeView_disableColor, ContextCompat.getColor(
                mContext, color.lib_common_color_dd
            )
        )

        boardColor = a.getColor(
            R.styleable.VerificationCodeView_boardColor, ContextCompat.getColor(
                mContext, color.lib_common_selected
            )
        )
        a.recycle()
        init()
    }


    private fun init() {
        setWillNotDraw(false)
        padding = dp2px(40F)
        strokeWidth = dp2px(1F)
        radiusSize = dp2px(3F)
        initPaint()
    }

    private fun initPaint() {
        textPaint = TextPaint()
        textPaint.color = enablColor
        textPaint.textSize = textsize
        textPaint.style = Paint.Style.FILL

        boardPaint = Paint()
        boardPaint.color = boardColor
        boardPaint.isAntiAlias = true
        boardPaint.style = Paint.Style.STROKE
        boardPaint.strokeWidth = strokeWidth


        fatherWidth = getTextW(textPaint, enableStr) + padding
        fatherHeight = getTextH(textPaint, enableStr) + padding / 2
    }

    /**
     * 开启倒计时
     */
    fun startCountdown() {
        mViewCountDownTimer?.cancel()
        mViewCountDownTimer = ViewCountDownTimer(totalCountdownTime * 1000L, 1000L)
        mViewCountDownTimer?.start()
    }

    fun cancelCountdown() {
        mViewCountDownTimer?.cancel()
        mViewCountDownTimer = null
    }

    override fun onDetachedFromWindow() {
        cancelCountdown()
        super.onDetachedFromWindow()
    }

    var mViewCountDownTimer: ViewCountDownTimer? = null

    inner class ViewCountDownTimer(millisInFuture: Long, countDownInterval: Long) :
        CountDownTimer(millisInFuture, countDownInterval) {

        override fun onTick(p0: Long) {
            mTickCount = p0 / 1000
            mEnable = false
            postInvalidate()
        }

        override fun onFinish() {
            mEnable = true
            postInvalidate()
        }

    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        if (mEnable) {
            if (isTouch) {
                textPaint.color = disablColor
                boardPaint.color = disablColor
            } else {
                textPaint.color = enablColor
                boardPaint.color = enablColor
            }
            if (showBoardColor) {
                canvas?.drawRoundRect(fatherRectF, radiusSize, radiusSize, boardPaint)
            }
            canvas?.drawText(
                enableStr,
                (fatherWidth - getTextW(textPaint, enableStr)) / 2,
                (getTextH(textPaint, enableStr) + fatherHeight) / 2,
                textPaint
            )
        } else {
            textPaint.color = disablColor
            boardPaint.color = disablColor
            if (showBoardColor) {
                canvas?.drawRoundRect(fatherRectF, radiusSize, radiusSize, boardPaint)
            }
            val format = String.format(disableStr, mTickCount)
            canvas?.drawText(
                format,
                (fatherWidth - getTextW(textPaint, format)) / 2,
                (getTextH(textPaint, format) + fatherHeight) / 2,
                textPaint
            )
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)


        //不能超过父布局大小
        if (fatherWidth > widthSize) {
            fatherWidth = widthSize.toFloat()
        }

        fatherRectF = RectF(0F, 0F, fatherWidth, fatherHeight)

        Log.d("ssss", "widthMode " + widthMode)
        Log.d("ssss", "widthSize " + widthSize)
        Log.d("ssss", "heightSize " + heightSize)

        setMeasuredDimension(fatherWidth.toInt(), fatherHeight.toInt())
    }

    private fun dp2px(dpValue: Float): Float {
        val scale = Resources.getSystem().displayMetrics.density
        return dpValue * scale + 0.5f
    }

    private fun getTextH(pFont: TextPaint, text: String): Int {
        val rect = Rect()
        pFont.getTextBounds(text, 0, text.length, rect)
        return rect.height()
    }

    private fun getTextW(pFont: TextPaint, text: String): Float {
        return pFont.measureText(text)
    }

    var isTouch = false
    var lastAction: Int = -1
    var actionDownTime: Long = 0
    var actionUpTime: Long = 0L
    var clickSpaceTime: Long = 1000L
    var lastClickTime: Long = 0L

    public interface OnClickListener {
        fun onClick()
    }

    var mOnClickListener: OnClickListener? = null

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (MotionEvent.ACTION_DOWN == event.action) {
            actionDownTime = System.currentTimeMillis()
            lastAction = MotionEvent.ACTION_DOWN
            isTouch = true
            invalidate()
        } else if (MotionEvent.ACTION_MOVE == event.action) {
            lastAction = MotionEvent.ACTION_MOVE
            isTouch = true
            invalidate()
        } else if (MotionEvent.ACTION_UP == event.action) {
            lastAction = MotionEvent.ACTION_UP
            actionUpTime = System.currentTimeMillis()
            val canClickSpace = actionUpTime - lastClickTime > clickSpaceTime
            val isClick = actionUpTime - actionDownTime < clickSpaceTime
            if (mEnable && canClickSpace && isClick) {
                lastClickTime = actionUpTime
                mOnClickListener?.onClick()
            }
            isTouch = false
            invalidate()
        }
        return true
    }
}

Style

 <declare-styleable name="VerificationCodeView">
        <attr name="enable" format="boolean" />
        <attr name="showBoardColor" format="boolean" />
        <attr name="disableColor" format="color|reference" />
        <attr name="enableColor" format="color|reference" />
        <attr name="backColor" format="color|reference" />
        <attr name="boardColor" format="color|reference" />
        <attr name="totalCountdownTime" format="integer" />
        <attr name="enableStr" format="reference" />
        <attr name="disableStr" format="reference" />
        <attr name="codeTextSize" format="reference" />
    </declare-styleable>
id 功能
enable 可用标志
showBoardColor 是否展示边框
disableColor 不可用颜色
enableColor 可用颜色
backColor 背景色
boardColor 边框颜色
totalCountdownTime 倒计时总时长
enableStr 可用字符串
disableStr 不可用字符串
codeTextSize 文字大小

Xml

    <com.guide.userinfo.widget.VerificationCodeView
        android:id="@+id/tv_get_code"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="@dimen/lib_common_20dp"
        app:codeTextSize="@dimen/lib_common_12sp"
        app:disableColor="@color/lib_common_gray_999"
        app:disableStr="@string/userinfo_num"
        app:enable="true"
        app:enableStr="@string/userinfo_get_code"
        app:layout_constraintBottom_toBottomOf="@+id/mobile_code"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@+id/mobile_code"
        app:showBoardColor="false"
        app:totalCountdownTime="60" />

Code

手动开启倒计时

     mViewBinding.tvGetCode.mOnClickListener = object : VerificationCodeView.OnClickListener {
            override fun onClick() {
                //发送验证码成功
                mViewBinding.tvGetCode.startCountdown()
            }
        }

你可能感兴趣的:(Android,自定义控件,Android,自定义控件,验证码)