admin管理员组

文章数量:1296914

I'm using InputTransformation in Jetpack Compose to filter user input in a BasicTextField. The transformation works correctly, but the cursor remains in the same position after entering text instead of moving backward.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activitypose.setContent
import androidx.activity.enableEdgeToEdge
import androidxpose.foundation.interaction.MutableInteractionSource
import androidxpose.foundation.layout.Column
import androidxpose.foundation.layout.fillMaxSize
import androidxpose.foundation.layout.fillMaxWidth
import androidxpose.foundation.layout.height
import androidxpose.foundation.layout.padding
import androidxpose.foundation.text.BasicTextField
import androidxpose.foundation.text.KeyboardOptions
import androidxpose.foundation.text.input.InputTransformation
import androidxpose.foundation.text.input.TextFieldBuffer
import androidxpose.foundation.text.input.TextFieldLineLimits
import androidxpose.foundation.text.input.TextFieldState
import androidxpose.foundation.text.input.maxLength
import androidxpose.foundation.text.input.rememberTextFieldState
import androidxpose.foundation.text.input.then
import androidxpose.material3.ExperimentalMaterial3Api
import androidxpose.material3.Text
import androidxpose.material3.TextFieldDefaults
import androidxpose.runtime.Composable
import androidxpose.runtime.remember
import androidxpose.ui.Modifier
import androidxpose.ui.text.input.KeyboardType
import androidxpose.ui.unit.dp

class MainActivity : ComponentActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            val stateOne = rememberTextFieldState(initialText = "Hellow")
            val stateTwo = rememberTextFieldState()
            BasicTextFieldExamples(
                stateTwo,
                remember { MutableInteractionSource() },
            )
        }
    }

    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun BasicTextFieldExamples(
        stateOTwo: TextFieldState,
        secondInteractionSource: MutableInteractionSource,
    ) {
        Column(
            Modifier
                .fillMaxSize()
                .padding(50.dp)
        ) {
            BasicTextField(
                state = stateOTwo,
                modifier = Modifier
                    .padding(top = 100.dp)
                    .fillMaxWidth()
                    .height(56.dp),
                inputTransformation = InputTransformation.maxLength(6)
                    .then(LetterOnlyTransformation()),
                interactionSource = secondInteractionSource,
                decorator = TextFieldDefaults.decorator(
                    state = stateOTwo,
                    enabled = true,
                    label = {
                        Text("Last Name")
                    },
                    placeholder = {
                        Text("Example 2")
                    },
                    lineLimits = TextFieldLineLimits.Default,
                    interactionSource = secondInteractionSource,
                    outputTransformation = null
                )
            )
        }
    }
}

class LetterOnlyTransformation : InputTransformation {
    override val keyboardOptions: KeyboardOptions?
        get() = KeyboardOptions(keyboardType = KeyboardType.Text)

    override fun TextFieldBuffer.transformInput() {
        val filteredValue = asCharSequence().filter { it.isLetter() }
        if (filteredValue != asCharSequence()) {
            replace(0, length, filteredValue)
        }
    }
}

Question:

How can I ensure that the cursor moves correctly after filtering input in InputTransformation? Is there a better way to handle this scenario in Jetpack Compose?

Any help is appreciated!

See the problem in here

I'm using InputTransformation in Jetpack Compose to filter user input in a BasicTextField. The transformation works correctly, but the cursor remains in the same position after entering text instead of moving backward.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activitypose.setContent
import androidx.activity.enableEdgeToEdge
import androidxpose.foundation.interaction.MutableInteractionSource
import androidxpose.foundation.layout.Column
import androidxpose.foundation.layout.fillMaxSize
import androidxpose.foundation.layout.fillMaxWidth
import androidxpose.foundation.layout.height
import androidxpose.foundation.layout.padding
import androidxpose.foundation.text.BasicTextField
import androidxpose.foundation.text.KeyboardOptions
import androidxpose.foundation.text.input.InputTransformation
import androidxpose.foundation.text.input.TextFieldBuffer
import androidxpose.foundation.text.input.TextFieldLineLimits
import androidxpose.foundation.text.input.TextFieldState
import androidxpose.foundation.text.input.maxLength
import androidxpose.foundation.text.input.rememberTextFieldState
import androidxpose.foundation.text.input.then
import androidxpose.material3.ExperimentalMaterial3Api
import androidxpose.material3.Text
import androidxpose.material3.TextFieldDefaults
import androidxpose.runtime.Composable
import androidxpose.runtime.remember
import androidxpose.ui.Modifier
import androidxpose.ui.text.input.KeyboardType
import androidxpose.ui.unit.dp

class MainActivity : ComponentActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            val stateOne = rememberTextFieldState(initialText = "Hellow")
            val stateTwo = rememberTextFieldState()
            BasicTextFieldExamples(
                stateTwo,
                remember { MutableInteractionSource() },
            )
        }
    }

    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    fun BasicTextFieldExamples(
        stateOTwo: TextFieldState,
        secondInteractionSource: MutableInteractionSource,
    ) {
        Column(
            Modifier
                .fillMaxSize()
                .padding(50.dp)
        ) {
            BasicTextField(
                state = stateOTwo,
                modifier = Modifier
                    .padding(top = 100.dp)
                    .fillMaxWidth()
                    .height(56.dp),
                inputTransformation = InputTransformation.maxLength(6)
                    .then(LetterOnlyTransformation()),
                interactionSource = secondInteractionSource,
                decorator = TextFieldDefaults.decorator(
                    state = stateOTwo,
                    enabled = true,
                    label = {
                        Text("Last Name")
                    },
                    placeholder = {
                        Text("Example 2")
                    },
                    lineLimits = TextFieldLineLimits.Default,
                    interactionSource = secondInteractionSource,
                    outputTransformation = null
                )
            )
        }
    }
}

class LetterOnlyTransformation : InputTransformation {
    override val keyboardOptions: KeyboardOptions?
        get() = KeyboardOptions(keyboardType = KeyboardType.Text)

    override fun TextFieldBuffer.transformInput() {
        val filteredValue = asCharSequence().filter { it.isLetter() }
        if (filteredValue != asCharSequence()) {
            replace(0, length, filteredValue)
        }
    }
}

Question:

How can I ensure that the cursor moves correctly after filtering input in InputTransformation? Is there a better way to handle this scenario in Jetpack Compose?

Any help is appreciated!

See the problem in here

Share Improve this question edited Feb 11 at 17:55 Vivek Modi asked Feb 11 at 17:39 Vivek ModiVivek Modi 7,30120 gold badges101 silver badges229 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

This issue occurs because TextFieldBuffer.transformInput() is called even during selection or cursor position changes, and the condition if (filteredValue != asCharSequence()) always returns true (this comparison would work as expected if they were String objects where the equals method is overridden).

As a result, replace(0, length, filteredValue) is called every time you move the cursor, and this method reverts cursor position changes.

You can use revertAllChanges() instead – it handles this under the hood.

Additionally, you can track the changes.changeCount value to return from transformInput() when changeCount is 0 (which occurs when the input text itself is not changing – e.g., during cursor position changes).

Here's how the transformInput() method may look:

@OptIn(ExperimentalFoundationApi::class)
override fun TextFieldBuffer.transformInput() {
    if (changes.changeCount == 0) return
    val currentInput = asCharSequence().toString()
    val filteredValue = currentInput.filter { it.isLetter() }
    if (filteredValue != currentInput) {
        revertAllChanges()
    }
}

本文标签: androidCursor doesn39t move after filtering text Jetpack ComposeStack Overflow