First, the final effect drawing

Analysis of implementation ideas: 1. Use a LinearLayout to fill each of the small squares, and dynamically add the number of input boxes needed. 2. Set the EditText input background and text to transparent, and set no display cursor. 3. Listen to the EditText content change, and bind the Content to the LinearLayout, so that each input is displayed by the sublayout of the LinearLayout

Layout file

<? The XML version = "1.0" encoding = "utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="wrap_content"> <LinearLayout android:orientation="horizontal" android:id="@+id/rvContentList" android:gravity="center" android:showDividers="middle" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <EditText android:id="@+id/inputReal" android:inputType="number" android:background="@android:color/transparent" android:layout_width="wrap_content" android:layout_height="wrap_content"  android:textColor="@android:color/transparent"/> </RelativeLayout>Copy the code

Dynamically create the LinearLayout sublayout fill in your code and bind the listener

Private Fun initContainer() {// Dynamically set the size of the EditText inputReal = findViewById(R.id.inputreal) rvContentList = findViewById(R.id.rvContentList) inputReal.width = (dividerDrawable? .minimumWidth ? : 0 * (verifyCodeLen - 1)) + inputBoxSize * verifyCodeLen inputReal.height = inputBoxSize inputReal.setTextSize(TypedValue.COMPLEX_UNIT_PX, InputTextSize * 1.0 F) / / disable the cursor inputReal isCursorVisible = false inputReal. Filters = ArrayOf (inputfilter.lengthfilter (verifyCodeLen)) inputtextview.clear () // dynamically add dividerDrawable between LinearLayout? .let { it.setBounds(0, 0, it.minimumWidth, it.minimumHeight) rvContentList.dividerDrawable = it } for (i in 0 until verifyCodeLen) { val textView = TextView(context) textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Width = inputBoxSize TextView. height = inputBoxSize textView.gravity = gravity textView.isFocusable = false textView.textColor = inputTextColor textView.backgroundResource = itemSelector inputTextView.add(textView) } inputTextView.forEach { rvContentList.addView(it) } }Copy the code
inputReal.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(p0: Editable?) { setVerifyCodeInputValue(p0.toString()) if (p0.toString().length == verifyCodeLen) { onCompleteListener? .onComplete(p0.toString()) } } override fun beforeTextChanged(p0: CharSequence? , p1: Int, p2: Int, p3: Int) { } override fun onTextChanged(p0: CharSequence? , p1: Int, p2: Int, p3: Int) { } })Copy the code
private fun setVerifyCodeInputValue(inputText: String) {
   inputTextView.forEach {
        it.text = ""
        it.isSelected = false
    }
    inputTextView.forEachIndexed { index, textView ->
        if (inputText.length > index) {
            textView.isSelected = true
            textView.text = inputText[index].toString()
        }
    }
}
Copy the code

The core code is here, in order to facilitate the extension, you can add custom attributes, dynamically set the extension effect, here is not clear, directly look at the code

Finally put the full source code:

package org.fireking.ap.custom.viewgroup.view

import android.content.Context
import android.content.res.TypedArray
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.text.Editable
import android.text.InputFilter
import android.text.Spanned
import android.text.TextWatcher
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
import android.widget.*
import androidx.core.view.forEach
import androidx.recyclerview.widget.RecyclerView
import org.fireking.ap.R
import org.jetbrains.anko.backgroundColor
import org.jetbrains.anko.backgroundResource
import org.jetbrains.anko.textColor

