继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

android kotlin 验证码输入框(自定义View)

慕姐8265434
关注TA
已关注
手记 1147
粉丝 221
获赞 1064

需求

  • 限制输入长度

  • 输入字符下有下划线

  • 有光标闪烁

  • 触摸时自动弹出输入法

  • 和editText一样有hint 提示语

  • 输入完毕回调

因为需求要求不是很多,所以暂时实现的比较简单
实现语言

//首先创建一个class 继承View/**
 * 密码输入框
 * 支持多位
 */class InputKeyWordView : View, View.OnFocusChangeListener {

    interface OnKeyWordChangedListener {
        fun onkeyWordChange(keyWord: String, keyWordView: InputKeyWordView)
    }

    companion object {
        private val defaultTxtColr: Int = Color.parseColor("#1A1A1A")
        private val defaultHintColor: Int = Color.parseColor("#9B9B9B")
        private val defaultCursorColor: Int = Color.parseColor("#9B9B9B")
    }    /**
     * 输入类型
     */
    interface InputType {
        companion object {
            val NUMBER: Int
                get() = 0x11

            val NUMBER_CHAR: Int
                get() = 0x12
        }
    }    class InputMatch {
        companion object {
            private val number_reg = "\\d"
            private val numberChar_reg = "\\w"
            val patternNumber = Pattern.compile(number_reg)
            val patternCharNumber = Pattern.compile(numberChar_reg)
        }
    }    //msgWhat
    private val alphaMsg = 0x111
    val mHandler: Handler = @SuppressLint("HandlerLeak")
    object : Handler() {
        override fun handleMessage(msg: Message) {            if (msg.what == alphaMsg) {
                onMessage()
            }
        }
    }

    private fun onMessage() {
        animEnableShow = !animEnableShow
        postInvalidate()
        mHandler.sendEmptyMessageDelayed(alphaMsg, ALPHA_ANIM_DELAY)
    }    //闪动间隔时间
    private val ALPHA_ANIM_DELAY: Long = 800

    //光标 宽度
    private val cursorWidth: Float    //光标颜色
    var cursorColor: Int
        set(value) {
            field = value
            cursorPaint.color = cursorColor
            invalidate()
        }    //   是否 底部线
    var showBottomLine: Boolean
        set(value) {
            field = value
            invalidate()
        }    //底部线的颜色
    var bottomLineColor: Int
        set(value) {
            field = value
            linePaint.color = bottomLineColor
            invalidate()
        }    //底部线的高度
    var bottomLineHeight: Float
        set(value) {
            field = value
            invalidate()
        }    //提示字
    var hint: String
        set(value) {
            field = value
            hintPaint.getTextBounds(hint, 0, hint.length, hintBounds)
            invalidate()
        }    //hint颜色
    var hintColor: Int
        set(value) {
            field = value
            hintPaint.color = hintColor
            invalidate()
        }    //字的颜色
    var txtColor: Int
        set(value) {
            field = value
            txtPaint.color = txtColor
            invalidate()
        }    //字体大小
    var txtSize: Float
        set(value) {
            field = value
            hintPaint.textSize = txtSize
            txtPaint.textSize = txtSize
            invalidate()
        }    /**
     *  输入类型
     *  [InputKeyWordView.InputType]
     */
    var inputType: Int
        set(value) {
            field = value
            invalidate()
        }    //输入位数
    var inputLength: Int
        set(value) {
            field = value
            invalidate()
        }    //    =================================
    private val hintPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private var txtPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private var linePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }
    private var cursorPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
    }

    private val lineInteval: Int    /**
     * 闪动第几个 下划线
     *///    private var alphaLine = 0
    //是否闪动
    private var alphaEnable = false
    private var animEnableShow = true

    //文字大小
    private val hintBounds = Rect()    //输入的文字大小
    private val inputBounds = Rect()    //输入的字符
    private val inputCharArray = StringBuilder()

    private val inputMethodManager: InputMethodManager    // 输入完成监听
    var onKeyWordChangedListener: OnKeyWordChangedListener? = null

    constructor(context: Context) : this(context, null)


    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.InputKeyWordView)
        showBottomLine = typedArray.getBoolean(R.styleable.InputKeyWordView_ipv_show_bottom_line, true)
        bottomLineColor = typedArray.getColor(R.styleable.InputKeyWordView_ipv_bottom_line_color, defaultHintColor)
        bottomLineHeight = typedArray.getDimension(R.styleable.InputKeyWordView_ipv_bottom_line_height, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, context.resources.displayMetrics))
        hintColor = typedArray.getColor(R.styleable.InputKeyWordView_ipv_hint_color, defaultHintColor)
        txtColor = typedArray.getColor(R.styleable.InputKeyWordView_ipv_show_txt_color, defaultTxtColr)
        txtSize = typedArray.getDimension(R.styleable.InputKeyWordView_ipv_txt_size, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14f, context.resources.displayMetrics))
        hint = typedArray.getString(R.styleable.InputKeyWordView_ipv_hint_text) ?: String()
        cursorColor = typedArray.getColor(R.styleable.InputKeyWordView_ipv_cursor_color, defaultCursorColor)
        inputType = typedArray.getInt(R.styleable.InputKeyWordView_ipv_input_type, InputType.NUMBER)
        inputLength = typedArray.getInt(R.styleable.InputKeyWordView_ipv_input_length, 6)
        typedArray.recycle()
        isFocusable = true
        onFocusChangeListener = this
        inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager        //下划线和文字间隔
        lineInteval = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics).toInt()
        cursorWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1f, resources.displayMetrics)        //=========================//        hintPaint.color = hintColor//        hintPaint.textSize = txtSize////        txtPaint.color = txtColor//        txtPaint.textSize = txtSize////        linePaint.color = bottomLineColor//        linePaint.style = Paint.Style.FILL//        hintPaint.getTextBounds(hint, 0, hint.length, hintBounds)
    }    /**
     * 清除已输入的字符
     */
    public fun clearInputKeyWord() {        if (inputCharArray.isNotEmpty()) {
            inputCharArray.delete(0, inputCharArray.lastIndex)
            drawHint = true
            invalidate()
        }

    }    //获取输入的
    public fun getInputKeyWord(): String {        return inputCharArray.toString()
    }

    override fun onCheckIsTextEditor(): Boolean {        return true
    }

    override fun onFocusChange(v: View, hasFocus: Boolean) {
        alphaEnable = hasFocus        if (hasFocus) {
            mHandler.sendEmptyMessageDelayed(alphaMsg, ALPHA_ANIM_DELAY)
            openKeyBroad()
        } else {
            mHandler.removeMessages(alphaMsg)
            closeKeyBroad()
        }
        println("onFocusChange:::::::$hasFocus")
    }

    private fun openKeyBroad() {
        inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
    }

    private fun closeKeyBroad() {
        inputMethodManager.hideSoftInputFromInputMethod(windowToken, InputMethodManager.HIDE_IMPLICIT_ONLY)
    }//重写onCreateInputConnection 用来设置与键盘的链接 
    override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
        println("onCreateInputConnection")
        outAttrs.inputType = when (inputType) {
            InputType.NUMBER -> EditorInfo.TYPE_CLASS_NUMBER
            InputType.NUMBER_CHAR -> EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD            else -> EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
        }//BaseInputConnection 是View 内 用来处理 键盘输入的类
        return object : BaseInputConnection(this, false) {
            override fun commitText(text: CharSequence, newCursorPosition: Int): Boolean {//                println("commitText:::::::" + text)
                if (when (inputType) {
                            InputType.NUMBER -> {
                                InputMatch.patternNumber.matcher(text).matches()
                            }
                            InputType.NUMBER_CHAR -> {
                                InputMatch.patternCharNumber.matcher(text).matches()
                            }                            else -> false
                        }) {
                    addKey(text)
                }                return true
            }

            override fun sendKeyEvent(event: KeyEvent): Boolean {                if (event.action == KeyEvent.ACTION_UP) {
                    when (event.keyCode) {
                        KeyEvent.KEYCODE_ENTER -> {
                            closeKeyBroad()
                        }
                        KeyEvent.KEYCODE_DEL -> {
                            deleteKey()
                        }
                    }
                }                return super.sendKeyEvent(event)
            }
        }
    }//模拟点击时  获取焦点  弹起键盘
    override fun performClick(): Boolean {
        isFocusableInTouchMode = true
        requestFocus()
        openKeyBroad()        return super.performClick()
    }//这里 监听手势,按下时开始处理
    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent): Boolean {        if (event.action == MotionEvent.ACTION_DOWN) {
            isFocusableInTouchMode = true
            requestFocus()
            openKeyBroad()
        }        return super.onTouchEvent(event)
    }//添加输入的字符  放入 字符数组内
    private fun addKey(key: CharSequence) {        if (inputCharArray.length < inputLength) {
            drawHint = false
            inputCharArray.append(key)//            alphaLine = inputCharArray.length
            invalidate()            if (null != onKeyWordChangedListener) {
                onKeyWordChangedListener?.onkeyWordChange(inputCharArray.toString(), this)
            }
        }
    }// 每次点击删除键  删除输入的字符
    private fun deleteKey() {        if (inputCharArray.isNotEmpty()) {
            drawHint = false
            inputCharArray.deleteCharAt(inputCharArray.lastIndex)//            alphaLine = inputCharArray.length
            invalidate()
        }        if (inputCharArray.isEmpty()) {//            alphaLine = 0
            drawHint = true
            invalidate()
        }        if (null != onKeyWordChangedListener) {
            onKeyWordChangedListener?.onkeyWordChange(inputCharArray.toString(), this)
        }
    }

    @SuppressLint("SwitchIntDef")
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec))
    }    //测量宽度
    private fun measureWidth(widthMeasureSpec: Int): Int {
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        var width = suggestedMinimumWidth
        val size = MeasureSpec.getSize(widthMeasureSpec)
        when (widthMode) {        //View想要到多少就到多少,没有限制
            MeasureSpec.AT_MOST                //未指定大小,任意//                , MeasureSpec.UNSPECIFIED
            -> {
                width = MeasureSpec.getSize(MeasureSpec.AT_MOST)
            }        //View指定大小
            MeasureSpec.EXACTLY -> {
                width = size
            }        //        //任意大小
            MeasureSpec.UNSPECIFIED -> {
                width = Math.max(width, size)
            }
        }        return width
    }


    private fun measureHeight(widthMeasureSpec: Int): Int {
        val heightMode = MeasureSpec.getMode(widthMeasureSpec)
        var height = suggestedMinimumHeight
        val size = MeasureSpec.getSize(widthMeasureSpec)
        when (heightMode) {        //根据自己的视图决定,想到多少就到多少
            MeasureSpec.AT_MOST                //未指定大小//                , MeasureSpec.UNSPECIFIED
            -> {
                height = hintBounds.height() + paddingTop + paddingBottom + lineInteval
            }        //指定高度
            MeasureSpec.EXACTLY -> {
                height = size
            }        //未指定,任意大小
            MeasureSpec.UNSPECIFIED -> {                //取最大值
                height = Math.max(height, size)
            }
        }        return height
    }


    private var drawHint: Boolean = true
    override fun onDraw(canvas: Canvas) {        if (drawHint) {
            drawHintTxt(canvas)
        }
        drawInputChar(canvas)
        drawBottomLine(canvas)
    }    //输入的字符
    private fun drawInputChar(canvas: Canvas) {
        val eachWidth = (measuredWidth - lineInteval * (inputLength - 1)) / inputLength        for (index in 0..inputLength) {            if (inputCharArray.length > index) {
                val char = inputCharArray[index].toString()
                txtPaint.getTextBounds(char, 0, char.length, inputBounds)
                val y: Float = ((measuredHeight + inputBounds.height()) / 2).toFloat() - txtPaint.fontMetrics.bottom / 2
                //中间点x
                val startX: Float = (eachWidth / 2 + index * (lineInteval + eachWidth) - inputBounds.width() / 2).toFloat()
                canvas.drawText(char
                        , 0
                        , char.length
                        , startX
                        , y
                        , txtPaint)
            } else break
        }
    }    //底部下划线 和光标
    private fun drawBottomLine(canvas: Canvas) {
        val eachWidth = (measuredWidth - lineInteval * (inputLength - 1)) / inputLength
        val y = (measuredHeight - paddingBottom - bottomLineHeight)
        val bottom = y + bottomLineHeight        for (index in 0..(inputLength - 1)) {
            val startX = index * (eachWidth + lineInteval)
            val stopX = startX + eachWidth            if (showBottomLine) {                //底部下划线
                canvas.drawRect(startX.toFloat(), y, stopX.toFloat(), bottom, linePaint)
            }            //获取焦点光标闪动
            if (alphaEnable) {                // 当前位置
                if (inputCharArray.length == index) {                    if (animEnableShow) {//光标显示
                        val cursorX = startX + eachWidth / 2f
                        canvas.drawRect(cursorX
                                , (measuredHeight - hintBounds.height()) / 2f
                                , cursorX + cursorWidth
                                , (measuredHeight + hintBounds.height()) / 2f
                                , cursorPaint)
                    }
                }
            }
        }
    }    //画提示字
    private fun drawHintTxt(canvas: Canvas) {        if (inputCharArray.isEmpty() && !alphaEnable) {
            val y: Float = ((measuredHeight + hintBounds.height()) / 2).toFloat() - hintPaint.fontMetrics.bottom / 2
            canvas.drawText(hint, 0, hint.length, 0f, y, hintPaint)
        }
    }

}

