Android Kotlin editText监听器问题

huangapple go评论59阅读模式
英文:

Android Kotlin editText Listener Question

问题

I'm trying to program something where a user inputs a number into an editText field and it automatically has decimal places appended. I'm using editText.setText inside the listener, but find that the code crashes every time. Upon logging the value that I'm trying to edit, I found that the moment I change the editText field, editText.setText runs non-stop, which causes the program to crash. Below is my code:

val etTesting = findViewById<EditText>(R.id.etTesting)
etTesting.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.isNullOrEmpty() && s.toString() != ".") {
            val value = s.toString().toDouble()
            val formatted = String.format("%.2f", value)
            Log.i(TAG, "onTextChanged $formatted")
            etTesting.setText(formatted)
            etTesting.setSelection(formatted.length)
        }
    }
})

I tried to use setText outside of the listener and it doesn't trigger a crash. It's only inside the listener. I'm guessing it has to do with the listener writing an updated value, seeing that it's updated again, and then goes through this sequence infinitely?

How is one supposed to use the listener to do what I'm trying to do?

Thanks for the help.

英文:

I'm trying to program something where a user inputs a number into an editText field and it automatically has decimal places appended. I'm using editText.setText inside the listener, but find that the code crashes every time. Upon logging the value that I'm trying to edit, I found that the moment I change the editText field, editText.setText runs non-stop, which causes the program to crash. Below is my code:

val etTesting = findViewById&lt;EditText&gt;(R.id.etTesting)
        etTesting.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.isNullOrEmpty() &amp;&amp; s.toString() != &quot;.&quot;) {
                    val value = s.toString().toDouble()
                    val formatted = String.format(&quot;%.2f&quot;, value)
                    Log.i(TAG, &quot;onTextChanged $formatted&quot;)
                    etTesting.setText(formatted)
                    etTesting.setSelection(formatted.length)
                }
            }
        })

I tried to use setText outside of the listener and it doesn't trigger a crash. It's only inside the listener. I'm guessing it has to do with the listener writing an updated value, seeing that it's updated again, and then goes through this sequence infinitely?

How is one supposed to use the listener to do what I'm trying to do?

Thanks for the help.

答案1

得分: 0

This will crash, because as soon as you format and set text, onTextChanged is called with a new value and this is repeated infinitely, so the best option here would be to use filters and validate and return the text as you need, an example of how to use

etTesting.filters = arrayOf(object : InputFilter {
    override fun filter(
        input: CharSequence?,
        p1: Int,
        p2: Int,
        p3: Spanned?,
        p4: Int,
        p5: Int
    ): CharSequence =
        if (!input.isNullOrEmpty() && input.toString() != ".") {
            val value = input.toString().toDouble()
            String.format("%.2f", value)
        } else input ?: ""
})
英文:

This will crash, because as soon as you format and set text, onTextChanged is called with new value and this is repeated infintely, so best option here would be to use filters and validate and return the text as you need, an example of how to use

etTesting.filters = arrayOf(object : InputFilter {
    override fun filter(
        input: CharSequence?,
        p1: Int,
        p2: Int,
        p3: Spanned?,
        p4: Int,
        p5: Int
    ): CharSequence =
        if (!input.isNullOrEmpty() &amp;&amp; input.toString() != &quot;.&quot;) {
            val value = input.toString().toDouble()
            String.format(&quot;%.2f&quot;, value)
        } else input?:&quot;&quot;
})        

答案2

得分: 0

您的应用将崩溃,因为当您在EditText中键入内容时,addTextChangedListener将触发,如果您在监听器中还使用setText,它将一遍又一遍地触发,导致应用崩溃,因为它将无限重复,就像使用while循环一样。

使用InputFilter接口来实现自己的过滤CharSequence。像这样。

val inputFilter = object : InputFilter {
    override fun filter(
        input: CharSequence?, p1: Int, p2: Int, p3: Spanned?, p4: Int, p5: Int
    ): CharSequence {
        return if (!input.isNullOrEmpty() && input.isDigitsOnly() && input.toString() != ".") {
            val value = input.toString().toDouble()
            String.format("%.2f", value)
        } else {
            ""
        }
    }
}

etTesting.setFilters(arrayOf<InputFilter>(inputFilter))

希望对您有帮助。

英文:

Your app will crash, because when you type something in the EditText, the addTextChangedListener will trigger and if you also use setText in your listener and it will trigger again and again, this will lead your app to crash, because it will repeat forever, Like you use a while loop.

Use the InputFilter interface to implement your own filtered CharSequence. Like this.

    val inputFilter = object : InputFilter {
        override fun filter(
            input: CharSequence?, p1: Int, p2: Int, p3: Spanned?, p4: Int, p5: Int
        ): CharSequence {
            return if (!input.isNullOrEmpty() &amp;&amp; input.isDigitsOnly() &amp;&amp; input.toString() != &quot;.&quot;) {
                val value = input.toString().toDouble()
                String.format(&quot;%.2f&quot;, value)
            } else {
                &quot;&quot;
            }
        }
    }

    etTesting.setFilters(arrayOf&lt;InputFilter&gt;(inputFilter))

Hope this would be helpful for you

答案3

得分: 0

作为更一般的方法(而不是依赖于过滤器,因为它们不一定适用于你想要做的事情),你可以在你的TextWatcher中创建一种updating标志:

object : TextWatcher {
    var updating = false

    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 (!updating) {
            updating = true
            // 对文本内容进行更改
            updating = false
        }
    }
}

基本上,当onTextChanged触发时,它会检查是否正在进行内部更新,只有在没有内部更新时才会继续。首先它设置了updating标志,然后更新文本,这立即触发了TextWatcher的回调函数。

这些调用是递归的,因此你的初始onTextChanged函数不会完成,直到触发的onTextChanged返回。如果这引发了另一个onTextChanged,你最终会得到无限嵌套的函数调用,直到发生堆栈溢出。

通过使用这个标志,你可以短路这种行为。因为第一次调用设置了updating标志,文本更改会触发第二次调用,第二次调用会看到updatingtrue,并且在不进行任何更改的情况下返回。然后你的第一个onTextChanged调用将完成。最后它会将updating设置为false,因为更新过程已经完成,所以它准备好处理下一次更改。

英文:

As a more general approach (instead of relying on filters, which aren't always enough for what you want to do) you could create some kind of updating flag in your TextWatcher:

object : TextWatcher {
    var updating = false

    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) {
        // skip reacting to the text change if it&#39;s caused by an internal update
        if (!updating) {
            updating = true
            // make changes to the text contents
            updating = false
        }
    }
}

Basically when onTextChanged fires, it checks to see if there's an internal update happening, and only proceeds if there isn't. First it sets that updating flag, and then updates the text, which immediately triggers the TextWatcher callbacks again.

Those calls are recursive, so your initial onTextChanged function won't complete until the triggered onTextChanged returns. And if that fires another onTextChanged, you end up with infinite nested function calls until you get a (heyo) stack overflow.

By using this flag, you short-circuit that behaviour. Because the first call sets that updating flag, the text change will trigger a second call, which sees that updating is true, and returns without making any changes. Then your first onTextChanged call will complete. And the last thing it does is set updating to false since the update process has finished, so it's ready to handle the next change.

huangapple
  • 本文由 发表于 2023年5月15日 06:32:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76249952.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定