I'm trying to make a number look like this
932-874838/9
I did this with my EditText to append the - and / after some spaces
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(text: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
onValueChange(s.toString())
}
})
}
private fun onValueChange(value: String) {
mNumberTxtView.text = value
if (value.length == 3) {
mNumberTxtView.append("-")
}
if (value.length == 10) {
mNumberTxtView.append("/")
}
}
When I'm typing like
932
it automatically appends the - , and that works, but after it appends the - and if I type another number it replaces the - with that number instead of continuing, so it becomes 932- at first but when trying to put another number,
9328
it gets replaced like that removing the appended -
I think the problem is inside the onValueChange() method
onValueChange should be like this:
var test: StringBuilder = StringBuilder()
var lastValue: String = ""
fun onValueChange(value: String) {
if(lastValue.length > value.length) {
test.deleteCharAt(test.lastIndex)
if(test.length == 3 || test.length == 10) {
test.deleteCharAt(test.lastIndex)
}
} else {
test.append(value.last())
if (test.length == 3) {
test.append("-")
} else if (test.length == 10) {
test.append("/")
}
}
lastValue = value
textView.text = test
}
Try this, instead.
private fun onValueChange(value: String) {
if (value.length == 3) {
mNumberTxtView.text = "${value}_"
} else if (value.length == 10) {
mNumberTxtView.text = "${value}/"
}
}
Let me know if this works.
(The curly brackets around "value" in the strings may not be necessary. I'm still getting used to Kotlin's way of handling string concatenation.)
Edited to remove redundant and potentially loop-causing part.
You should not change text in beforeTextChanged and afterTextChanged to prevent re-call of those methods by TextWatcher. Make changes in afterTextChanged.
But be careful not to get yourself into an infinite loop, because any changes you make will cause this method to be called again recursively.
So set invoke of onValueChanged into afterTextChanged method
with removal of mNumberTxtView.text = value
Related
I've made a custom in-app keyboard (by following this tutorial). But I'd like it to delete text from a different edit text when the delete key is pressed. Is this possible to achieve?
This is my onClick code in my keyboard activity file:
override fun onClick(v: View) {
if (v.id == R.id.button_delete) {
// Delete key is pressed.
} else {
// Regular key is pressed.
val value = keyValues[v.id]
inputConnection?.commitText(value, 1)
}
}
Let me know if there's anything else you'd like to know :)
I eventually solved this by putting a character e.g: 'd' into the edittext like so:
override fun onClick(v: View) {
if (v.id == R.id.button_delete) {
inputConnection?.commitText("d", 1)
} else {
val value = keyValues[v.id]
inputConnection?.commitText(value, 1)
}
}
Then in the activity that controlls the edittext's layout, e.g: for me it was PasscodeActivity, put a TextWatcher that detects when the text inside of the edittext has changed.
private fun setEditTextListener() {
val inputEl = findViewById<EditText>(resources.getIdentifier("inputEl", "id", packageName))
inputEl.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(textBeforeChangedStr: CharSequence?, startNum: Int, countNum: Int, afterNum: Int) {
...
}
override fun onTextChanged(textAfterChangedStr: CharSequence?, startNum: Int, beforeNum: Int, countNum: Int) {
...
}
override fun afterTextChanged(editableEl: Editable?) {
if (inputEl.text.toString() == "d") { // The 'd' character is put into the edittext when the delete key is pressed.
clearOtherEditText()
}
...
}
})
}
Then in the function clearOtherEditText() I put in some code to get rid of the text in a different edittext like so:
val otherInputEl = findViewById<EditText>(resources.getIdentifier("otherInput", "id", packageName))
otherInputEl.text.clear()
Hope this helps someone else :)
try below solution
override fun onClick(v: View) {
if (v.id == R.id.button_delete) {
// Delete key is pressed.
**inputConnection.deleteSurroundingText(1, 0)**
} else {
// Regular key is pressed.
val value = keyValues[v.id]
inputConnection?.commitText(value, 1)
}
}
i'm referring this answer https://stackoverflow.com/a/42802060/12428090
basically my goal is to handle word count of 100 words
the abve solution works fine for typed 100 words and even handles copy paste very well
but it doesnt handles the new line case
suppose in entered word or copy pasted word contains new line then the word count is returning incorrect
following is the code please help out
override fun onTextChanged(s: CharSequence, start: Int, before: Int,
count: Int) {
val wordsLength: Int = countWords(s.toString()) // words.length;
limitmoderator.text = "$wordsLength/$MAX_WORDS"
val yourText: String =
moderator_intro.getText().toString().replace(160.toChar().toString(), " ")
if (yourText.split("\\s+".toRegex()).size > MAX_WORDS) {
var space = 0
var length = 0
for (i in 0 until yourText.length) {
if (yourText[i] == ' ') {
space++
if (space >= MAX_WORDS) {
length = i
break
}
}
}
if (length > 1) {
moderator_intro.getText()
.delete(length, yourText.length) // deleting last exceeded words
setCharLimit(moderator_intro, length - 1) //limit edit text
}
} else {
removeFilter(moderator_intro)
}}
private fun countWords(s: String): Int {
val trim = s.trim()
return if (trim.isEmpty()) 0 else trim.split("\\s+".toRegex()).size
// separate string around spaces
}
private var filter: InputFilter? = null
private fun setCharLimit(et: EditText, max: Int) {
filter = LengthFilter(max)
et.filters = arrayOf<InputFilter>(filter as LengthFilter)
}
private fun removeFilter(et: EditText) {
if (filter != null) {
et.filters = arrayOfNulls(0)
filter = null
}
}
so i have tried rplacing the "\n" in the text but it doesnt seems to be handling the case properly
any help will be appreciated
thanks in advance
Here's a different strategy than the one from the question you linked. Notice I'm using afterTextChanged and not onTextChanged!
I'm manually counting words to get the character index of the first whitespace after the last allowable word. That way I don't have to trim and then use Regex, and then try to figure out the index offset of that Regex. Then instead of applying a temporary filter, I directly cut the end of the Editable off.
editText.setSelection is to keep the cursor from jumping to the beginning.
override fun afterTextChanged(s: Editable) {
var previousWasWhitespace = true
var i = 0
var wordCount = 0
for (c in s) {
val whitespace = c.isWhitespace()
if (whitespace && !previousWasWhitespace && ++wordCount == MAX_WORDS) {
break
}
previousWasWhitespace = whitespace
i++
}
if (i < s.length) {
s.delete(i, s.length)
editText.setSelection(i)
}
}
You could write a regular expression to match the text that you want to keep and remove the rest. In this case you want match (non-whitespace+)(whitespace*) maximum 100 times.
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
val input = moderator_intro.getText().toString()
val maxWords = 100
val whiteSpace = "\\p{javaWhitespace}\u00A0\u2007\u202F" //All white space characters
val keepRegex = Regex("[$whiteSpace]*([^$whiteSpace]+[$whiteSpace]*){1,${maxWords.toString()}}")
val allowedText = keepRegex.find(input)?.value ?: ""
val wordAmount = allowedText.split(Regex("[$whiteSpace]+")).filter { it.isNotBlank() }.size
val trailingWhiteSpace = Regex("[$whiteSpace]+$")
if(wordAmount == maxWords && allowedText.contains(trailingWhiteSpace)) {
val newText = allowedText.replace(trailingWhiteSpace, "")
moderator_intro.getText().delete(allowedText.length, input.length)
setCharLimit(moderator_intro, newText.length)
} else {
removeFilter(moderator_intro)
}
}
I'm creating a form on an android application using kotlin. Each field of the form is associated with a livedata, to validate or update the data instantly. To obtain this result, I used a textwatcher, which updates the associated livedata at each change of the text. The problem is that with each update, it places the cursor at the start of the field making it impossible to write continuously.
I paste here the intresting part of the code:
Activity
viewModel.name.observe(lifecycleOwner, Observer { nameValue->
binding.nameEditText.setText(
nameValue?.toString()
)
binding.nameTextInputLayout.error =
viewModel.nameError.value
})
viewModel.price.observe(lifecycleOwner, Observer { priceValue->
binding.priceEditText.setText(
price?.toString()
)
binding.priceTextInputLayout.error =
viewModel.priceError.value
})
binding.nameEditText.addTextChangedListener(
TextFieldValidation(
binding.nameEditText
)
)
binding.priceEditText.addTextChangedListener(
TextFieldValidation(
binding.priceEditText
)
)
inner class TextFieldValidation(private val view: View) : 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) {
when (view.id) {
R.id.nameEditText-> {
viewModel.onNameChanged(s.toString())
viewModel.isNameValid()
}
R.id.priceEditText-> {
viewModel.onPriceChanged(s.toString())
viewModel.isPriceValid()
}
}
}
ViewModel
var name = MutableLiveData<String>()
var price = MutableLiveData<Double>()
var nameError = MutableLiveData<String>()
var priceError = MutableLiveData<String>()
fun onNameChanged(newValue: String?) {
if ((name.value != newValue)) {
name.value = newValue
}
}
fun onPriceChanged(newValue: Double?) {
if ((price.value != newValue)) {
price.value = newValue
}
}
fun isNameValid() : Boolean {
return if ((name.value == null) || (name.value == "")) {
nameError.value = "Error"
false
} else {
nameError.value = ""
true
}
}
fun isPriceValid() : Boolean {
return if ((price.value == null) || (price.value == "")) {
priceError.value = "Error"
false
} else {
priceError.value = ""
true
}
}
XML
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/nameTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/nameEditText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:backgroundTint="#color/white"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/priceTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="#+id/priceEditText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:backgroundTint="#color/white"
android:inputType="numberDecimal" />
</com.google.android.material.textfield.TextInputLayout>
I tried using 'mEdittext.setSelection (mEdittext.length ());' but it doesn't work well, because if I make changes in the middle of the string, it brings the cursor to the end. And even in double fields, it doesn't behave correctly.
I need to have the cursor always at the exact position? Either in case of adding, deleting or writing in the middle of the string. And both in the case of a string and in the case of a double.
Could someone help me?
Thank you for your patience and help!
This is happening, because in your ViewModel observer you are setting nameValue and priceValue back to the EditText where they just came from. You could add a check and not set unchanged values back to the EditText:
if ((nameValue?.toString() == binding.nameEditText.getText().toString()) == false) {
binding.nameEditText.setText(nameValue?.toString())
}
I also had the same problem that the cursor moved to the beginning, searching I found a first solution that served me to a certain extent allowing me to write a message in a row, which would be the following
viewModel.name.observe(lifecycleOwner, Observer { nameValue->
binding.nameEditText.setText(
nameValue?.toString()
)
binding.nameEditText.setSelection(
nameValue?.toString().length
)
})
I thought I had already solved it, but another problem arose and that is that if I wanted to edit any part of the center after typing a character the cursor would move to the end, until I found the correct solution https://stackoverflow.com/a/65794745/15798571
viewModel.name.observe(lifecycleOwner, Observer { nameValue->
val selection = binding.nameEditText.selectionEnd
val length = nameValue.length
binding.nameEditText.setText(
nameValue?.toString()
)
binding.nameEditText.setSelection(
Math.min(selection, length)
)
})
I hope it serves you as it served me
I want to compare two String objects coming out of two different lists and no matter if i use
equals(), contentEquals() or ==, it is always false.
Has it something to do with how the strings of the first list are put into it?
edit: it's getting weirder look at the log outcome in this picture:
DictWord.dictWords.forEach {
Log.i("testen", "it is: $it and equals 'black'? - ${it.equals("black")}")
Log.i("testen", "it is: $it and equals $newWord - ${it.equals(newWord)}")
Log.i("testen", "it is: $it and equals $newWord - ${it.contentEquals("black")}")
Log.i("testen", "it is: $it and == $newWord - ${it == newWord}")
Log.i("black", "it is: 'black' and equals $newWord - ${"black" == newWord}")
... subStrainsAdapter.addHeaderAndSubmitList(null)
var textList = mutableListOf<String>()
var movingText = ""
thoughtContent.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)
{movingText += s.last()}
})
//SUBSTRAIN INPUT - goes to onSubStrainEditEnd above when ENTER hit
thoughtContent.setOnKeyListener(object : View.OnKeyListener {
#RequiresApi(Build.VERSION_CODES.Q)
override fun onKey(v: View?, key: Int, event: KeyEvent): Boolean {
return if (event.action == KeyEvent.ACTION_DOWN && key == KeyEvent.KEYCODE_SPACE) {
textList.add(movingText)
movingText = ""
false } else false
}})
Output log for the above code:
edit
if(b == false) {
thoughtsViewModel.editThought(thoughtContent.text.toString(), thoughtItem.id)
val testList = thoughtContent.text.toString().split(" ")
textList.forEach {
(Log.i("testen", "it is $it"))
if(DictWord.dictWords.keys.contains(it)) {Log.i("testen", "TRIGGGEERRRED and its $it")}
}
testList.forEach {
(Log.i("testen", "it is $it"))
if(DictWord.dictWords.keys.contains(it)) {Log.i("testen", "test list TRIGGGEERRRED and its $it")}
}
newWord is not trimmed it seems. From your log, it has an extra space before it.
These two lines in the log correspond to this code respectively:
Log.i("testen", "it is: $it and equals 'black'? - ${it.equals("black")}")
Log.i("testen", "it is: $it and equals $newWord - ${it.equals(newWord)}")
You can see that you didn't add two spaces in the second line but still your black has an extra space before
You should either correct your list or do newWord.trim() which will remove all leading and trailing whitespaces
Otherwise you should always use String.equals(otherString: String) or s1 == s2 (they are the same in kotlin)
Your example is badly formatted and I believe missing some code, but as #snachmsm pointed out, contentEquals is a method on arrays in Kotlin. You should use equals() for Strings instead (though == is the preferred way in Kotlin)
So I'm using a text watcher to show the user suggestions for tags.
Currently I am using a dummy list of tags. When the user starts typing, the code should create a another list of tags that is just the objects of the first list filtered based on whether they contain the character sequence the user is currently typing.
It works great for the first character, but as the user keeps typing the list doesn't change anymore.
This is my code:
val tags = listOf(
"John Smith",
"Kate Eckhart",
"Emily Sun",
"Frodo Baggins",
"Yanay Zabary",
"Sze Lok Ho",
"Jesse Albright",
"Shayna something",
"Makena Lawrence"
)
questionTagsInput.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val userInput = s.toString()
if (userInput == "") {
tagSuggestionRecycler.visibility = View.GONE
tagsFiltredAdapter.clear()
} else {
val relevantTags: List<String> = tags.filter { it.contains(userInput) }
for (t in relevantTags) {
tagSuggestionRecycler.visibility = View.VISIBLE
tagsFiltredAdapter.add(SingleTagSuggestion(t))
}
}
}
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
})
You Need to clear the tagsFiltredAdapter in each call of onTextChanged.
Currently, when typing the first character, your filtered list contains the right results, and when typing more characters, the list's size should decrease to match the new characters. Instead, you just adding items to the list.