• 验证码自定义控件


    效果图

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

    控件源代码

    
    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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    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" />
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Code

    手动开启倒计时

         mViewBinding.tvGetCode.mOnClickListener = object : VerificationCodeView.OnClickListener {
                override fun onClick() {
                    //发送验证码成功
                    mViewBinding.tvGetCode.startCountdown()
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    最新NVIDIA英伟达GPU显卡算力表
    迅为RK3568开发Android12系统烧写 Android 固件
    【手把手带你学JavaSE】第五篇:Java中的数组
    OSPF复习
    前端面试基础学习笔记
    【系统架构】-什么是MDA架构、ADL、DSSA
    什么是AIGC
    Kafka中的acks机制——一次由错误资料引发的源码学习
    Rust-是否使用Rc<T>
    【第二十一讲】参数解析器
  • 原文地址:https://blog.csdn.net/qq_20330595/article/details/133298722