Basic text composable that provides an interactive box that accepts text input through software or hardware keyboard, but provides no decorations like hint or placeholder.
BasicTextFieldCustomInputTransformationSample
@Composable
fun BasicTextFieldCustomInputTransformationSample() {
// Demonstrates how to create a custom and relatively complex InputTransformation.
val state = remember { TextFieldState() }
BasicTextField(
state,
inputTransformation =
InputTransformation {
// A filter that always places newly-input text at the start of the string, after a
// prompt character, like a shell.
val promptChar = '>'
fun CharSequence.countPrefix(char: Char): Int {
var i = 0
while (i < length && get(i) == char) i++
return i
}
// Step one: Figure out the insertion point.
val newPromptChars = asCharSequence().countPrefix(promptChar)
val insertionPoint = if (newPromptChars == 0) 0 else 1
// Step two: Ensure text is placed at the insertion point.
if (changes.changeCount == 1) {
val insertedRange = changes.getRange(0)
val replacedRange = changes.getOriginalRange(0)
if (!replacedRange.collapsed && insertedRange.collapsed) {
// Text was deleted, delete forwards from insertion point.
delete(insertionPoint, insertionPoint + replacedRange.length)
}
}
// Else text was replaced or there were multiple changes - don't handle.
// Step three: Ensure the prompt character is there.
if (newPromptChars == 0) {
insert(0, ">")
}
// Step four: Ensure the cursor is ready for the next input.
placeCursorAfterCharAt(0)
},
)
}
BasicTextFieldDecoratorSample
@Composable
fun BasicTextFieldDecoratorSample() {
// Demonstrates how to use the decorator API on BasicTextField
val state = rememberTextFieldState("Hello, World!")
BasicTextField(
state = state,
decorator = { innerTextField ->
// Because the decorator is used, the whole Row gets the same behaviour as the internal
// input field would have otherwise. For example, there is no need to add a
// `Modifier.clickable` to the Row anymore to bring the text field into focus when user
// taps on a larger text field area which includes paddings and the icon areas.
Row(
Modifier.background(Color.LightGray, RoundedCornerShape(percent = 30))
.padding(16.dp)
) {
Icon(Icons.Default.MailOutline, contentDescription = "Mail Icon")
Spacer(Modifier.width(16.dp))
innerTextField()
}
},
)
}
BasicTextFieldSample
@Composable
fun BasicTextFieldSample() {
var value by
rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) }
BasicTextField(
value = value,
onValueChange = {
// it is crucial that the update is fed back into BasicTextField in order to
// see updates on the text
value = it
},
)
}
BasicTextFieldWithStringSample
@Composable
fun BasicTextFieldWithStringSample() {
var value by rememberSaveable { mutableStateOf("initial value") }
BasicTextField(
value = value,
onValueChange = {
// it is crucial that the update is fed back into BasicTextField in order to
// see updates on the text
value = it
},
)
}
BasicTextFieldWithValueOnValueChangeSample
@Composable
fun BasicTextFieldWithValueOnValueChangeSample() {
var text by remember { mutableStateOf("") }
// A reference implementation that demonstrates how to create a TextField with the legacy
// state hoisting design around `BasicTextField(TextFieldState)`
StringTextField(value = text, onValueChange = { text = it })
}
CreditCardSample
@Composable
fun CreditCardSample() {
/** The offset translator used for credit card input field */
val creditCardOffsetTranslator =
object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
return when {
offset < 4 -> offset
offset < 8 -> offset + 1
offset < 12 -> offset + 2
offset <= 16 -> offset + 3
else -> 19
}
}
override fun transformedToOriginal(offset: Int): Int {
return when {
offset <= 4 -> offset
offset <= 9 -> offset - 1
offset <= 14 -> offset - 2
offset <= 19 -> offset - 3
else -> 16
}
}
}
/**
* Converts up to 16 digits to hyphen connected 4 digits string. For example, "1234567890123456"
* will be shown as "1234-5678-9012-3456"
*/
val creditCardTransformation = VisualTransformation { text ->
val trimmedText = if (text.text.length > 16) text.text.substring(0..15) else text.text
var transformedText = ""
trimmedText.forEachIndexed { index, char ->
transformedText += char
if ((index + 1) % 4 == 0 && index != 15) transformedText += "-"
}
TransformedText(AnnotatedString(transformedText), creditCardOffsetTranslator)
}
var text by rememberSaveable { mutableStateOf("") }
BasicTextField(
value = text,
onValueChange = { input ->
if (input.length <= 16 && input.none { !it.isDigit() }) {
text = input
}
},
modifier = Modifier.size(170.dp, 30.dp).background(Color.LightGray).wrapContentSize(),
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
visualTransformation = creditCardTransformation,
)
}
PlaceholderBasicTextFieldSample
@Composable
fun PlaceholderBasicTextFieldSample() {
var value by rememberSaveable { mutableStateOf("initial value") }
Box {
BasicTextField(value = value, onValueChange = { value = it })
if (value.isEmpty()) {
Text(text = "Placeholder")
}
}
}
TextFieldWithIconSample
@Composable
fun TextFieldWithIconSample() {
var value by rememberSaveable { mutableStateOf("initial value") }
BasicTextField(
value = value,
onValueChange = { value = it },
decorationBox = { innerTextField ->
// Because the decorationBox is used, the whole Row gets the same behaviour as the
// internal input field would have otherwise. For example, there is no need to add a
// Modifier.clickable to the Row anymore to bring the text field into focus when user
// taps on a larger text field area which includes paddings and the icon areas.
Row(
Modifier.background(Color.LightGray, RoundedCornerShape(percent = 30))
.padding(16.dp)
) {
Icon(Icons.Default.MailOutline, contentDescription = null)
Spacer(Modifier.width(16.dp))
innerTextField()
}
},
)
}