0

我正在尝试创建一个数字输入字段,当输入有效数字时更新支持字段。当支持字段更新时,UI 应该反映这一点,因为它也可以被其他东西更新。

我有一个实现,其中我有一个正在编辑的本地字符串,它会显示出来,每次值更改时,都会检查该字符串是否可以解析出一个整数,在这种情况下,支持字段会更新。问题是光标重置到字段的开头 - 因此,如果您输入多位数字,则数字会乱序。

似乎没有任何东西可以用来知道用户何时离开控件并完成编辑。尽管我使用的是TextFieldValue,但我无法更新该对象中的文本,也无法保留编辑状态,而不是重新创建整个对象。

这不可能是一个新问题,但在线讨论很少。我是在做一些愚蠢的事情并且使事情变得过于复杂吗?

代码:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.numericinputtest.ui.theme.NumericInputTestTheme
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue


class State : ViewModel()
{
    private val _numCycles = MutableLiveData<Int>(0)
    val numCycles: LiveData<Int> = _numCycles

    fun onNewNumCycles(cycles: Int) {
        _numCycles.value = cycles
    }
}

class StringToInt {
    companion object {
        fun tryParse(s: String): Int? {
            try {
                return s.toInt()
            } catch (e: java.lang.NumberFormatException) {
                return null
            }
        }
    }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val state = State()

        setContent {
            NumericInputTestTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                    TestNumeric(state = state)
                }
            }
        }
    }
}

@Composable
fun TestNumeric(state: State) {
    val numCycles: Int by state.numCycles.observeAsState(0)

    //To be able to edit the text normally, we need a local string and the backing field
    //only gets updated when there's a valid number
    val numCyclesString = remember { mutableStateOf(TextFieldValue(numCycles.toString())) }

    //Since we're now displaying a local string, it doesn't get changed when the backing state
    //changes. So we need to catch this occurrence and update manually.
    state.numCycles.observeAsState()
        .run { numCyclesString.value = TextFieldValue(numCycles.toString()) }

    Surface()
    {
        TextField(
            value = numCyclesString.value,
            onValueChange = {
                numCyclesString.value = it
                val i = StringToInt.tryParse(it.text)
                if (i != null) {
                    state.onNewNumCycles(i)
                }
            },
            singleLine = true,
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
        )
    }


}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    val state = State()
    TestNumeric(state)
}
4

1 回答 1

0

TextFieldValue包含有关光标位置的信息。您正在使用以下行清除此信息:

.run { numCyclesString.value = TextFieldValue(numCycles.toString()) }

copy您可以使用仅更新文本的方法修复它:

numCyclesString.value = numCyclesString.value.copy(text = numCycles.toString())

但大多数情况下,当您不需要访问应用程序中的选择信息时,您可以使用另一个Text接受和更新String值的重载:它会在内部执行此copy逻辑,您的代码会更简洁。

还有一些小技巧:

  1. LiveData当您有一些使用它的依赖项时才在 Compose 中使用。在其他情况下,直接使用可变状态会更干净:

    var numCycles by mutableStateOf<Int?>(null)
        private set
    
    fun onNewNumCycles(cycles: Int?) {
        numCycles = cycles
    }
    
  2. 您可以使用toIntOrNull代替您的StringToInt.tryParse助手

  3. 您不需要在活动中创建视图模型并将其传递给您的视图。您可以用任何可组合的方式检索它viewModel:它将在第一次调用时创建一个对象并在文本时间重用。

最终的可组合代码:

val state = viewModel<State>()

TextField(
    value = state.numCycles?.toString() ?: "",
    onValueChange = {
        if (it.isEmpty()) {
            state.onNewNumCycles(null)
        } else {
            val i = it.toIntOrNull()
            if (i != null) {
                state.onNewNumCycles(i)
            }
        }
    },
    singleLine = true,
    keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
于 2021-11-30T12:38:23.337 回答