如何为Jetpack Compose TextField配置仅支持货币正则输入的InputFilter?是否可使用InputFilter列表实现特定货币格式输入限制?
Question:
How can I configure an InputFilter for Jetpack Compose's TextField to only accept currency format inputs that match a regex pattern? Specifically, I want to allow formats like $1,01 or $100,95. Here's how I implemented similar logic using EditText:
class CurrencyFormatInputFilter : InputFilter { private val pattern = Pattern.compile(CURRENCY_INPUT_REGEX) override fun filter( source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int ): CharSequence? { val result = (dest.subSequence(0, dstart).toString() + source.toString() + dest.subSequence(dend, dest.length)) val matcher = pattern.matcher(result) return if (!matcher.matches()) dest.subSequence(dstart, dend) else null } } // Usage editText.filters = arrayOf(CurrencyFormatInputFilter(), InputFilter.LengthFilter(8))
Answer:
Great question! In Jetpack Compose, TextField doesn’t expose a direct filters property like EditText, but we have two straightforward ways to replicate this behavior: using a custom TextFilter (Compose’s equivalent of InputFilter) or handling validation directly in the onValueChange callback. Let’s break this down with practical examples.
1. Custom TextFilter (Reusable, Analogous to InputFilter)
Compose provides the TextFilter interface, which works almost identically to Android View's InputFilter. We can build a reusable filter for currency validation:
import androidx.compose.ui.text.input.TextFilter import android.text.Spanned import java.util.regex.Pattern class CurrencyTextFilter(private val pattern: Pattern) : TextFilter { override fun filter( source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int ): CharSequence? { // Build the full text that would result from the proposed input val newText = dest.subSequence(0, dstart).toString() + source.subSequence(start, end) + dest.subSequence(dend, dest.length) val matcher = pattern.matcher(newText) // Reject the change if the new text doesn't match the pattern return if (!matcher.matches()) dest.subSequence(dstart, dend) else null } }
To use this filter, define a regex that supports partial valid inputs (for better user experience) and pass it to your TextField:
import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.text.input.KeyboardOptions import androidx.compose.ui.text.input.KeyboardType import java.util.regex.Pattern @Composable fun CurrencyTextField() { // Regex allows incremental input: "$", "$1", "$123,", "$123,95" val currencyPattern = Pattern.compile("^\\$\\d{0,3}(,\\d{0,2})?$") val textState = remember { mutableStateOf("") } Column { TextField( value = textState.value, onValueChange = { textState.value = it }, filter = CurrencyTextFilter(currencyPattern), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text("Enter Currency (e.g., $100,95)") }, maxLines = 1 ) } }
2. Direct Filtering in onValueChange (Simpler, No Reusable Filter)
If you don’t need a reusable filter, handling validation directly in onValueChange is more concise:
import androidx.compose.foundation.layout.Column import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.text.input.KeyboardOptions import androidx.compose.ui.text.input.KeyboardType @Composable fun CurrencyTextField() { val currencyRegex = Regex("^\\$\\d{0,3}(,\\d{0,2})?$") val maxLength = 8 // Matches the LengthFilter from your EditText example val textState = remember { mutableStateOf("") } Column { TextField( value = textState.value, onValueChange = { newText -> // Only update the state if input is valid and within length limit if (newText.length <= maxLength && (newText.isEmpty() || currencyRegex.matches(newText))) { textState.value = newText } }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text("Enter Currency (e.g., $100,95)") }, maxLines = 1 ) } }
Key Tips for Better UX:
- Partial Input Support: The regex
^\\$\\d{0,3}(,\\d{0,2})?$lets users type incrementally (e.g., start with$, add digits, then the comma and cents). A strict regex like^\\$\\d{1,3},\\d{2}$would block partial inputs, making it impossible to build the valid format step-by-step. - Length Restriction: We added a
maxLengthcheck in theonValueChangeexample to replicate theInputFilter.LengthFilter(8)from your EditText code. - Keyboard Type: Using
KeyboardType.Numberhelps users enter digits quickly, though they’ll still need to type$and,manually (you could add a visual transformation to auto-inject the$if needed).
内容的提问来源于stack exchange,提问作者Thracian

