Jetpack Compose文本框如何去除默认填充?

64

我想要自定义 Jetpack Compose 中的 TextField 组件。我正试图实现下面图片中的效果,但是 TextField 似乎有一些默认填充值,我找不到如何更改这些值。我想要移除默认填充并进行自定义。

(右侧图片是我实现的结果。我画了一个边框,以便您可以看到它有填充。顺便说一句,在那个TextField 下面只有Text组件,它们不是TextFields)

enter image description here enter image description here

下面是我的TextField代码:

TextField(
    value = "",
    onValueChange = {},
    modifier = Modifier
        .weight(1F)
        .padding(0.dp)
        .border(width = 1.dp, color = Color.Red),
    placeholder = {
        Text(
            "5555 5555 5555 5555", style = TextStyle(
                color = Color.Gray
            )
        )
    },
    colors = TextFieldDefaults.textFieldColors(
        backgroundColor = Color.Transparent,
        unfocusedIndicatorColor = Color.Transparent,
        focusedIndicatorColor = Color.Transparent
    ),
)
10个回答

48

您可以使用BasicTextField,它是一个没有任何装饰的纯文本字段。请注意,它也没有占位符/提示,如果需要,您必须自己实现这些。

BasicTextField(value = "", onValueChange = {}, Modifier.fillMaxWidth())

自从1.2.0-alpha04以来,使您的BasicTextField看起来像TextFieldOutlinedTextField变得更加容易。 您可以复制TextField的源代码,这很短,因为大部分逻辑都移动到了TextFieldDefaults.TextFieldDecorationBox中,并将所需的填充值传递给TextFieldDefaults.TextFieldDecorationBoxcontentPadding参数。


2
如果您需要为BasicTextField添加一个trailingIcon,该怎么办? - kc_dev
@kc_dev 将TextFieldDefaults.TextFieldDecorationBox作为decorationBox参数传递,其中包含leadingIcontrailingIcon参数。 - Eyjafl
1
复制源代码是不好的做法。如果这是唯一的解决方案,那么就没有解决方案了。 - Marty Miller

38
在最新的alpha版本中(androidx.compose.material:material:1.2.0-alpha04),他们暴露了TextFieldDefaults.TextFieldDecorationBox
这是材料TextField实现中使用的decorationBox composable的实现。您可以按以下方式使用它:
val interactionSource = remember { MutableInteractionSource() }
BasicTextField(
    value = value,
    onValueChange = onValueChange,
    modifier = modifier,
    visualTransformation = visualTransformation,
    interactionSource = interactionSource,
    enabled = enabled,
    singleLine = singleLine,
) { innerTextField ->
    TextFieldDefaults.TextFieldDecorationBox(
        value = value,
        visualTransformation = visualTransformation,
        innerTextField = innerTextField,
        singleLine = singleLine,
        enabled = enabled,
        interactionSource = interactionSource,
        contentPadding = PaddingValues(0.dp), // this is how you can remove the padding
    )
}

这将允许您删除填充,但仍然获得TextField的其余特性。

记得为BasicTextFieldTextFieldDefaults.TextFieldDecorationBox使用相同的MutableInteractionSource

我上面提供的官方文档展示了更多使用示例。


现在它已被弃用。 - user924
1
不确定您所指的是什么,但是最新的稳定版(1.4.3)和最新的 alpha 版本(1.5.0-alpha04)中,BasicTextField 组合函数和 TextFieldDefaults.TextFieldDecorationBox 组合函数均未被弃用。BasicTextField 组合函数已更新以包含 minLines 参数,并且原始的 BasicTextField 函数已被弃用,以鼓励使用新函数,但由于二进制兼容性原因尚未删除。上述代码与新函数签名完全兼容。 - jeran

14

谢谢大家,我使用了 BasicTextField 并且达到了我想要的结果 :)

