Compose-自定义输入框(BasicTextField)

本文档展示了如何在Jetpack Compose中自定义BasicTextField,添加左侧图标、右侧删除按钮以及hint功能。提供了完整的自定义组件代码,并给出了实际使用示例,包括手机号和验证码输入框。此外,还提供了一个仅包含hint的多行输入框实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

Compose 中的 TextField()material 风格的输入框, 只得把 BasicTextField 自定义一下。
BasicTextField 的参数这里就不介绍了, 网上介绍的文章已经不少了.

先上图:
在这里插入图片描述
自定义的内容有:

  1. 左侧的小图标
  2. 右侧的删除按钮
  3. hint 空字符时的提示

一、上代码

/**
 * @param hint: 空字符时的提示
 * @param startIcon: 左侧图标;  -1 则不显示
 * @param iconSpacing: 左侧图标与文字的距离; 相当于: drawablePadding
 */
@Composable
fun CustomEdit(
  text: String = "",
  onValueChange: (String) -> Unit,
  modifier: Modifier,
  hint: String = "请输入",
  @DrawableRes startIcon: Int = -1,
  iconSpacing: Dp = 6.dp,

  enabled: Boolean = true,
  readOnly: Boolean = false,
  textStyle: TextStyle = TextStyle.Default,
  keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
  keyboardActions: KeyboardActions = KeyboardActions.Default,
  visualTransformation: VisualTransformation = VisualTransformation.None,
  cursorBrush: Brush = SolidColor(MaterialTheme.colors.primary)
) {
  // 焦点, 用于控制是否显示 右侧叉号
  var hasFocus by remember { mutableStateOf(false) }

  BasicTextField(
    value = text,
    onValueChange = onValueChange,
    modifier = modifier.onFocusChanged { hasFocus = it.isFocused },
    singleLine = true,
    enabled = enabled,
    readOnly = readOnly,
    textStyle = textStyle,
    keyboardOptions = keyboardOptions,
    keyboardActions = keyboardActions,
    visualTransformation = visualTransformation,
    cursorBrush = cursorBrush,
    decorationBox = @Composable { innerTextField ->
      Row(
        modifier = Modifier.fillMaxWidth(),
        verticalAlignment = Alignment.CenterVertically
      ) {
        // -1 不显示 左侧Icon
        if(startIcon != -1){
          Image(painter = painterResource(id = startIcon), contentDescription = null)
          Spacer(modifier = Modifier.width(iconSpacing))
        }

        Box(modifier = Modifier.weight(1f)){
          // 当空字符时, 显示hint
          if(text.isEmpty())
            Text(text = hint, color = GrayAa, style = textStyle)

          // 原本输入框的内容
          innerTextField()
        }

        // 存在焦点 且 有输入内容时. 显示叉号
        if(hasFocus && text.isNotEmpty()) {
          Image(imageVector = Icons.Filled.Clear, // 清除图标
            contentDescription = null,
            // 点击就清空text
            modifier = Modifier.clickable { onValueChange.invoke("") })
        }
      }
    }
  )
}

二、使用

// vm : ViewModel; 
// 手机号输入框
CustomEdit(
  text = vm.phone,
  onValueChange = {
    vm.phone = it
  },
  modifier = Modifier
    .fillMaxWidth()
    .padding(start = 16.dp, top = 20.dp, end = 16.dp)
    .height(50.dp)
    .background(GrayE5, shape = MaterialTheme.shapes.medium)
    .padding(horizontal = 16.dp),
  hint = "请输入手机号",
  startIcon = R.drawable.ic_login_phone,
  iconSpacing = 16.dp,
  textStyle = Typography.body1,
  // keyboardType -> 半个 inputType; 这里是 Phone
  keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
)

// code 输入框
CustomEdit(
  text = vm.code,
  onValueChange = {
    vm.code = it
  },
  modifier = Modifier
    .fillMaxSize()
    .background(GrayE5, shape = MaterialTheme.shapes.medium)
    .padding(horizontal = 16.dp),
  hint = "请输入验证码",
  startIcon = R.drawable.ic_login_pwd,
  iconSpacing = 16.dp,
  textStyle = Typography.body1,
  keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
)

