Jetpack Compose Material Design TextField WrapContentWidth [duplicate] - android

I'm trying to make an OutlinedTextField (see below image) act as "wrap-content". It would be ideal for it to be just big enough to fit the inside text 500g and expand and contract if user edits the field.
If I remember correctly this was possible to do with the old views with the (non-Compose) ConstraintLayout but in Compose this isn't working.
So far I have tried adjusting various modifiers, (each separately):
.defaultMinSize(minWidth = 10.dp)
.width(IntrinsicSize.Min)
.widthIn(1.dp, Dp.Infinity)
.requiredWidth(IntrinsicSize.Min)
I also tried putting the Row into a Compose ConstraintLayout, but nothing seems to work
If I set the size modifier to a small width it does reduce the width but it no longer expands...
Anyone was able to achieve this?

The OutlinedTextField has default min size.
You can achieve it using a OutlinedTextFieldDecorationBox together with BasicTextField, applying the modifier width(IntrinsicSize.Min).
Something like:
var text by remember { mutableStateOf("500") }
val interactionSource = remember { MutableInteractionSource() }
val enabled = true
val singleLine = true
BasicTextField(
value = text,
onValueChange = {text = it},
interactionSource = interactionSource,
enabled = enabled,
singleLine = singleLine,
modifier = Modifier.width(IntrinsicSize.Min)
) {
TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = text,
visualTransformation = VisualTransformation.None,
innerTextField = it,
singleLine = singleLine,
enabled = enabled,
interactionSource = interactionSource,
// keep vertical paddings but change the horizontal
contentPadding = TextFieldDefaults.textFieldWithoutLabelPadding(
start = 8.dp, end = 8.dp
),
colors = TextFieldDefaults.outlinedTextFieldColors()
)
}
Note: it requires at least the compose version 1.2.0

Related

Jetpack Compose - How to use custom height on TextFieldDefaults.OutlinedTextFieldDecorationBox [duplicate]

This question already has answers here:
Jetpack Compose Decrease height of TextField
(5 answers)
Closed 4 months ago.
I'm trying to create a custom TextField using Android Jetpack compose, see below
TextFieldDefaults.OutlinedTextFieldDecorationBox(
contentPadding = PaddingValues(vertical = 10.dp),
value = searchText,
innerTextField = innerTextField,
enabled = enabled,
singleLine = singleLine,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
colors = textFieldColors,
leadingIcon = {
Icon(
modifier = Modifier.size(leadingIconSize),
painter = painterResource(id = R.drawable.ic_search),
contentDescription = ""
)
},
placeholder = {
Text("what is doin ")
}
)
The height of the text field should be 36dp due to design requirements, however the text field has its own height about 56dp.
I could not find any information how to customize that value so I used the parameter contentPadding to achieve the goal.
However, it seems too ugly for me. Is there any other way to achieve to implement this?
Thanks in advance.
Apply the height modifier to the BasicTextField.
Something like:
BasicTextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.height(36.dp),
singleLine = singleLine,
interactionSource = interactionSource
) { innerTextField ->
TextFieldDefaults.OutlinedTextFieldDecorationBox(
value = text,
innerTextField = innerTextField,
enabled = enabled,
singleLine = singleLine,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
contentPadding = TextFieldDefaults.textFieldWithoutLabelPadding(
top = 0.dp,
bottom = 0.dp
)
)
}

How do I make a disabled compose OutlinedTextField look like its enabled?

I have a compose OutlinedTextField which shouldn't be manually editable but filled using input from something that happens on clicking the text field. But setting the field as readOnly=true makes the clickable modifier not work. So a workaround I found is to set it as enabled=false which lets clickable work.
OutlinedTextField(
value = text,
onValueChange = { text = it},
enabled = false,
modifier = Modifier.clickable { text= "Clicked"}
)
How can I make this look as if its enabled based on whatever theme is being followed without setting a fixed color?
You can use the TextFieldDefaults.outlinedTextFieldColors applying in the disabled colors the same value of the enabled colors without using hardcoded values:
OutlinedTextField(
//...
enabled = false,
colors = TextFieldDefaults.outlinedTextFieldColors(
disabledTextColor = LocalContentColor.current.copy(LocalContentAlpha.current),
backgroundColor = Color.Transparent,
disabledBorderColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled),
disabledLabelColor = MaterialTheme.colors.onSurface.copy(ContentAlpha.medium),
)
)

Showing a text field in the app bar in Jetpack Compose with Material3

