Search code examples
kotlinandroid-jetpack-composeoutlined-text-fields

The label position of OutlinedTextFieldDefaults.DecorationBox is wrong


I try to use OutlinedTextFieldDefaults.DecorationBox to achieve such an effect: a frame surrounded by OutlinedTextField border style, which can accommodate any compose component inside. But when I tried to implement it, I found that the position of label was incorrect. I am not sure which parameter is affected by this. I'd like to fix this so that the label is in the correct position, or use another method to achieve a similar effect.

@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun ShowTagArea (
    paddingValue: PaddingValues,
    showTags: List<Tag>,
    isSelected: Boolean,
) {
    OutlinedTextFieldDefaults.DecorationBox(
        contentPadding = paddingValue,
        value = "value",
        innerTextField = {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
            ) {
                FlowRow(
                    horizontalArrangement = Arrangement.spacedBy(8.dp),
                    modifier = Modifier
                        .padding(10.dp)
                        .fillMaxWidth()
                ) {
                    showTags.forEach { tag ->
                        FilterChip(
                            selected = isSelected,
                            onClick = {},
                            label = { Text(text = tag.tagName) },
                        )
                    }
                }
            }
        },
        singleLine = true,
        enabled = true,
        label = { Text("Label") },
        placeholder = {},
        visualTransformation = VisualTransformation.None,
        interactionSource = remember { MutableInteractionSource() },
        colors = OutlinedTextFieldDefaults.colors(),
        container = {
            OutlinedTextFieldDefaults.Container(
                modifier = Modifier.padding(paddingValue),
                enabled = true,
                isError = false,
                interactionSource = remember { MutableInteractionSource() },
            )
        }
    )
}

@Composable
@Preview(showBackground = true)
fun FileDetailView() {
    LazyColumn (
        modifier = Modifier.fillMaxWidth()
    ) {
        item {
            OutlinedTextField(
                value = "2023.6.10 10:10:10",
                label = { Text(text = "Latest Modify Time") },
                onValueChange = {},
                readOnly = true,
                modifier = Modifier.fillMaxWidth().padding(5.dp)
            )
        }
        item {
            ShowTagArea(
                paddingValue = PaddingValues(5.dp),
                showTags = data.tags,
                isSelected = true
            )
        }
    }
}

preview

I expected that label will be aligned with "Latest Modify Time" label.


Solution

  • Wrap DecorationBox in a Box and apply paddingValue to that Box instead of anywhere inside of the DecorationBox:

    @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
    @Composable
    private fun ShowTagArea(
        paddingValue: PaddingValues,
        showTags: List<Tag>,
        isSelected: Boolean,
    ) {
        Box(
            modifier = Modifier.padding(paddingValue)
        ) {
            OutlinedTextFieldDefaults.DecorationBox(
    //            contentPadding = paddingValue,
                value = "value",
                innerTextField = {
                    Box(
                        modifier = Modifier
                            .fillMaxWidth()
                    ) {
                        FlowRow(
                            horizontalArrangement = Arrangement.spacedBy(8.dp),
                            modifier = Modifier
    //                        .padding(10.dp)
                                .fillMaxWidth()
                        ) {
                            showTags.forEach { tag ->
                                FilterChip(
                                    selected = isSelected,
                                    onClick = {},
                                    label = { Text(text = tag.tagName) },
                                )
                            }
                        }
                    }
                },
                singleLine = true,
                enabled = true,
                label = { Text("Label") },
                placeholder = {},
                visualTransformation = VisualTransformation.None,
                interactionSource = remember { MutableInteractionSource() },
                colors = OutlinedTextFieldDefaults.colors(),
                container = {
                    OutlinedTextFieldDefaults.Container(
    //                modifier = Modifier.padding(paddingValue),
                        enabled = true,
                        isError = false,
                        interactionSource = remember { MutableInteractionSource() },
                    )
                }
            )
        }
    }
    

    Edit:

    Alternatively, do not add any padding to the individual items, set padding and vertical spacing for the LazyColumn instead :

    LazyColumn(
        contentPadding = PaddingValues(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
    

    Explanation:

    The reason for label being misaligned with the border is the padding being added to OutlinedTextFieldDefaults.Container. That container is essentially is a Box with a border, so padding adds space along the border only and doesn't affect label.

    There is also no reason to set contentPadding parameter of DecorationBox. This parameter has default value by the m3 specs and it also affects the label cutout position, better leave it as it is. I have also removed FlowRow padding because there is padding around it already (contentPadding param).

    After all those paddings removed, the label is aligned with the border, but there is no padding around ShowTagArea and there is no modifier parameter in DecorationBox to add padding. We can fix it by wrapping DecorationBox in a layout composable like Box and adding padding to this container. Or we could let the outer container (LazyColumn) handle all the spacing and padding for us.