美文网首页
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