在attr.xml中配置 自定义属性
需要的可以增加 属性

<!--密码输入框-->
    <declare-styleable name="InputKeyWordView">
        <!--输入位数 默认4位-->
        <attr name="ipv_input_length" format="integer" />
        <!--底部线-->
        <attr name="ipv_show_bottom_line" format="boolean" />
        <!--底部线高度-->
        <attr name="ipv_bottom_line_height" format="dimension" />
        <!--底部线的颜色-->
        <attr name="ipv_bottom_line_color" format="color" />
        <!--显示字的颜色-->
        <attr name="ipv_show_txt_color" format="color" />
        <!--显示字的大小-->
        <attr name="ipv_txt_size" format="dimension" />
        <!--提示字-->
        <attr name="ipv_hint_text" format="string" />
        <!--提示颜色-->
        <attr name="ipv_hint_color" format="color" />
        <!--光标颜色-->
        <attr name="ipv_cursor_color" format="color" />
        <!--输入类型-->
        <attr name="ipv_input_type" format="enum">
            <!--数字-->
            <enum name="number" value="0x11" />
            <!--数字和字母-->
            <enum name="numChar" value="0x12" />
        </attr>

    </declare-styleable>

使用方式

        <com.zfzx.investor.custom.weight.InputKeyWordView
            android:id="@+id/ipv_sms_code"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="1"
            android:paddingBottom="2dp"
            app:ipv_bottom_line_color="?attr/gray_1"
            app:ipv_bottom_line_height="1dp"
            app:ipv_hint_color="?attr/black_gray_1"
            app:ipv_hint_text="@string/sms_verify_code"
            app:ipv_input_length="6"
            app:ipv_input_type="numChar"
            app:ipv_show_bottom_line="true"
            app:ipv_show_txt_color="?attr/black_1"
            app:ipv_txt_size="14sp" />



作者:Mocaris
链接:https://www.jianshu.com/p/b794750c05a1


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP