效果图:
cell_edittext.gif支持功能:
- 键盘的自定义
- 输入焦点选中框
- 输入类型password,number,text
- 长按粘贴
下面主要讲一下自定义输入框的实现思路:
本来产品的要求是实现一个验证码输入框,主流的验证码都是纯数字组成,所以刚开始想到用自定义数字键盘和输入框来实现,也就有了这篇文章https://www.jianshu.com/p/6c51be7a8371。
但是,产品之后又提出另外一个需求,要求能输入密码。这个不仅要支持数字,还要支持各种文本,符号。所以还是老老实实用系统自带的键盘吧。想到的解决办法就是在输入框上面盖上一个透明的EditText来与键盘交互。通过监听键盘的输入达到输入框、EditText的联动。
实现步骤:
- 自定义属性:
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>
- 实现逻辑
- 将屏幕去掉固定间隔,分成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)
}
}
网友评论