You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中实现带特定聚焦/失焦样式的自定义TextField

Jetpack Compose 自定义带悬浮提示的TextField组件实现方案

组件样式说明

  • 失焦状态:提示文字位于输入框内部,边框为浅灰色细边框,无高亮效果
  • 聚焦状态:提示文字缩小上浮到输入框顶部边框位置,边框切换为主题高亮色,宽度加粗

核心实现思路

选择BasicTextField作为底层基础组件,相比封装度高的Material TextField,自定义样式的自由度更高,核心需要监听两个状态:输入框的焦点状态、输入文本的内容状态,配合属性动画实现提示文字的过渡效果。

具体实现步骤

  • 定义组件入参,预留自定义配置入口,支持传入提示文本、失焦边框颜色、聚焦边框颜色、文字样式、是否可编辑、文本变更回调等参数,适配不同业务场景
  • 监听焦点状态,通过FocusRequester配合Modifier.onFocusChanged捕获输入框的焦点变更,更新当前是否聚焦的状态变量
  • 绘制容器与边框,外层容器预留顶部padding给上浮的提示文字,根据焦点状态动态切换边框的颜色和宽度,圆角可以根据需求自定义
  • 实现提示文字的悬浮动画,用animateDpAsState实现提示文字的Y轴位移动画,animateFloatAsState实现文字大小的缩放动画:当输入框聚焦或者文本不为空时,提示文字上浮到顶部边框、缩小字号;失焦且文本为空时回到输入框内部、恢复原字号
  • 嵌入输入文本区域,调整BasicTextField的内边距,避免输入文本和上浮的提示文字重叠,配置键盘类型、单行/多行等基础属性

示例代码

@Composable
fun CustomOutlinedTextField(
    hint: String,
    text: String,
    onTextChange: (String) -> Unit,
    modifier: Modifier = Modifier,
    focusedBorderColor: Color = MaterialTheme.colors.primary,
    unfocusedBorderColor: Color = Color.LightGray,
    borderWidth: Dp = 1.dp,
    focusedBorderWidth: Dp = 2.dp,
    textStyle: TextStyle = LocalTextStyle.current,
    hintStyle: TextStyle = textStyle.copy(color = Color.Gray),
    cornerRadius: Dp = 8.dp
) {
    val focusRequester = remember { FocusRequester() }
    var isFocused by remember { mutableStateOf(false) }
    // 提示文字动画参数
    val hintOffsetY by animateDpAsState(
        targetValue = if (isFocused || text.isNotEmpty()) (-12).dp else 0.dp,
        label = "hintOffsetY"
    )
    val hintScale by animateFloatAsState(
        targetValue = if (isFocused || text.isNotEmpty()) 0.8f else 1f,
        label = "hintScale"
    )
    val currentBorderColor by animateColorAsState(
        targetValue = if (isFocused) focusedBorderColor else unfocusedBorderColor,
        label = "borderColor"
    )
    val currentBorderWidth by animateDpAsState(
        targetValue = if (isFocused) focusedBorderWidth else borderWidth,
        label = "borderWidth"
    )

    Box(
        modifier = modifier
            .fillMaxWidth()
            .border(
                width = currentBorderWidth,
                color = currentBorderColor,
                shape = RoundedCornerShape(cornerRadius)
            )
            .padding(horizontal = 16.dp, vertical = 12.dp)
            .focusRequester(focusRequester)
            .onFocusChanged { isFocused = it.isFocused }
            .clickable { focusRequester.requestFocus() }
    ) {
        // 提示文字:背景和页面背景一致,遮盖边框实现嵌入效果
        Text(
            text = hint,
            style = hintStyle,
            modifier = Modifier
                .align(Alignment.CenterStart)
                .graphicsLayer {
                    translationY = hintOffsetY.toPx()
                    scaleX = hintScale
                    scaleY = hintScale
                }
                .background(MaterialTheme.colors.surface)
                .padding(horizontal = 4.dp)
        )
        // 输入文本区域
        BasicTextField(
            value = text,
            onValueChange = onTextChange,
            modifier = Modifier
                .fillMaxWidth()
                .padding(top = 8.dp),
            textStyle = textStyle,
            singleLine = true
        )
    }
}

使用示例

@Composable
fun UsageDemo() {
    var inputText by remember { mutableStateOf("") }
    CustomOutlinedTextField(
        hint = "请输入内容",
        text = inputText,
        onTextChange = { inputText = it },
        modifier = Modifier.padding(16.dp)
    )
}

内容的提问来源于stack exchange,提问作者Saeed Noshadi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 08:06:01