栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Android 自定义验证码View,可控制输入数量,可定制化样式。高度可定制化。Kotlin版本

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Android 自定义验证码View,可控制输入数量,可定制化样式。高度可定制化。Kotlin版本

文章目录

前言分析核心代码使用

前言

需要开发一个自定义验证码输入控件,允许根据业务需求控制输入的验证码的数量,比如:

允许输入6位数的验证码 ⬇️⬇️⬇️⬇️⬇️⬇️

允许输入4位数的验证码⬇️⬇️⬇️⬇️⬇️⬇️

所以就手动撸一个吧!‍♀️

分析

既然需要允许可控制输入的数量,那么需要一个容器且其中每一个ItemView都应该是动态添加的。所以下面分别对容器和ItemView的实现进行简单的分析。

ItemView

    从上图分析,ItemView需要的元素是这么几个,显示数字、下划线、光标需要有这么几种状态,光标闪烁、空白(即还没有被输入到)、数字显示(填充)

容器

    对于容器来说,首先肯定需要输入,所以需要一个背景透明的EditText作为输入需要一个容器,这里选用LinearLayout作为容器,动态添加ItemView

代码结构
对于容器层面来说,需要在不同的时候分别调用,ItemView的不同状态显示,所以可以封装接口,用来控制ItemView的几种状态(光标闪烁、空白(即还没有被输入到)、数字显示(填充))来供容器调用。
这样一个封装好的容器,只需要对接实现这个接口的ItemView即可,至于ItemView则可以随时进行替换。

好了,上面就是大致思路,下面来核心代码解析。

核心代码

接口
经过上面分析,我们知道需要提供一个接口供ItemView实现,提供三种样式的调用方法。


interface VerifyCodeItemViewStyle {

    
    fun displayNumStyle(char: Char)

    
    fun cursorBlinksStyle()

    
    fun defaultStyle()
}

默认ItemView实现


open class DefaultVerifyCodeItemView
@JvmOverloads
constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr), VerifyCodeItemViewStyle {

    
    val tvValue = TextView(context)

    
    var cursorControl = false

    
    val mPaint: Paint = Paint().apply {
        isAntiAlias = true
        style = Paint.Style.FILL
        color = ContextCompat.getColor(context, R.color.gray_3394EC)
    }

    
    var timer: CountDownTimer? = null

    init {
        //自身相关属性设置
        orientation = VERTICAL
        gravity = Gravity.CENTER
        layoutParams =
            LayoutParams(LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
    }
    
    
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        //默认ViewGroup不会进行绘制 所以需要允许进行绘制
        setWillNotDraw(false)

        //textView 相关属性设置
        val width = 46F.dpToPx
        val height = 56F.dpToPx
        val lpt = LayoutParams(width, height)
        tvValue.setBackgroundColor(ContextCompat.getColor(context, R.color.transparent))
        tvValue.gravity = Gravity.CENTER
        tvValue.setTextColor(ContextCompat.getColor(context, R.color.black))
        tvValue.textSize = 48F
        tvValue.typeface = Typeface.defaultFromStyle(Typeface.BOLD)
        //添加进容器
        addView(tvValue, lpt)

        //添加下划线
        val heightLine = 2F.dpToPx
        val line = View(context)
            .apply {
                setBackgroundColor(ContextCompat.getColor(context, R.color.gray_d9d9d9))
            }
        val lll = LayoutParams(width, heightLine)
        //添加进容器
        addView(line, lll)
    }

    
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        customOnDraw(canvas)
    }

    open fun customOnDraw(canvas: Canvas?) {
        if (cursorControl) {
            //cursor 宽度
            val cursorWidth = 2F.dpToPx.toFloat()

            //绘制坐标
            val xStart = width / 2 - cursorWidth / 2
            val xEnd = width / 2 + cursorWidth / 2
            val hStart = 10F.dpToPx.toFloat()
            val hEnd = height.toFloat() - 8F.dpToPx

            canvas?.drawRect(xStart, hStart, xEnd, hEnd, mPaint)
        }
    }

    override fun displayNumStyle(char: Char) {
        cursorControl = false
        timer?.cancel()
        tvValue.visibility = View.VISIBLE
        tvValue.text = char.toString()
        invalidate()
    }

    override fun cursorBlinksStyle() {
        tvValue.visibility = View.INVISIBLE
        //光标闪烁
        if (timer == null) {
            timer = object : CountDownTimer(Int.MAX_VALUE.toLong(), 600) {
                override fun onTick(millisUntilFinished: Long) {
                    cursorControl = !cursorControl
                    invalidate()
                }

                override fun onFinish() {

                }

            }
        }
        timer?.start()
    }

    override fun defaultStyle() {
        timer?.cancel()
        cursorControl = false
        tvValue.visibility = View.INVISIBLE
        invalidate()
    }

    
    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        timer?.cancel()
        timer = null
    }

}