三、只有hint的 BasicTextField

1. 自定义CustomEditHint

/**
 * 只增加 hint;  适合多行输入框, 没有前后Icon的小输入框; 背景 尺寸等 定义在modifier中;
 * @param hint: 空字符时的提示
 */
@Composable
fun CustomEditHint(
  text: String = "",
  onValueChange: (String) -> Unit,
  modifier: Modifier,
  hint: String = "请输入",

  enabled: Boolean = true,
  readOnly: Boolean = false,
  singleLine: Boolean = false,
  textStyle: TextStyle = TextStyle.Default,
  keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
  keyboardActions: KeyboardActions = KeyboardActions.Default,
  visualTransformation: VisualTransformation = VisualTransformation.None,
  cursorBrush: Brush = SolidColor(MaterialTheme.colors.primary)
) {
  // 焦点, 用于控制是否显示 右侧叉号
  BasicTextField(
    value = text,
    onValueChange = onValueChange,
    modifier = modifier,
    singleLine = singleLine,
    enabled = enabled,
    readOnly = readOnly,
    textStyle = textStyle,
    keyboardOptions = keyboardOptions,
    keyboardActions = keyboardActions,
    visualTransformation = visualTransformation,
    cursorBrush = cursorBrush,
    decorationBox = @Composable { innerTextField ->
      if(text.isEmpty())
        Text(text = hint, color = GrayAa, style = textStyle)
      innerTextField()
    }
  )
}

2.Preview

@Preview(showBackground = true)
@Composable
fun TextFieldViewPreview() {
  val scrollState = rememberScrollState()
  Column(
    modifier = Modifier.verticalScroll(scrollState)
  ) {
    var text by remember { mutableStateOf("") }
	// 带前icon 及 后清除icon 的输入框
    CustomEdit(
      text = text,
      onValueChange = {
        text = it.trim()
      },
      modifier = Modifier
        .fillMaxWidth()
        .padding(start = 16.dp, top = 20.dp, end = 16.dp)
        .height(50.dp)
        .background(GrayE5, shape = MaterialTheme.shapes.medium)
        .padding(horizontal = 16.dp),
      hint = "请输入手机号",
      iconSpacing = 16.dp,
      textStyle = TextDark16,
      keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
      keyboardActions = KeyboardActions(onSearch = {
        ComApplication.INSTANCES.toast("搜索:$text")
      }),
    )

	// 多行输入框
    CustomEditHint(
      text = text,
      onValueChange = {
        text = it.trim()
      },
      modifier = Modifier
        .fillMaxWidth()
        .padding(top = 16.dp)
        .height(200.dp)
        .background(GrayE5)
        .padding(horizontal = 14.dp, vertical = 12.dp),
      hint = "请输入详情",
    )
	
	// 小输入框
    var text2 by remember { mutableStateOf("") }
    Box(
      modifier = Modifier
        .fillMaxWidth()
        .background(Color.White)
        .padding(14.dp)
    ){
      Text(text = "联系方式", style = TextDarkBlod14)

      CustomEditHint(
        text = text2,
        onValueChange = {
          text2 = it.trim().let { str ->
            // maxLength = 15
            if(str.length > 15){
              str.substring(0, 15)
            } else {
              str
            }
          }
        },
        modifier = Modifier
          .align(Alignment.CenterEnd)
          .defaultMinSize(100.dp, 14.dp),
        hint = "手机或邮箱都可以哦",
        singleLine = true,
        textStyle = TextStyle.Default.copy(textAlign = TextAlign.End)
      )
    }
  }
}

总结

没有总结!

上一篇: 记一次自定义View:滑动标尺
下一篇: Compose-navigation: 问题汇总

