es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Entrada Numérica de Jetpack Compose

Estoy tratando de hacer un campo de entrada numérico que actualice un campo de respaldo cuando se ingresa un número válido. Cuando se actualiza el campo de respaldo, la interfaz de usuario debe reflejar eso, ya que también puede actualizarse por otras cosas.

Tengo una implementación donde tengo una cadena local que se edita, y eso se muestra, y cada vez que cambia el valor, se verifica si se puede analizar un entero, en cuyo caso se actualiza el campo de respaldo. El problema es que el cursor se restablece al inicio del campo, por lo que si está escribiendo un número de varios dígitos, los dígitos se ingresan fuera de secuencia.

No parece haber nada que pueda usar para saber cuándo el usuario abandona el control y la edición ha terminado. Aunque estoy usando un TextFieldValue, no puedo actualizar el texto en ese objeto y de lo contrario preservar el estado de edición en lugar de recrear todo el objeto.

Esto no puede ser un problema nuevo y sin embargo la discusión en línea es escasa. ¿Estoy haciendo algo estúpido y exageradamente complicado?

Código:

Tags:  , ,

Answer

  1. Avatar for davy.ai

    Para resolver el problema de que el cursor se reinicie al inicio del campo cuando se introduce un número de varios dígitos, podría utilizar un objeto FocusRequester para solicitar el enfoque en un elemento de su elección cuando se termine la edición y luego perder el enfoque del campo de texto. Podría crear una instancia estado-dependiente de FocusRequester en su composable TestNumeric y utilizarlo para solicitar el enfoque en otro elemento, como un elemento de Texto ficticio, cuando se ha introducido un número válido y se ha añadido al campo de fondo.

    Para actualizar TextFieldValue y conservar el estado de edición, puede utilizar el parámetro opcional cursorPosition de TextField para establecer la posición del cursor en la longitud de la cadena editada. De esta manera, el cursor permanece al final del texto editado después de que se haya realizado una actualización.

    Por último, para saber cuándo el usuario abandona el control y ha finalizado la edición, puede utilizar el callback opcional onImeActionPerformed de TextField. Este callback se llamará cuando el usuario cierre el IME, lo que indica que ha finalizado la edición. En este callback, puede solicitar el enfoque en el elemento de Texto ficticio y perder el enfoque del campo de texto.

    Aquí está una versión actualizada de su código con estos cambios:

    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.runtime.saveable.rememberSaveable
    import androidx.compose.ui.platform.LocalSoftwareKeyboardController
    import androidx.compose.ui.text.input.KeyboardType
    import androidx.compose.ui.text.input.TextFieldValue
    import androidx.compose.ui.unit.dp
    import androidx.lifecycle.LiveData
    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.ViewModel
    import com.example.numericinputtest.ui.theme.NumericInputTestTheme
    
    class State : ViewModel() {
        private val _numCycles = MutableLiveData<int>(0)
        val numCycles: LiveData<int> = _numCycles
    
        fun onNewNumCycles(cycles: Int) {
            _numCycles.value = cycles
        }
    }
    
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            val state = State()
    
            setContent {
                NumericInputTestTheme {
                    // Un contenedor de superficie que utiliza el color 'background' del tema
                    Surface(color = MaterialTheme.colors.background) {
                        TestNumeric(state = state)
                    }
                }
            }
        }
    }
    
    @Composable
    fun TestNumeric(state: State) {
        val numCycles: Int by state.numCycles.observeAsState(0)
    
        // Para poder editar el texto normalmente, necesitamos una cadena local y el campo de fondo
        // sólo se actualiza cuando hay un número válido
        val numCyclesString = rememberSaveable { mutableStateOf(TextFieldValue(numCycles.toString())) }
    
        // Dado que ahora estamos mostrando una cadena local, ésta no se cambia cuando el estado de fondo
        // cambia. Así que tenemos que capturar esta ocurrencia y actualizar manualmente.
        state.numCycles.observeAsState()
            .run { numCyclesString.value = TextFieldValue(numCycles.toString()) }
    
        // Crear un FocusRequester para solicitar el enfoque en otro elemento después de la edición
        val focusRequester = remember { FocusRequester() }
    
        // Solicitar el enfoque en el elemento ficticio de Texto cuando se termine de editar
        // y luego perder el enfoque del campo de texto
        val softwareKeyboardController = LocalSoftwareKeyboardController.current
        val onImeActionPerformed = {
            focusRequester.requestFocus()
            softwareKeyboardController?.hide()
        }
    
        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,
                    imeAction = androidx.compose.ui.text.input.ImeAction.Done
                ),
                cursorPosition = numCyclesString.value.text.length,
                onImeActionPerformed = onImeActionPerformed,
                modifier = androidx.compose.ui.Modifier.focusRequester(focusRequester)
                    .navigationBarsWithImePadding()
                    .padding(bottom = 8.dp) // Añadir un poco de padding extra en la parte inferior para el teclado móvil
            )
    
            // Un elemento de Texto ficticio para solicitar el enfoque después de la edición
            Text(text = "", modifier = androidx.compose.ui.Modifier.requestFocus())
        }
    }
    

    Tenga en cuenta que también he añadido algunas modificaciones adicionales, como utilizar rememberSaveable en lugar de remember para preservar el valor de numCyclesString en los cambios de configuración, y añadir un poco más de padding en la parte inferior de TextField para evitar que sea cubierto por el teclado móvil.

Comments are closed.