@Composable
fun BottomOutlineTextField(placeholder: String, value: String, onValueChange: (String) -> Unit) {

    BasicTextField(
        modifier = Modifier.fillMaxWidth(),
        value = value,
        onValueChange = onValueChange,
        textStyle = TextStyle(
            color = if (isSystemInDarkTheme()) Color(0xFF969EBD) else Color.Gray
        ),
        decorationBox = { innerTextField ->
            Row(modifier = Modifier.fillMaxWidth()) {
                if (value.isEmpty()) {
                    Text(
                        text = placeholder,
                        color = if (isSystemInDarkTheme()) Color(0xFF969EBD) else Color.Gray,
                        fontSize = 14.sp
                    )
                }
            }
            innerTextField()
        }
    )
}

恭喜! - Richard Onslow Roper
1
那么你不应该将菲利普的答案标记为正确答案吗? - Richard Onslow Roper
这个解决方案非常好,但是另一个问题出现了,焦点底部指示器没有出现。 - Mário Henrique

4

我希望在开头减去16.dp。我通过以下方式实现了这一点:

BoxWithConstraints(modifier = Modifier
          .clipToBounds()
) {
    TextField(modifier = Modifier
        .requiredWidth(maxWidth+16.dp)
        .offset(x=(-8).dp))
}

1
我通过复制TextField的所有源代码并替换以下代码行来解决了这个问题:
              val paddingToIcon = TextFieldPadding - HorizontalIconPadding
//            val padding = Modifier.padding(
//                start = if (leading != null) paddingToIcon else TextFieldPadding,
//                end = if (trailing != null) paddingToIcon else TextFieldPadding
//            )
            val padding = Modifier.padding(
                start = if (leading != null) paddingToIcon else 0.dp,
                end = if (trailing != null) paddingToIcon else 0.dp
            )

而且这个非常好用!


2
你真的知道被问了什么吗? - Kishan Solanki

1

很简单,只需复制所有TextFieldOutlinedTextField函数并删除defaultMinSize,然后添加contentPadding

之后,您可以使用它来替代主要的TextFieldOutlinedTextField

对于OutlinedTextField

@Composable
fun BorderTextField(
    value: String,
    onValueChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    readOnly: Boolean = false,
    textStyle: TextStyle = LocalTextStyle.current,
    label: @Composable (() -> Unit)? = null,
    placeholder: @Composable (() -> Unit)? = null,
    leadingIcon: @Composable (() -> Unit)? = null,
    trailingIcon: @Composable (() -> Unit)? = null,
    isError: Boolean = false,
    visualTransformation: VisualTransformation = VisualTransformation.None,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    keyboardActions: KeyboardActions = KeyboardActions.Default,
    singleLine: Boolean = false,
    maxLines: Int = Int.MAX_VALUE,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    shape: Shape = MaterialTheme.shapes.small,
    colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
) {
    // If color is not provided via the text style, use content color as a default
    val textColor = textStyle.color.takeOrElse {
        colors.textColor(enabled).value
    }
    val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
    @OptIn(ExperimentalMaterialApi::class)
    BasicTextField(
        value = value,
        modifier = if (label != null) {
            modifier
                // Merge semantics at the beginning of the modifier chain to ensure padding is
                // considered part of the text field.
                .semantics(mergeDescendants = true) {}
        } else {
            modifier
        }
            .background(colors.backgroundColor(enabled).value, shape),
        onValueChange = onValueChange,
        enabled = enabled,
        readOnly = readOnly,
        textStyle = mergedTextStyle,
        cursorBrush = SolidColor(colors.cursorColor(isError).value),
        visualTransformation = visualTransformation,
        keyboardOptions = keyboardOptions,
        keyboardActions = keyboardActions,
        interactionSource = interactionSource,
        singleLine = singleLine,
        maxLines = maxLines,
        decorationBox = @Composable { innerTextField ->
            TextFieldDefaults.OutlinedTextFieldDecorationBox(
                value = value,
                visualTransformation = visualTransformation,
                innerTextField = innerTextField,
                placeholder = placeholder,
                label = label,
                leadingIcon = leadingIcon,
                trailingIcon = trailingIcon,
                singleLine = singleLine,
                enabled = enabled,
                isError = isError,
                interactionSource = interactionSource,
                colors = colors,
                contentPadding = TextFieldDefaults.outlinedTextFieldPadding(
                    start = 0.dp,
                    top = 0.dp,
                    end = 0.dp,
                    bottom = 0.dp
                ),
                border = {
                    TextFieldDefaults.BorderBox(
                        enabled,
                        isError,
                        interactionSource,
                        colors,
                        shape
                    )
                }
            )
        }
    )
}