Many Android apps feature a search box in the app bar / toolbar, that looks somewhat like this screenshot:
How do I recreate something similar in Jetpack Compose + Material3? I would like the text field to have these features:
support showing a hint when the content string is empty (e.g. "Type your search...")
auto-focus on the first composition, so that the keyboard is opened
put the cursor at the end of the content string on the first composition
I tried to reproduce the same behavior in Jetpack Compose + Material3 by putting a TextField in the title of a CenterAlignedTopAppBar but the result does not look good. The text field uses the whole height of the app bar and has a grey background, and both of these things look odd.
I came up with a AppBarTextField after some engineering, see the code below. I had to use the lower-level BasicTextField since the normal TextField is not customizable enough. The code having to do with theming and color was copied directly from TextField's implementation, so that the theme's customizations apply normally to the components of the text field.
The parameters the AppBarTextField composable accepts are:
value: the content string to show in the text field
onValueChange: new values are passed here (remember to update value!)
hint: the hint to show when the text field is empty
modifier, keyboardOptions and keyboardActions: they are passed directly to BasicTextField and they behave the same as they would in a normal TextField. If you need to customize other TextField parameters just add them to the function signature and then pass them to BasicTextField.
The requested features are implemented:
the focus acquisition was achieved with a SideEffect, so that it would only happen on the first composition
putting the cursor at the end on the first composition required using a TextFieldValue
the strange-looking background is not present anymore, since no .background() modifier is present (while it is in the normal TextField)
the hint was added using by passing a placeholder to TextFieldDecorationBox in the decorationBox parameter (note that this was also possible with TextField)
TextFieldDecorationBox's padding is also now only 4dp. Padding was added here (and not with a modifier on BasicTextField) since otherwise the bottom line indicator (which is, instead, displayed using the .indicatorLine() modifier) would not be shown correctly.
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun AppBarTextField(
value: String,
onValueChange: (String) -> Unit,
hint: String,
modifier: Modifier = Modifier,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default,
) {
val interactionSource = remember { MutableInteractionSource() }
val textStyle = LocalTextStyle.current
// make sure there is no background color in the decoration box
val colors = TextFieldDefaults.textFieldColors(containerColor = Color.Unspecified)
// If color is not provided via the text style, use content color as a default
val textColor = textStyle.color.takeOrElse {
MaterialTheme.colorScheme.onSurface
}
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor, lineHeight = 50.sp))
// request focus when this composable is first initialized
val focusRequester = FocusRequester()
SideEffect {
focusRequester.requestFocus()
}
// set the correct cursor position when this composable is first initialized
var textFieldValue by remember {
mutableStateOf(TextFieldValue(value, TextRange(value.length)))
}
textFieldValue = textFieldValue.copy(text = value) // make sure to keep the value updated
CompositionLocalProvider(
LocalTextSelectionColors provides LocalTextSelectionColors.current
) {
BasicTextField(
value = textFieldValue,
onValueChange = {
textFieldValue = it
// remove newlines to avoid strange layout issues, and also because singleLine=true
onValueChange(it.text.replace("\n", ""))
},
modifier = modifier
.fillMaxWidth()
.heightIn(32.dp)
.indicatorLine(
enabled = true,
isError = false,
interactionSource = interactionSource,
colors = colors
)
.focusRequester(focusRequester),
textStyle = mergedTextStyle,
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
interactionSource = interactionSource,
singleLine = true,
decorationBox = { innerTextField ->
// places text field with placeholder and appropriate bottom padding
TextFieldDefaults.TextFieldDecorationBox(
value = value,
visualTransformation = VisualTransformation.None,
innerTextField = innerTextField,
placeholder = { Text(text = hint) },
singleLine = true,
enabled = true,
isError = false,
interactionSource = interactionSource,
colors = colors,
contentPadding = PaddingValues(bottom = 4.dp)
)
}
)
}
}
Here is an example usage:
var value by rememberSaveable { mutableStateOf("initial content") }
CenterAlignedTopAppBar(
title = {
AppBarTextField(
value = value,
onValueChange = { newValue -> value = newValue },
hint = "A hint..."
)
},
navigationIcon = /* the back icon */,
actions = /* the search icon */
)

Jetpack Compose TextField cuts text on scroll

I have a TextField with a fixed height. When the user enters a longer text it will scroll. It will cut off any text within the padding when scrolling:
Basically something like this:
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { value -> text = value },
modifier = modifier
.fillMaxWidth()
.height(100.dp),
colors = TextFieldDefaults.textFieldColors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
backgroundColor = Color.Transparent
)
)
It is possible to adjust/remove the padding for a TextField, by using BasicTextField directly, e.g. see this stack overflow question.
However I want to keep the padding, but without the clipping of the text when the user scrolls. A simple Text Composable has this behavior.
You can use BasicTextField and modify its decorationBox parameter.
Simply put innerTextField() inside a scrollable Column and add Spacers at the top and the bottom of it.
var text by remember {
mutableStateOf("Hello Stackoverflow")
}
val paddingSize = remember { 16.dp }
BasicTextField(
modifier = Modifier
.height(100.dp),
value = text,
onValueChange = { text = it },
decorationBox = { innerTextField ->
Column(
modifier = Modifier
.fillMaxWidth()
.verticalScroll(state = rememberScrollState())
) {
Spacer(modifier = Modifier.height(paddingSize))
innerTextField()
Spacer(modifier = Modifier.height(paddingSize))
}
}
)
Just use the maxLines parameter of the TextField to avoid clipping. Set it to 1 or 2, per your case, then adjust the height/font-size so the max lines you specify are accomodated correctly. It will likely start to snap to the visible portion, cutting the extra stuff entirely.

Dense TextField in Jetpack Compose

In the old View system, we can make TextInputLayout dense using style Widget.MaterialComponents.TextInputLayout.FilledBox.Dense.
How can I create a dense variant of TextField in Compose?
Starting with 1.2.0-alpha04 you can use the TextFieldDecorationBox together with BasicTextField to build a custom text field based on Material Design text fields.
To create a dense text field, use contentPadding parameter to decrease the paddings around the input field.
Something like:
var text by remember { mutableStateOf("") }
val singleLine = true
val enabled = true
val interactionSource = remember { MutableInteractionSource() }
BasicTextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.indicatorLine(enabled, false, interactionSource, TextFieldDefaults.textFieldColors())
.background(
TextFieldDefaults.textFieldColors().backgroundColor(enabled).value,
TextFieldDefaults.TextFieldShape
)
.width(TextFieldDefaults.MinWidth),
singleLine = singleLine,
interactionSource = interactionSource
) { innerTextField ->
TextFieldDecorationBox(
value = text,
innerTextField = innerTextField,
enabled = enabled,
singleLine = singleLine,
visualTransformation = VisualTransformation.None,
interactionSource = interactionSource,
label = { Text("Label") },
contentPadding = TextFieldDefaults.textFieldWithLabelPadding(
start = 4.dp,
end = 4.dp,
bottom = 4.dp // make it dense
)
)
}

Categories

Resources