class VerifyCodeInputLayout(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
    RelativeLayout(context, attrs, defStyleAttr) {

    private lateinit var inputReal: EditText
    private lateinit var rvContentList: LinearLayout
    private var onCompleteListener: OnCompleteListener? = null

    private var verifyCodeLen = 0
    private var inputTextSize: Int = 0
    private var inputTextColor: Int = 0
    private var inputBoxSize: Int = 0
    private var verifyInputLayoutHeight = 0
    private var dividerDrawable: Drawable? = null
    private var itemSelector: Int = R.drawable.verify_code_text_selector

    private var inputTextView = ArrayList<TextView>(4)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) {
        LayoutInflater.from(context).inflate(R.layout.verify_code_input_layout, this, true)

        //设置默认值
        verifyCodeLen = 4
        inputTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16.0F, resources.displayMetrics).toInt()
        inputTextColor = Color.parseColor("#FF333333")
        inputBoxSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50F, resources.displayMetrics).toInt()
        dividerDrawable = context.resources.getDrawable(R.drawable.linearlayout_divider)

        //获取自定义属性值
        val a = context.obtainStyledAttributes(attrs, R.styleable.VerifyCodeInputLayout)
        if (a.hasValue(R.styleable.VerifyCodeInputLayout_textSize)) {
            inputTextSize = a.getDimensionPixelSize(R.styleable.VerifyCodeInputLayout_textSize, inputTextSize)
        }

        if (a.hasValue(R.styleable.VerifyCodeInputLayout_textColor)) {
            inputTextColor = a.getColor(R.styleable.VerifyCodeInputLayout_textColor, Color.parseColor("#FF333333"))
        }

        if (a.hasValue(R.styleable.VerifyCodeInputLayout_inputBoxSize)) {
            inputBoxSize = a.getDimensionPixelSize(
                R.styleable.VerifyCodeInputLayout_inputBoxSize,
                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 44F, resources.displayMetrics).toInt()
            )
        }

        if (a.hasValue(R.styleable.VerifyCodeInputLayout_dividerDrawable)) {
            dividerDrawable = a.getDrawable(R.styleable.VerifyCodeInputLayout_dividerDrawable)
        }

        if (a.hasValue(R.styleable.VerifyCodeInputLayout_itemSelector)) {
            itemSelector = a.getResourceId(R.styleable.VerifyCodeInputLayout_itemSelector, itemSelector)
        }

        if (a.hasValue(R.styleable.VerifyCodeInputLayout_maxLength)) {
            verifyCodeLen = a.getInt(R.styleable.VerifyCodeInputLayout_maxLength, 4)
        }
        a.recycle()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        verifyInputLayoutHeight = measuredHeight
    }

    fun setOnCompleteListener(onCompleteListener: OnCompleteListener) {
        this.onCompleteListener = onCompleteListener
    }

    override fun onFinishInflate() {
        super.onFinishInflate()

        initContainer()
        initListener()
    }

    private fun initListener() {
        inputReal.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(p0: Editable?) {
                setVerifyCodeInputValue(p0.toString())
                if (p0.toString().length == verifyCodeLen) {
                    onCompleteListener?.onComplete(p0.toString())
                }
            }

            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
            }

            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
            }
        })
    }

    private fun setVerifyCodeInputValue(inputText: String) {
        inputTextView.forEach {
            it.text = ""
            it.isSelected = false
        }
        inputTextView.forEachIndexed { index, textView ->
            if (inputText.length > index) {
                textView.isSelected = true
                textView.text = inputText[index].toString()
            }
        }
    }

    private fun initContainer() {

        inputReal = findViewById(R.id.inputReal)
        rvContentList = findViewById(R.id.rvContentList)
        inputReal.width = (dividerDrawable?.minimumWidth ?: 0 * (verifyCodeLen - 1)) + inputBoxSize * verifyCodeLen
        inputReal.height = inputBoxSize
        inputReal.setTextSize(TypedValue.COMPLEX_UNIT_PX, inputTextSize * 1.0F)

        inputReal.isCursorVisible = false
        inputReal.filters = arrayOf(InputFilter.LengthFilter(verifyCodeLen))

        inputTextView.clear()

        dividerDrawable?.let {
            it.setBounds(0, 0, it.minimumWidth, it.minimumHeight)
            rvContentList.dividerDrawable = it
        }

        for (i in 0 until verifyCodeLen) {
            val textView = TextView(context)
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, inputTextSize * 1.0F)
            textView.width = inputBoxSize
            textView.height = inputBoxSize
            textView.gravity = Gravity.CENTER
            textView.isFocusable = false
            textView.textColor = inputTextColor
            textView.backgroundResource = itemSelector
            inputTextView.add(textView)
        }

        inputTextView.forEach {
            rvContentList.addView(it)
        }
    }

    interface OnCompleteListener {
        fun onComplete(content: String)
    }
}
Copy the code

Custom attributes

<? The XML version = "1.0" encoding = "utf-8"? > <resources> <declare-styleable name="VerifyCodeInputLayout"> <attr name="textSize" format="dimension"/> <attr name="textColor" format="color"/> <attr name="inputBoxSize" format="dimension"/> <attr name="dividerDrawable" format="reference"/> <attr name="maxLength" format="integer"/> <attr name="itemSelector" format="reference"/> </declare-styleable> </resources>Copy the code