1
复制一个类从来都不是个好主意。这些类中的代码会随着新版本的更新而更新,你会错过这些变化。 - Marty Miller

0

使用

...
decorationBox{
     TextFieldDefaults.TextFieldDecorationBox(
          ....
     ) 

需要我添加注释@OptIn(ExperimentalMaterialApi::class),这似乎不是一个适合发布场景的好方法。 相反,我可以通过以下实现来获得我期望的结果:
@Composable
fun Input(
    text: TextFieldValue = TextFieldValue(),
    onValueChanged: (TextFieldValue) -> Unit = { },
    keyboardType: KeyboardType = KeyboardType.Text,
    imeAction: ImeAction = ImeAction.Done,
    isEnable: Boolean = true,
    singleLine: Boolean = true,
    shape: Shape = rounded,
    autoCorrect: Boolean = false,
    innerPadding: PaddingValues = PaddingValues(15.dp, 7.dp)
) {
    val focusManager = LocalFocusManager.current
    val fontSize = 16.sp

    BasicTextField(
        value = text,
        onValueChange = onValueChanged,
        Modifier
            .clip(shape)
            .border(1.dp, Color.Gray, shape)
            .background(Color.White),
        textStyle = TextStyle(color = Color.Black, fontSize = fontSize),
        enabled = isEnable,
        singleLine = singleLine,
        keyboardOptions = KeyboardOptions(
            KeyboardCapitalization.None,
            autoCorrect,
            keyboardType,
            imeAction
        ),
        keyboardActions = KeyboardActions(
            onAny = {
                focusManager.clearFocus()
            }
        ),
        decorationBox = {

            Box(
                modifier = Modifier.padding(innerPadding)
            ) {

                if(text.text.isBlank()) {
                    Text(
                        text = "Search",
                        style = TextStyle(color = Color.Black, fontSize = fontSize)
                    )
                }
                it()
            }
        }
    )
}

希望对某人有所帮助。

0
默认的`Text`组合中的`TextOverflow`值是`TextOverflow.Clip`。
如果您的文本居中,但顶部或底部被裁剪了,您可以使用以下示例进行更新:
placeholder = {
        Text(
            "5555 5555 5555 5555", style = TextStyle(
                color = Color.Gray
            ), overflow = TextOverflow.Visible
        )
    }

可视化表示示例:

Visual representation example


占位文本无法垂直对齐。我正在与SearchBar一起使用它 - undefined

-1

你可以把你的TextField放入一个Box中,并应用修饰符,例如:

Box(
        modifier = Modifier.background(
            shape = RoundedCornerShape(percent = 10),
            color = colorBackgroundGray
        )
    ) {
        TextField(
            value = text,
            onValueChange = onValueChange,
            modifier = Modifier.fillMaxWidth().padding(8.dp, 0.dp, 0.dp, 0.dp)...
}

1
如果您不需要下划线并相应地调整颜色,那么只需添加填充的解决方案非常好。 - Uscher

-4

实际上这是固有的,它遵循材料指南。如果您希望禁用它,另一种方法是显式设置TextField的高度,并将其与文本的字体大小匹配。这样它只会延伸到文本所在的位置。另一种方法是查看TextField的源代码。您可以复制源代码并进行修改以满足您的要求。前者听起来像一个简单的解决方案,但后者也不是什么大问题。它是可行的,并且是一种推荐的做法,以满足您的需求自定义行为。此外,作为一个旁注,我认为禁用该填充不是一个好主意。由于它似乎非常合理和自然,因此将其添加到设计指南中。有时候我们觉得某些设计在想象中很吸引人,但实现起来并不好。


不工作... - Nasyx Nadeem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接