美文网首页
Android自定义密码&验证码输入框

Android自定义密码&验证码输入框

作者: 刺客的幻影 | 来源:发表于2018-11-13 11:15 被阅读0次

    效果图:

    cell_edittext.gif

    支持功能:

    • 键盘的自定义
    • 输入焦点选中框
    • 输入类型password,number,text
    • 长按粘贴

    下面主要讲一下自定义输入框的实现思路:

    本来产品的要求是实现一个验证码输入框,主流的验证码都是纯数字组成,所以刚开始想到用自定义数字键盘和输入框来实现,也就有了这篇文章https://www.jianshu.com/p/6c51be7a8371
    但是,产品之后又提出另外一个需求,要求能输入密码。这个不仅要支持数字,还要支持各种文本,符号。所以还是老老实实用系统自带的键盘吧。想到的解决办法就是在输入框上面盖上一个透明的EditText来与键盘交互。通过监听键盘的输入达到输入框、EditText的联动。

    实现步骤:

    1. 自定义属性:
      isPassword 输入类型是否为密码(因为设计图上密码效果和Android自带效果不一致,需要自定义,单独拿出来与inputType区分)
      textColor 字体颜色
      textSize 字体大小
      inputType 输入类型 (支持number、text)
     <declare-styleable name="CellInputView">
            <attr name="isPassword" format="boolean" />
            <attr name="textColor" format="color|reference" />
            <attr name="textSize" format="float" />
            <attr name="inputType" format="enum">
                <enum name="number" value="0x00000002" />
                <enum name="text" value="0x00000001" />
            </attr>
        </declare-styleable>
    

    2.布局,使用透明的EditText覆盖到单个的输入框上

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <LinearLayout
            android:id="@+id/ll_verity"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
    
            <TextView
                android:id="@+id/tv_pay1"
                style="@style/CellInputStyle" />
    
            <TextView
                android:id="@+id/tv_pay2"
                style="@style/CellInputStyle" />
    
            <TextView
                android:id="@+id/tv_pay3"
                style="@style/CellInputStyle" />
    
            <TextView
                android:id="@+id/tv_pay4"
                style="@style/CellInputStyle" />
    
            <TextView
                android:id="@+id/tv_pay5"
                style="@style/CellInputStyle" />
    
            <TextView
                android:id="@+id/tv_pay6"
                style="@style/CellInputStyle" />
    
        </LinearLayout>
    
        <EditText
            android:id="@+id/et_input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/transparent"
            android:cursorVisible="false"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:imeOptions="actionNext"
            android:inputType="text"
            android:maxLength="6"
            android:textColor="@android:color/transparent" />
    
    </RelativeLayout>
    
    
    
     <style name="CellInputStyle">
            <item name="android:layout_width">wrap_content</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="android:layout_marginStart">16dp</item>
            <item name="android:background">@drawable/input_cell_bg</item>
            <item name="android:gravity">center</item>
            <item name="android:textColor">#396AFC</item>
            <item name="android:textSize">20dp</item>
        </style>
    
    1. 实现逻辑
    • 将屏幕去掉固定间隔,分成6等分,得到单个正方形输入框的边长
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
            View.inflate(context, R.layout.input_cell_layout, this)
            var textColor = Color.parseColor("#396AFC")
            var textSize = 16f
            attrs?.let {
                context.obtainStyledAttributes(attrs, R.styleable.CellInputView)?.let {
                    isPassword = it.getBoolean(R.styleable.CellInputView_isPassword, false)
                    textColor = it.getColor(R.styleable.CellInputView_textColor, Color.parseColor("#1B1B4E"))
                    textSize = it.getFloat(R.styleable.CellInputView_textSize, 16f)
                    inputType = it.getInt(R.styleable.CellInputView_inputType, EditorInfo.TYPE_CLASS_NUMBER)
                    it.recycle()
                }
            }
    
            val width = (context.resources.displayMetrics.widthPixels - UIUtils.dp2px(context, 16f) * 7) / 6
            for (i in 0 until ll_verity.childCount) {
                val child = ll_verity.getChildAt(i)
                val layoutParams = child.layoutParams
                layoutParams.width = width
                layoutParams.height = width
                child.layoutParams = layoutParams
                textViews[i] = child as TextView
                textViews[i]?.setTextColor(textColor)
                textViews[i]?.textSize = textSize
            }
            val layoutParams = et_input.layoutParams as LayoutParams
            layoutParams.height = width
            et_input.layoutParams = layoutParams
            et_input.inputType = inputType
            textViews[0]?.isSelected = true
            setEditTextListener()
        }
    
    • 定义输入完毕的回调接口
     interface InputCompleteListener {
            fun inputComplete()
        }
    fun setInputCompleteListener(inputCompleteListener: InputCompleteListener) {
            this.inputCompleteListener = inputCompleteListener
        }
    
    • 监听EditText的输入变化,改变单个输入框的显示类型和内容
      et_input.addTextChangedListener(object : TextWatcher {
                override fun afterTextChanged(s: Editable?) {
                    inputContent = et_input.text.toString()
                    if (inputContent.length >= number) {
                        inputCompleteListener?.inputComplete()
                    } else {
                        inputCompleteListener?.invalidContent()
                    }
                    for (i in 0 until number) {
                        textViews[i]?.isSelected = false
                        if (i < inputContent.length) {
                            textViews[i]?.text = if (isPassword) "●" else inputContent[i].toString()
                        } else {
                            textViews[i]?.text = ""
                        }
                    }
                    when {
                        number - 1 <= inputContent.length -> textViews[5]?.isSelected = true
                        inputContent.isEmpty() -> textViews[0]?.isSelected = true
                        else -> textViews[inputContent.length]?.isSelected = true
                    }
                }
    
                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
                }
    
                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                }
    
            })
    
    • 完整代码
    package com.po1arbear.custom.celledittext
    
    import android.annotation.SuppressLint
    import android.content.Context
    
    import android.graphics.Color
    import android.support.v4.content.ContextCompat
    import android.text.Editable
    import android.text.TextWatcher
    import android.text.method.DigitsKeyListener
    import android.util.AttributeSet
    import android.view.View
    import android.view.inputmethod.EditorInfo
    import android.view.inputmethod.InputMethodManager
    import android.widget.RelativeLayout
    import android.widget.TextView
    import kotlinx.android.synthetic.main.input_cell_layout.view.*
    
    class CellInputView : RelativeLayout {
    
        private val number = 6
        private val textViews: Array<TextView?> = arrayOfNulls(number)
        private var inputContent: String = ""
        private var isPassword = false
        private var inputType = 0
    
        constructor (context: Context) : this(context, null)
    
        constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    
        @SuppressLint("Recycle")
        constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
            View.inflate(context, R.layout.input_cell_layout, this)
            var textColor = Color.parseColor("#396AFC")
            var textSize = 16f
            attrs?.let {
                context.obtainStyledAttributes(attrs, R.styleable.CellInputView)?.let {
                    isPassword = it.getBoolean(R.styleable.CellInputView_isPassword, false)
                    textColor = it.getColor(R.styleable.CellInputView_textColor, Color.parseColor("#1B1B4E"))
                    textSize = it.getFloat(R.styleable.CellInputView_textSize, 16f)
                    inputType = it.getInt(R.styleable.CellInputView_inputType, EditorInfo.TYPE_CLASS_NUMBER)
                    it.recycle()
                }
            }
    
            val width = (context.resources.displayMetrics.widthPixels - UIUtils.dp2px(context, 16f) * 7) / 6
            for (i in 0 until ll_verity.childCount) {
                val child = ll_verity.getChildAt(i)
                val layoutParams = child.layoutParams
                layoutParams.width = width
                layoutParams.height = width
                child.layoutParams = layoutParams
                textViews[i] = child as TextView
                textViews[i]?.setTextColor(textColor)
                textViews[i]?.textSize = textSize
            }
            val layoutParams = et_input.layoutParams as LayoutParams
            layoutParams.height = width
            et_input.layoutParams = layoutParams
            et_input.inputType = inputType
            textViews[0]?.isSelected = true
            setEditTextListener()
        }
    
        private fun setEditTextListener() {
            et_input.addTextChangedListener(object : TextWatcher {
                override fun afterTextChanged(s: Editable?) {
                    inputContent = et_input.text.toString()
                    if (inputContent.length >= number) {
                        inputCompleteListener?.inputComplete()
                    }
                    for (i in 0 until number) {
                        textViews[i]?.isSelected = false
                        if (i < inputContent.length) {
                            textViews[i]?.text = if (isPassword) "●" else inputContent[i].toString()
                        } else {
                            textViews[i]?.text = ""
                        }
                    }
                    when {
                        number - 1 <= inputContent.length -> textViews[5]?.isSelected = true
                        inputContent.isEmpty() -> textViews[0]?.isSelected = true
                        else -> textViews[inputContent.length]?.isSelected = true
                    }
                }
    
                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
    
                }
    
                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    
                }
    
            })
    
    
            et_input.setOnEditorActionListener(TextView.OnEditorActionListener { v, actionId, _ ->
                if (actionId == EditorInfo.IME_ACTION_NEXT) {
                    inputContent = et_input.text.toString()
                    if (inputContent.length >= number) {
                        inputCompleteListener?.inputComplete()
                    }
                    return@OnEditorActionListener true
                }
                false
            })
    
        }
    
        fun clearContent() {
            et_input.setText("")
        }
    
        private var inputCompleteListener: InputCompleteListener? = null
    
        fun setInputCompleteListener(inputCompleteListener: InputCompleteListener) {
            this.inputCompleteListener = inputCompleteListener
        }
    
        interface InputCompleteListener {
    
            fun inputComplete()
    
            fun invalidContent() {}
        }
    
        fun setDigitsKeyListener(digitsKeyListener: DigitsKeyListener) {
            et_input.keyListener = digitsKeyListener
        }
    
        fun setIsPassword(isPassword: Boolean) {
            this.isPassword = isPassword
        }
    
        fun setTextColor(color: Int) {
            textViews.forEach { it?.setTextColor(ContextCompat.getColor(context, color)) }
        }
    
        fun getEditContent(): String {
            return inputContent
        }
    
        fun showKeyboard() {
            et_input.isFocusable = true
            et_input.isFocusableInTouchMode = true
            et_input.requestFocus()
            val inputManager = context
                    .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            inputManager.showSoftInput(et_input, 0)
        }
    
        fun hideKeyboard() {
            val inputManager: InputMethodManager? =
                    context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            inputManager?.hideSoftInputFromWindow(windowToken, 0)
        }
    
    
    }
    

    项目地址:https://github.com/po1arbear/CellEditText.git

    相关文章

      网友评论

          本文标题:Android自定义密码&验证码输入框

          本文链接:https://www.haomeiwen.com/subject/mpacfqtx.html