Jetpack Compose 数字输入
Jetpack Compose Numeric Input
我正在尝试制作一个数字输入字段,在输入有效数字时更新支持字段。当支持字段更新时,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)
}
TextFieldValue
包含有关光标位置的信息。您正在使用以下行清除此信息:
.run { numCyclesString.value = TextFieldValue(numCycles.toString()) }
您可以使用 copy
方法修复它,仅更新文本:
numCyclesString.value = numCyclesString.value.copy(text = numCycles.toString())
但大多数情况下,当您不需要访问应用程序中的选择信息时,您可以使用 Text
的另一个重载来接受和更新 String
值:它会做这个 copy
里面的逻辑和你的代码会更清晰。
还有一些小提示:
如果您有一些正在使用它的依赖项,则仅在 Compose 中使用 LiveData
。在其他情况下,直接使用可变状态要干净得多:
var numCycles by mutableStateOf<Int?>(null)
private set
fun onNewNumCycles(cycles: Int?) {
numCycles = cycles
}
您可以使用 toIntOrNull
代替您的 StringToInt.tryParse
助手
您不需要在 activity 中创建视图模型并将其传递给您的视图。您可以使用 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)
)
我正在尝试制作一个数字输入字段,在输入有效数字时更新支持字段。当支持字段更新时,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)
}
TextFieldValue
包含有关光标位置的信息。您正在使用以下行清除此信息:
.run { numCyclesString.value = TextFieldValue(numCycles.toString()) }
您可以使用 copy
方法修复它,仅更新文本:
numCyclesString.value = numCyclesString.value.copy(text = numCycles.toString())
但大多数情况下,当您不需要访问应用程序中的选择信息时,您可以使用 Text
的另一个重载来接受和更新 String
值:它会做这个 copy
里面的逻辑和你的代码会更清晰。
还有一些小提示:
如果您有一些正在使用它的依赖项,则仅在 Compose 中使用
LiveData
。在其他情况下,直接使用可变状态要干净得多:var numCycles by mutableStateOf<Int?>(null) private set fun onNewNumCycles(cycles: Int?) { numCycles = cycles }
您可以使用
toIntOrNull
代替您的StringToInt.tryParse
助手您不需要在 activity 中创建视图模型并将其传递给您的视图。您可以使用
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)
)