安卓自定义验证码输入框

自定义了一个验证码输入框,先看效果

效果图

本来这种需求花点时间可以自定义一个EditText,但是时间有限,我用了种取巧的方式,在FrameLayout中嵌套EditTextLinearLayout,然后隐藏EditText的文字并在LinearLayoutadd数个TextView

代码:


import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.Editable
import android.text.InputFilter
import android.text.InputType
import android.text.TextWatcher
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.get

/**
 *
 *Created by LG on 2021/4/21.
 */
class VerificationCodeView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), TextWatcher {
    private var maxLength = 6
    private var cursorColor = 0//光标颜色
    private var numberColor = 0//字符颜色
    private var numberFocusedBackground: Drawable? = null//获取焦点的字符背景
    private var numberBackground: Drawable? = null//默认字符背景
    private var numberSpacing = 0//字符间距
    private var numberSize = 0f//字符大小 px
    private val editText: EditText = EditText(context)
    private val linearLayout = LinearLayout(context)
    private var animator: Animator? = null

    //输入完成回调
    var onInputComplete: ((text: String) -> Unit)? = null

    init {
        val a = context.obtainStyledAttributes(attrs, R.styleable.VerificationCodeView)
        maxLength = a.getInt(R.styleable.VerificationCodeView_maxLength, 6)
        cursorColor = a.getColor(R.styleable.VerificationCodeView_cursorColor, Color.BLUE)
        numberColor = a.getColor(R.styleable.VerificationCodeView_numberColor, Color.WHITE)
        numberBackground = a.getDrawable(R.styleable.VerificationCodeView_numberBackground)
        numberFocusedBackground = a.getDrawable(R.styleable.VerificationCodeView_numberFocusedBackground)
        numberSpacing = a.getDimensionPixelSize(R.styleable.VerificationCodeView_numberSpacing, 10)
        numberSize = a.getDimension(R.styleable.VerificationCodeView_numberSize, 20f)
        a.recycle()
        editText.apply {
            inputType = InputType.TYPE_CLASS_NUMBER
            isCursorVisible = false
            textSize = 0f
            filters = arrayOf(InputFilter.LengthFilter(maxLength))
            background = null
            setTextIsSelectable(false)
            layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
            addTextChangedListener(this@VerificationCodeView)
        }
        addView(editText)
        linearLayout.apply {
            layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
            orientation = LinearLayout.HORIZONTAL
        }
        addView(linearLayout)
        repeat(maxLength) {
            val textView = TextView(context).apply {
                gravity = Gravity.CENTER
                layoutParams = LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT).apply {
                    weight = 1f
                    if (it != 0) {
                        marginStart = numberSpacing
                    }
                }
                background = numberBackground
            }
            linearLayout.addView(textView)
        }
        updateCursor(0)
    }

    private fun updateCursor(index: Int) {
        animator?.removeAllListeners()
        animator?.cancel()
        val tv = (linearLayout[index] as TextView)
        tv.background = numberFocusedBackground
        //如果不需要光标可以把这个函数的以下部分注释掉
        tv.text = "|"
        tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, numberSize - 2)
        val r = cursorColor shr 16 and 0xFF
        val g = cursorColor shr 8 and 0xFF
        val b = cursorColor and 0xFF
        animator = ValueAnimator.ofFloat(1f, 0f).also {
            it.duration = 1000
            it.repeatCount = ValueAnimator.INFINITE
            it.repeatMode = ValueAnimator.RESTART
            it.startDelay = 100
            it.addUpdateListener { anim ->
                val a = (255 * (anim.animatedValue as Float)).toInt()
                tv.setTextColor(Color.argb(a, r, g, b))
            }
        }
        animator!!.start()
    }

    override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
    }

    override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
    }

    override fun afterTextChanged(s: Editable) {
        val length = s.length
        repeat(maxLength) {
            val char = s.getOrNull(it)
            (linearLayout[it] as TextView).apply {
                text = char?.toString()
                setTextColor(numberColor)
                setTextSize(TypedValue.COMPLEX_UNIT_PX, numberSize)
                background = if (it > length) numberBackground else numberFocusedBackground
            }
        }
        if (length < maxLength) {
            updateCursor(length)
        } else {
            Log.d(TAG, "afterTextChanged: $s")
            animator?.removeAllListeners()
            animator?.cancel()
            onInputComplete?.invoke(s.toString())
        }
    }

    companion object {
        private const val TAG = "VerificationCodeView"
    }
}

attr:


    
    
    
    
    
    
    

使用:


输入完成拿到输入的内容

v_verification_code.onInputComplete = {
    Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}

numberBackgroundnumberFocusedBackground是两个drawable,我就不贴代码了,根据自身业务自定义一下就好了,写完感觉值得优化的地方还挺多的,有时间再更新吧

你可能感兴趣的:(安卓自定义验证码输入框)