自定义了一个验证码输入框,先看效果
本来这种需求花点时间可以自定义一个EditText
,但是时间有限,我用了种取巧的方式,在FrameLayout
中嵌套EditText
和LinearLayout
,然后隐藏EditText
的文字并在LinearLayout
中add
数个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:
<declare-styleable name="VerificationCodeView">
<attr name="cursorColor" format="color|reference" />
<attr name="numberColor" format="color|reference" />
<attr name="numberFocusedBackground" format="reference" />
<attr name="numberBackground" format="reference" />
<attr name="numberSpacing" format="dimension" />
<attr name="numberSize" format="dimension" />
<attr name="maxLength" format="integer" />
</declare-styleable>
使用:
<VerificationCodeView
android:id="@+id/v_verification_code"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginStart="33dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="33dp"
app:cursorColor="@color/colorAccent"
app:layout_constraintTop_toBottomOf="@+id/tv_second_title"
app:numberBackground="@drawable/verify_line_bg_normal"
app:numberColor="@color/white"
app:maxLength="4"
app:numberFocusedBackground="@drawable/verify_line_bg_focus"
app:numberSize="24sp"
app:numberSpacing="30dp" />
输入完成拿到输入的内容
v_verification_code.onInputComplete = {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
numberBackground
和numberFocusedBackground
是两个drawable
,我就不贴代码了,根据自身业务自定义一下就好了,写完感觉值得优化的地方还挺多的,有时间再更新吧