<think>嗯,用户问的是Jetpack Compose中光标不工作的问题以及如何正确使用。首先,我需要回忆一下Compose处理文本输入的相关组件。记得在Compose中,TextField和TextFieldValue是关键。传统Android View的EditText在Compose里被替代了,所以可能用户还在用旧的方法导致光标无效。 然后,用户可能没有正确管理状态。Compose是声明式的,状态变化驱动UI更新。如果用户没有使用remember来保存TextField的状态,可能会导致光标问题,因为每次重组都会重置状态。比如,他们可能直接在TextField里用了字符串,而不是TextFieldValue,而后者包含光标位置的信息。 另外,光标的行为可能受到输入参数的影响,比如visualTransformation或者键盘选项。例如,如果设置了keyboardOptions的imeAction,但没有正确处理onImeAction,可能会导致光标行为异常。还有,用户可能没有正确应用Modifier,比如focusRequester来管理焦点,导致光标无法显示或移动。 还有可能,用户遇到的是Compose本身的bug,或者版本问题。不同版本的Compose可能在光标处理上有差异,需要检查是否使用了最新稳定版,或者是否有已知问题。 接下来,正确使用的方法应该包括使用TextField或OutlinedTextField,配合TextFieldValue和onValueChange来更新状态,同时用remember保存状态。处理焦点时,可能需要使用FocusRequester和Modifier.focusRequester()来主动请求焦点,确保光标显示。此外,注意键盘选项和视觉变换是否正确设置,避免干扰光标行为。 还要参考官方文档和社区的最佳实践,比如在输入框外点击时获取焦点,或者通过状态控制光标位置。例如,当需要动态设置光标位置时,可以通过修改TextFieldValue中的selection属性来实现。 最后,可能需要提醒用户检查Compose版本,确保没有已知的bug,并参考官方示例代码来调整他们的实现方式。</think>在Jetpack Compose中光标(Cursor)无效的问题通常与状态管理和焦点控制相关。以下是具体原因及解决方案: ### 一、光标无效的常见原因 1. **未正确使用`TextFieldValue`状态** Compose的`TextField`要求使用`TextFieldValue`类型管理文本状态,包含光标位置信息。直接使用`String`类型会导致光标无法定位: ```kotlin var text by remember { mutableStateOf(TextFieldValue("")) } // 正确方式 ``` 2. **焦点未正确获取** 需要通过`FocusRequester`主动请求焦点才能显示光标: ```kotlin val focusRequester = remember { FocusRequester() } TextField( value = text, onValueChange = { text = it }, modifier = Modifier.focusRequester(focusRequester) ) LaunchedEffect(Unit) { focusRequester.requestFocus() } // 自动获取焦点 ``` 3. **键盘配置冲突** 未正确处理输入法动作可能导致光标行为异常: ```kotlin KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { /* 处理完成动作 */ }) ``` ### 二、正确实现光标功能的步骤 1. **基本实现模式** ```kotlin var text by remember { mutableStateOf(TextFieldValue("初始文本")) } TextField( value = text, onValueChange = { newValue -> text = newValue // 必须更新包含光标位置的状态 }, modifier = Modifier.fillMaxWidth() ) ``` 2. **动态控制光标位置** 通过修改`TextFieldValue.selection`属性设置光标位置: ```kotlin text = text.copy(selection = TextRange(3)) // 将光标定位到第4个字符后 ``` 3. **多输入框焦点切换** 使用多个`FocusRequester`实现输入框间焦点切换: ```kotlin val (first, second) = remember { FocusRequester.createRefs() } TextField(..., modifier = Modifier.focusRequester(first)) TextField(..., modifier = Modifier.focusRequester(second)) Button(onClick = { second.requestFocus() }) // 点击按钮切换焦点 ``` ### 三、最佳实践建议 1. 对于复杂表单,建议使用`ViewModel`集中管理文本状态[^3] 2. 使用`BasicTextField`实现高度自定义输入框时,需手动处理光标绘制逻辑 3. 组合使用`onFocusChanged`监听器实现动态UI响应: ```kotlin Modifier.onFocusChanged { focusState -> if (focusState.isFocused) showKeyboard() } ``` > 注意:Jetpack Compose 1.2.0之后优化了光标闪烁频率控制,可通过`LocalTextStyle`调整视觉效果[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值