问题描述
我遇到了以下问题:我需要针对应将电话输入 EditText
的情况实施解决方案。这个手机应该有不可拆卸的部分,最后四个数字应该在开头用下划线填充,然后当用户输入它们时下划线应该改为数字,例如:
+12345____ -> typing 6 -> +123456___
我实现了不可拆卸部分。这是我的做法:
binding.etPhoneNumber.filters = arrayOf(InputFilter.LengthFilter(args.phoneNumber?.length ?: 0))
binding.etPhoneNumber.doAfterTextChanged {
val symbolsLeft = it?.toString()?.length ?: 0
if (symbolsLeft < phoneNumberUi.length) {
binding.etPhoneNumber.setText(phoneNumberUi)
binding.etPhoneNumber.setSelection(symbolsLeft + 1)
}
}
但是现在我不明白,如何处理带有下划线的逻辑。我尝试在 doAfterTextChanged
中附加下划线,例如 ((args.phoneNumber?.length ?: 0) > (it?.toString()?.length ?: 0))
附加 n 个下划线,其中 n 是长度之间的差,但在这种情况下,我无法添加新符号,因为 EditText
已填充并且下划线不会被删除。那么,我该如何解决这个问题呢?在此先感谢您的帮助!
解决方法
您可以删除 LengthFilter
并检查 doAfterTextChanged
中的长度:
val phoneNumberUi = "+12345"
val length = 10
binding.etPhoneNumber.doAfterTextChanged {
when {
it == null -> {
}
// missing / incomplete prefix
it.length < phoneNumberUi.length -> {
it.replace(0,it.length,phoneNumberUi)
}
// prefix was edited
!it.startsWith(phoneNumberUi) -> {
it.replace(0,phoneNumberUi.length,phoneNumberUi)
}
// too short
it.length < length -> {
it.append("_".repeat(length - it.length))
}
// too long
it.length > length -> {
it.replace(length,"")
}
// set the cursor at the first _
it.indexOf("_") >= 0 -> {
binding.etPhoneNumber.setSelection(it.indexOf("_"))
}
}
}
注意:这使用了 when
,因为每次更改都会立即触发对 doAfterTextChanged
的递归调用
这种方法有以下条件分支
- 用户添加输入的地方(不可移动部分或可变部分)
- 输入的字符(数字或退格)
并通过在 onTextChanged()
及其索引(第二个参数)中获取输入的字符(数字/退格符)并根据它们的值设置新的 EditText 值来工作。
EditText
的值也由 currentText
变量跟踪。这样我们一次只能替换用户输入的一个字符,避免操作整个文本的负担。
您可以通过代码在下面的注释中找到其余的解释:
attachTextWatcher(findViewById(R.id.edittext))
fun attachTextWatcher(editText: EditText) {
// set the cursor to the first underscore
editText.setSelection(editText.text.indexOf("_"))
var currentText = editText.text.toString() // which is "+12345____"
val watcher: TextWatcher = object : TextWatcher {
override fun onTextChanged(
s: CharSequence,newCharIndex: Int,// "newCharIndex" is the index of the new entered char
before: Int,count: Int
) {
// New entered char by the user that triggers the TextWatcher callbacks
val newChar = s.subSequence(newCharIndex,newCharIndex + count).toString().trim()
/* Stop the listener in order to programmatically
change the EditText Without triggering the TextWatcher*/
editText.removeTextChangedListener(this)
// Setting the new text of the EditText upon examining the user input
currentText =
if (newChar.isEmpty()) { // User entered backspace to delete a char
if (newCharIndex in 0..5) { // The backspace is pressed in the non-removable part
"+12345" + currentText.substring(6)
} else { // The backspace is pressed in the changeable part
val sb = StringBuilder(currentText)
// replace the the number at which backspace pressed with underscore
sb.setCharAt(newCharIndex,'_')
sb.toString()
}
} else { // User entered a number
if (newCharIndex in 0..5) { // The number is entered in the non-removable part
// replace the first underscore with the entered number
val sb = StringBuilder(currentText)
sb.setCharAt(sb.indexOf("_"),newChar[0])
sb.toString()
} else { // The number is entered in the changeable part
if (newCharIndex < 10) { // Avoid ArrayOutOfBoundsException as the number length should not exceed 10
val sb = StringBuilder(currentText)
// replace the the number at which the number is entered with the new number
sb.setCharAt(newCharIndex,newChar[0])
sb.toString()
} else currentText
}
}
// Set the adjusted text to the EditText
editText.setText(currentText)
// Set the current cursor place
if (editText.text.contains("_"))
editText.setSelection(editText.text.indexOf("_"))
else
editText.setSelection(editText.text.length)
// Re-add the listener,so that the EditText can intercept the number by the user
editText.addTextChangedListener(this)
}
override fun beforeTextChanged(s: CharSequence,start: Int,count: Int,after: Int) {
}
override fun afterTextChanged(s: Editable) {
}
}
editText.addTextChangedListener(watcher)
}
这是我正在测试的布局:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLength="11"
android:text="+12345____"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
注意:确保在配置更改时保留 currentText
的值。
预览
,我认为 PhúcNguyễn 有一个好主意,将 TextView 与 EditText 结合以生成您正在寻找的内容。您可以将它们作为单独的字段放置在布局中或将它们放置在复合视图中。无论哪种方式,效果都是一样的,你可以达到你想要的。
您已经知道如何处理字段开头的静态文本。我在下面展示的是如何处理下划线,以便输入的字符看起来覆盖下划线。
对于演示,我在自定义 EditText 旁边放置了一个带有静态文本的 TextView。真正令人感兴趣的是自定义 EditText。在自定义视图中,onDraw() 函数被覆盖以将下划线作为背景的一部分写入。尽管这些下划线会像字段中的任何其他字符一样出现,但不能以任何方式选择、删除、跳过或操纵它们,除非在用户键入时,下划线会被逐一覆盖。自定义视图的结束填充被操纵为下划线和文本提供空间。
这是自定义视图:
EditTextFillInBlanks.kt
class EditTextFillInBlanks @JvmOverloads constructor(
context: Context,attrs: AttributeSet? = null,defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatEditText(context,attrs,defStyleAttr) {
// Right padding before we manipulate it
private var mBaseRightPadding = 0
// Width of text that has been entered
private var mTextWidth = 0f
// Mad length of data that can be entered in characters
private var mMaxLength = 0
// The blanks (underscores) that we will show
private lateinit var mBlanks: String
// MeasureSpec for measuring width of entered characters.
private val mUnspecifiedWidthHeight = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED)
init {
mBaseRightPadding = paddingRight
doOnTextChanged { text,_,_ ->
measure(mUnspecifiedWidthHeight,mUnspecifiedWidthHeight)
mTextWidth = measuredWidth.toFloat() - paddingStart - paddingEnd
updatePaddingForBlanks(text)
}
setText("",BufferType.EDITABLE)
}
/*
Make sure that the end padding is sufficient to hold the blanks that we are showing.
The blanks (underscores) are written into the expanded padding.
*/
private fun updatePaddingForBlanks(text: CharSequence?) {
if (mMaxLength <= 0) {
mMaxLength = determineMaxLen()
check(mMaxLength > 0) { "Maximum length must be > 0" }
}
text?.apply {
val blanksCount = max(0,mMaxLength - length)
mBlanks = "_".repeat(blanksCount).apply {
updatePadding(right = mBaseRightPadding + paint.measureText(this).toInt())
}
}
}
/*
Draw the underscores on the canvas. They will appear as characters in the field but
cannot be manipulated by the user.
*/
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (mBlanks.isNotEmpty()) {
canvas?.withSave {
drawText(mBlanks,paddingStart + mTextWidth,baseline.toFloat(),paint)
}
}
}
fun setMaxLen(maxLen: Int) {
mMaxLength = maxLen
}
private fun determineMaxLen(): Int {
// Before Lollipop,we can't get max for InputFilter.LengthFilter
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return 0
return filters.firstOrNull { it is InputFilter.LengthFilter }
?.let {
it as InputFilter.LengthFilter
it.max
} ?: 0
}
}
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_blue_light"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:text="+12345"
android:textColor="@android:color/black"
android:textSize="36sp"
app:layout_constraintBaseline_toBaselineOf="@id/editableSuffix"
app:layout_constraintEnd_toStartOf="@+id/editableSuffix"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="@+id/guideline2" />
<com.example.edittextwithblanks.EditTextFillInBlanks
android:id="@+id/editableSuffix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/edittext_background"
android:inputType="number"
android:maxLength="@integer/blankFillLen"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:textColor="@android:color/black"
android:textSize="36sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/textView"
app:layout_constraintTop_toTopOf="parent"
tools:text="____">
<requestFocus />
</com.example.edittextwithblanks.EditTextFillInBlanks>
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="92dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val mStaticStart = "+12345"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (BuildConfig.VERSION_CODE < Build.VERSION_CODES.P) {
val maxLen = resources.getInteger(R.integer.blankFillLen)
findViewById<EditTextFillInBlanks>(R.id.editableSuffix).setMaxLen(maxLen)
}
}
}
很可能您可以将静态文本处理合并到自定义视图中以获得完整的解决方案。