Reputation: 1654
I have an OutlinedTextField with DropdownMenu inside it I want that after pressing on the item inside the DropdownMenu list, the value of the item started to be inside the OutlinedTextField being also adjusted by width depending of how long the text is. How I can do it ?
Update (14.02.2022)
By default OutlinedTextField uses OutlinedTextFieldLayout which contains BasicTextField with defaultMinSize Modifier parameter.
BasicTextField(
value = value,
modifier = modifier
.then(
if (decoratedLabel != null) {
Modifier.padding(top = OutlinedTextFieldTopPadding)
} else {
Modifier
}
)
.defaultMinSize(
minWidth = MinWidth,
minHeight = MinHeight
)
...
/** The default min width applied for a TextField and OutlinedTextField. Note that you can override it by applying Modifier.widthIn directly on a text field. */
val MinWidth = 280.dp
To make the width be Intrinsic Min I should have to duplicate 3 files (TextField.kt, TextFieldImpl.kt, OutlinedTextField.kt) from Compose library and make my own OutlinedTextField with these changes in OutlinedTextFieldLayout component:
@Composable
internal fun OutlinedFormTextFieldLayout(
modifier: Modifier,
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
enabled: Boolean,
readOnly: Boolean,
keyboardOptions: KeyboardOptions,
keyboardActions: KeyboardActions,
textStyle: TextStyle,
singleLine: Boolean,
maxLines: Int = Int.MAX_VALUE,
visualTransformation: VisualTransformation,
interactionSource: MutableInteractionSource,
decoratedPlaceholder: @Composable ((Modifier) -> Unit)?,
decoratedLabel: @Composable (() -> Unit)?,
leading: @Composable (() -> Unit)?,
trailing: @Composable (() -> Unit)?,
leadingColor: Color,
trailingColor: Color,
labelProgress: Float,
indicatorWidth: Dp,
indicatorColor: Color,
cursorColor: Color,
shape: Shape
) {
val textWidth = remember { mutableStateOf(0) }
val labelSize = remember { mutableStateOf(Size.Zero) }
fun Modifier.widthIntrinsicSizeMinModifier() = width(IntrinsicSize.Min)
fun Modifier.widthTextWidthModifier() = width(textWidth.value.dp)
if (textWidth.value == 0) {
modifier.then(Modifier.widthIntrinsicSizeMinModifier())
} else {
modifier.then(Modifier.widthTextWidthModifier())
}
BasicTextField(
value = value,
modifier = modifier
.then(
if (decoratedLabel != null) {
Modifier.padding(top = OutlinedTextFieldTopPadding)
} else {
Modifier
}
)
.onSizeChanged {
textWidth.value = it.width
}, ...
With these changes we don't have a default width anymore but we still have some spacing left from the right side
Update(15.02.2022)
Don't duplicate files from @Compose library. Some of the API call won't work. In my case textColor and background set was not working for my custom OutlinedFormTextField where for OutlinedTextField everything was working fine:
colors = TextFieldDefaults.outlinedTextFieldColors(
textColor = Color.Red,
backgroundColor = backgroundColor.value
...
I also found that instead of overwriting the files which related to OutlinedTextField somehow you can just wrap your OutlinedTextField with Row component and set in it:
Modifier.defaultMinSize(minWidth = 1.dp)
It will remove the huge minWidth that Compose suggest by default but the extra spacing after the label is still exist.
Does anyone knows how to remove it ?
Upvotes: 2
Views: 3127
Reputation: 35997
To force the BasicTextField
to warp its width instead of using the defaut minWidth = 280 dp use the following modifier:
BasicTextField(
modifier = modifier
.width(IntrinsicSize.Min)
)
What doesn't work: Setting the Modifier.widthIn(min = 1.dp)
.
Upvotes: 0
Reputation: 1654
Here is my temporary solution with adjustable OutlinedTextField component. It also fix the OutlinedTextField crossed label on the top left corner when the background was changed:
// Duration
private const val LabelOffsetAnimationDuration = 500
private const val LabelBackgroundColorAnimationDuration = 1
private const val LabelBackgroundColorAnimationDelay = 200
private const val TextFieldBackgroundColorAnimationDuration = 0
private const val TextFieldBackgroundColorAnimationDelay = 0
private const val BorderColorAnimationDuration = 10
private const val BorderColorAnimationDelay = 50
// Offset
private const val LabelAnimationTopLeftPositionOffsetX = 20F
private const val LabelAnimationTopLeftPositionOffsetY = -30F
private const val LabelAnimationCenterPositionOffsetX = 25F
private const val LabelAnimationCenterPositionOffsetY = 0F
private const val LabelTextSizeAnimationTopLeftPosition = 12F
private const val LabelTextSizeAnimationCenterPosition = 16F
// Z-Index
private const val LabelBubbleZIndex = 2f
// Size
private val TextFieldMinWidth = 100.dp
private fun getTargetLabelPosition(isFocused: Boolean, text: String) =
if (!isFocused && text.isNotEmpty() || isFocused) {
LabelPosition.TopLeft
} else {
LabelPosition.Center
}
@Composable
fun MyOutlinedTextField(
modifier: Modifier = Modifier,
textFieldState: TextFieldState,
onValueChange: (String) -> Unit,
label: String,
enabled: Boolean = true,
readOnly: Boolean = false,
isError: Boolean,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardAction: ((KeyboardActionScope) -> Unit),
trailingIcon: @Composable (() -> Unit)? = null,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center
) {
var textFieldIsFocused by remember { mutableStateOf(false) }
var labelPosition by remember {
mutableStateOf(
getTargetLabelPosition(
isFocused = textFieldIsFocused,
text = textFieldState.text
)
)
}
val labelPositionTransition = updateTransition(
targetState = labelPosition,
label = LabelPositionTransitionLabel
)
val labelOffsetAnimation by labelPositionTransition.animateOffset(
transitionSpec = {
tween(
durationMillis = LabelOffsetAnimationDuration
)
},
label = LabelOffsetAnimationLabel
) { position ->
when (position) {
LabelPosition.TopLeft -> Offset(
LabelAnimationTopLeftPositionOffsetX,
LabelAnimationTopLeftPositionOffsetY
)
LabelPosition.Center -> Offset(
LabelAnimationCenterPositionOffsetX,
LabelAnimationCenterPositionOffsetY
)
}
}
val labelTextSizeAnimation by labelPositionTransition.animateFloat(
label = LabelTextSizeAnimationLabel
) { position ->
when (position) {
LabelPosition.TopLeft -> LabelTextSizeAnimationTopLeftPosition
LabelPosition.Center -> LabelTextSizeAnimationCenterPosition
}
}
val labelBackgroundColorAnimation by labelPositionTransition.animateColor(
transitionSpec = {
tween(
durationMillis = LabelBackgroundColorAnimationDuration,
delayMillis = LabelBackgroundColorAnimationDelay,
)
},
label = LabelBackgroundColorAnimationLabel
) { position ->
when (position) {
LabelPosition.TopLeft -> Color.White
LabelPosition.Center -> Color.Transparent
}
}
val textFieldIsFocusedTransition = updateTransition(
targetState = textFieldIsFocused,
label = TextFieldIsFocusedTransitionLabel
)
val textFieldBackgroundColorAnimation by textFieldIsFocusedTransition.animateColor(
transitionSpec = { ->
tween(
durationMillis = TextFieldBackgroundColorAnimationDuration,
delayMillis = TextFieldBackgroundColorAnimationDelay,
)
},
label = TextFieldBackgroundColorAnimationLabel
) { _isFocused ->
when {
_isFocused -> LinkWater
!_isFocused && textFieldState.text.isEmpty() -> Color.Transparent
!_isFocused && textFieldState.text.isNotEmpty() -> Alabaster
else -> Color.Transparent
}
}
val borderColorAnimation by textFieldIsFocusedTransition.animateColor(
transitionSpec = { ->
tween(
durationMillis = BorderColorAnimationDuration,
delayMillis = BorderColorAnimationDelay,
)
},
label = BorderColorAnimationLabel
) { _isFocused ->
when {
_isFocused -> Color.Transparent
!_isFocused && textFieldState.text.isEmpty() -> Mercury
!_isFocused && textFieldState.text.isNotEmpty() -> Color.Transparent
else -> Mercury
}
}
val textFieldBoxModifier = Modifier.textFieldBoxModifier(
textFieldState = textFieldState,
textFieldBackgroundColorAnimation = textFieldBackgroundColorAnimation,
borderColorAnimation = borderColorAnimation,
textFieldIsFocused = textFieldIsFocused
)
Box(
contentAlignment = Alignment.CenterStart
) {
Text(
modifier = Modifier
.zIndex(LabelBubbleZIndex)
.defaultMinSize(1.dp)
.offset(labelOffsetAnimation.x.dp, labelOffsetAnimation.y.dp)
.clip(
shape = CircleShape
)
.background(
color = labelBackgroundColorAnimation
)
// Padding inside the Email bubble
.padding(
start = if (labelPosition == LabelPosition.TopLeft) 8.dp else 0.dp,
end = if (labelPosition == LabelPosition.TopLeft) 8.dp else 0.dp,
top = 2.dp,
bottom = 2.dp
),
text = label,
fontSize = dpToSp(
labelTextSizeAnimation,
LocalContext.current
).sp,
style = LocalTextStyle.current.copy(
color = OsloGray
),
)
Box(
modifier = textFieldBoxModifier
) {
TextField(
modifier = Modifier
.padding(
start = 8.dp,
end = 8.dp
)
.onFocusChanged {
textFieldIsFocused = it.isFocused
labelPosition =
getTargetLabelPosition(textFieldIsFocused, textFieldState.text)
textFieldState.enableDisplayErrors(textFieldIsFocused)
}
.defaultMinSize(
minWidth = 1.dp
),
value = textFieldState.text,
onValueChange = onValueChange,
enabled = enabled,
keyboardOptions = keyboardOptions,
readOnly = readOnly,
isError = isError,
keyboardActions = KeyboardActions(
keyboardAction
),
trailingIcon = trailingIcon,
singleLine = true,
textStyle = TextFieldStyle,
colors = TextFieldDefaults.textFieldColors(
textColor = Fiord,
cursorColor = Fiord,
disabledTextColor = Color.Transparent,
backgroundColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
errorIndicatorColor = Color.Transparent
)
)
}
}
textFieldState.getError()?.let { error ->
TextFieldError(
textError = error
)
}
}
}
private fun Modifier.textFieldBoxModifier(
textFieldState: TextFieldState,
textFieldBackgroundColorAnimation: Color,
borderColorAnimation: Color,
textFieldIsFocused: Boolean
): Modifier {
var basicTextFieldBoxModifier = this
.padding(
start = 4.dp,
end = 4.dp,
top = 8.dp,
bottom = 8.dp
)
.height(60.dp)
.background(
color = textFieldBackgroundColorAnimation,
shape = CircleShape
)
.border(1.dp, borderColorAnimation, CircleShape)
.zIndex(1f)
.wrapContentSize(
align = Alignment.CenterStart
)
when {
textFieldIsFocused -> {
basicTextFieldBoxModifier = basicTextFieldBoxModifier.then(Modifier.fillMaxWidth())
}
!textFieldIsFocused && textFieldState.text.isEmpty() -> {
basicTextFieldBoxModifier =
basicTextFieldBoxModifier.then(Modifier.width(TextFieldMinWidth))
}
}
return basicTextFieldBoxModifier
}
Upvotes: 1