问题
I've looked into using a TextWatcher
and an InputFilter
, but I'm not too sure how to approach this problem.
The idea is to have an EditText
that inserts text from right to left. As the use input changes, I would like the following to occur.
- User enters "1" -> Text formats as 00:01
- User enters "2" -> Text formats as 00:12
- User enters "8" -> Text formats as 01:28
How could I approach this? Inputfilter
seems to be for excluding text and using setText
inside the TextWatcher
appears to run in an endless loop.
回答1:
Try this:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<EditText
android:id="@+id/edittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25dp"
android:padding="20dp"
android:inputType="number"
android:maxLength="4"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textview"
android:textSize="25dp"
android:padding="20dp"/>
</LinearLayout>
MainActivity.kt
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
edittext.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int,
count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int,
before: Int, count: Int) {
if (s.length<5){
var a=""
var i=4-s.length
var j=0
while (j<i){
a+="0"
j++
}
var b=a+s
b = b.substring(0, 2) + ":" + b.substring(2, b.length)
textview.setText(b)}
}
})
}
}
回答2:
So I came up with a pretty decent solution yesterday.
I used two edit texts and a text view. I Aligned them within a constraint layout
<TextView
android:id="@+id/colon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text=":"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/hoursInput"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:inputType="number"
android:maxLength="3"
android:text="00"
app:layout_constraintBottom_toBottomOf="@id/colon"
app:layout_constraintEnd_toStartOf="@id/colon"
app:layout_constraintTop_toTopOf="@id/colon"
app:layout_constraintVertical_bias="0.0" />
<EditText
android:id="@+id/minutesInput"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:inputType="number"
android:maxLength="3"
android:text="00"
app:layout_constraintBottom_toBottomOf="@id/colon"
app:layout_constraintStart_toEndOf="@id/colon"
app:layout_constraintTop_toTopOf="@id/colon" />
Since I only wanted to use numbers so I had to use a text view to represent the colon. modified the Text watcher for each input. I leveraged the string size in determining when to update the text entirely, and used onTextChanged
to force the string to always be at minimum size 2 - Both the hours and minutes look exactly the same, with the exception of the enum/cursor position. The fields themselves would allow for a third digit to be enterd at the cursor position. So a fiel with "12" would turn into "123" and then trigger update time, which figures out if we need to set text else where, or reject the number, or whatever custom logic is required.
addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (s != null && s.toString().length > 2)
//I used an ENUM to tell my update time function which view I was in. More to come. Cusor position is set in the keylistener
updateTime(s.toString, hoursInput.text, cursorPosition, Field.MINUTES)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (s != null && s.length < 2)
StringBuilder(s).apply { this.insert(0, "0") }.toString().also {
minutesInput.setText(it)
}
}
})
I also added an onKeyListener to watch the delete key specifically to implement custom logic. But before it would return the result of the key pressed, i'd track the cursor position on keydown for every event. Both minutes and Hours inputs look the same/similar.
setOnKeyListener { _, keyCode, keyEvent ->
if (keyEvent.action == KeyEvent.ACTION_DOWN) cursorPosition = selectionStart
(keyCode == KeyEvent.KEYCODE_DEL && keyEvent.action == KeyEvent.ACTION_DOWN && selectionStart == selectionEnd && selectionStart != 0).also { performDelete ->
if (performDelete) Logic.deleteTime(minutesInputString, minutesInput.text, cursorPosition, Field.Minutes)
}
}
Finally, I have my Logic class which I actually performed the string manipulation.
// Inside update/delete I convert the strings into a string that is like 00129 and use the cursorPosition/Field enum to determine where to add text.
Logic.updateTime(minutesInputString, hoursInputString, cursorPosition, Field) : String[]
Logic.deleteTime(minutesInputString, hoursInputString, cursorPosition, Field): String[]
I then set the time immediately with the inputs.
updateTime(mins, hours, position, Field.MINUTES).also{
// ("00", "129", 2, Minutes) returns as ["01", "29"]
minutesInput.setText(it[0])
minutesInput.setText(it[1])
}
Complicated problem that is still being worked on, but this is how I've solved it. Implementing this solution took me 4 hours where I had spun my wheels for nearly three days!
来源:https://stackoverflow.com/questions/60839902/dynamic-leading-text-for-androids-edittext