容器实现
下面进行容器代码实现。

 
open class VerifyCodeView
@JvmOverloads
constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : frameLayout(context, attrs, defStyleAttr) {

    
    val itemContainer = LinearLayout(context)

    
    val editText = EditText(context).apply {
        inputType = InputType.TYPE_CLASS_NUMBER
        isLongClickable = false
        setBackgroundColor(ContextCompat.getColor(context, R.color.transparent))
        setTextColor(ContextCompat.getColor(context, R.color.transparent))
        isCursorVisible = false
    }

    
    var verifyNum = 6
        set(value) {
            field = if (value >= 1 || value <= 8) {
                value
            } else {
                6
            }

            //设置EditText的最大输入数量
            editText.filters = arrayOf(InputFilter.LengthFilter(field))
        }

    
    var inputCompleteListener: InputCompleteListener? = null

    init {
        //解析xml属性
        val attributes = context.obtainStyledAttributes(attrs, R.styleable.VerifyCodeView)
        //验证码位数,默认为6,允许更改
        verifyNum = attributes.getInt(R.styleable.VerifyCodeView_msg_code_length, 6)
        attributes.recycle()
    }

    init {
        //itemContainer 填充默认的itemView
        fillUpDefaultItemView()
    }

    
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        //添加 容器
        addView(itemContainer, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
        //添加 输入框EditText
        addView(editText, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))

        //editText监听处理
        editTextListener()

        //默认第一个为itemView 进行光标闪烁
        itemContainer.takeIf { it.childCount > 0 }?.let { linearLayout ->
            val itemView = try {
                linearLayout.getChildAt(0) as VerifyCodeItemViewStyle
            } catch (e: Exception) {
                throw RuntimeException("VerifyItemView 必须是 VerifyCodeItemViewStyle 类型的", e)
            }
            itemView.cursorBlinksStyle()
        }
    }

    
    private fun editTextListener() {
        editText.afterTextChanged { editable: Editable? ->
            val container = itemContainer
            val childCount = container.childCount
            val content = editable.toString()
            val inputCount = content.length

            for (i in 0 until childCount) {
                //获取itemView
                val itemView = try {
                    container.getChildAt(i) as VerifyCodeItemViewStyle
                } catch (e: Exception) {
                    throw RuntimeException("VerifyItemView 必须是 VerifyCodeItemViewStyle 类型的", e)
                }

                if (i < inputCount) {
                    //设置显示的字体
                    val singleChar = content[i]
                    itemView.displayNumStyle(singleChar)
                    continue
                }

                //光标闪烁
                if (i == inputCount) {
                    itemView.cursorBlinksStyle()
                    continue
                }

                //什么都不显示
                itemView.defaultStyle()
            }

            //验证码是否完成回调
            if (inputCount >= childCount) {
                inputCompleteListener?.inputComplete()
            } else {
                inputCompleteListener?.invalidContent()
            }
        }
    }

    
    open fun getEditContent(): String? = editText.text.toString()

    
    open fun setText(activity: Activity?, inputContent: String?) {
        editText.setText(inputContent)
        if (TextUtils.isEmpty(inputContent)) {
            if (editText != null) {
                editText.setFocusable(true)
                editText.setFocusableInTouchMode(true)
                editText.requestFocus()
                if (editText.getContext() is Activity) {
                    (editText.getContext() as Activity).window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
                } else {
                    val imm =
                        application.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
                    imm?.showSoftInput(editText, InputMethodManager.SHOW_FORCED)
                }
            }
        }
    }

    
    private fun fillUpDefaultItemView() {
        for (i in 0 until verifyNum) {
            val defaultVerifyCodeItemView = DefaultVerifyCodeItemView(context)
            itemContainer.addView(
                defaultVerifyCodeItemView,
                LinearLayout.LayoutParams(
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    1F
                )
            )
        }
    }

    
    open fun  fillUpCustomItemView(customItemView: Class) {
        if (VerifyCodeItemViewStyle::class.java.isAssignableFrom(customItemView)) {
            //先移除所有的View
            itemContainer.removeAllViews()
            for (i in 0 until verifyNum) {
                val verifyCodeItemView = try {
                    customItemView.getConstructor(Context::class.java).newInstance(context) as View
                } catch (e: Exception) {
                    throw RuntimeException("不能够创建一个实例 $customItemView , 或者不是一个View类型", e)
                }
                //填充View
                itemContainer.addView(
                    verifyCodeItemView,
                    LinearLayout.LayoutParams(
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT,
                        1F
                    )
                )
            }
        }
    }
}

其他非核心辅助代码

//辅助工具类


interface InputCompleteListener {
    fun inputComplete()
    fun invalidContent()
}


inline fun EditText.afterTextChanged(crossinline block: (Editable?) -> Unit = {}): TextWatcher {
    return object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            block(s)
        }

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

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        }
    }.also {
        addTextChangedListener(it)
    }

}

val Float.dpToPx
    get() = dp2px(this)
    
fun dp2px(dpValue: Float): Int {
    val scale = application.resources.displayMetrics.density
    return (dpValue * scale + 0.5f).toInt()
}

//自定义属性 在attrs.xml 中添加
    
        
        
    
使用

简单使用

     //设置验证码个数

可以代码中调用fillUpCustomItemView函数,手动填充自定义的ItemView。
可以在代码中调用verifyNum设置验证码数量。

创作不易,如有帮助一键三连咯‍♀️。欢迎技术探讨噢!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/715554.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号