I'm building a screen in Jetpack Compose where I want a BasicTextField
to be "active" as soon as the screen loads. By "active," I mean that the label should animate upward (just like it does when the field is focused) and the placeholder should be visible. However, I do not want the blinking cursor to appear until the user actually interacts with the BasicTextField
.
Currently, my implementation auto-focuses the BasicTextField on launch using a FocusRequester
, but this approach also brings up the cursor immediately, which is not the desired behavior.
Here’s a simplified version of my current code:
MainActivity.kt
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activitypose.setContent
import androidx.activity.enableEdgeToEdge
import androidxpose.foundation.interaction.MutableInteractionSource
import androidxpose.foundation.layout.Column
import androidxpose.foundation.layout.fillMaxSize
import androidxpose.foundation.layout.fillMaxWidth
import androidxpose.foundation.layout.height
import androidxpose.foundation.layout.padding
import androidxpose.foundation.text.BasicTextField
import androidxpose.foundation.text.input.TextFieldLineLimits
import androidxpose.foundation.text.input.TextFieldState
import androidxpose.foundation.text.input.rememberTextFieldState
import androidxpose.material3.ExperimentalMaterial3Api
import androidxpose.material3.Text
import androidxpose.material3.TextFieldDefaults
import androidxpose.runtime.Composable
import androidxpose.runtime.remember
import androidxpose.ui.Modifier
import androidxpose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val stateOne = rememberTextFieldState(initialText = "Hellow")
val stateTwo = rememberTextFieldState()
BasicTextFieldExamples(
stateOne,
remember { MutableInteractionSource() },
stateTwo,
remember { MutableInteractionSource() },
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BasicTextFieldExamples(
stateOne: TextFieldState,
firstInteractionSource: MutableInteractionSource,
stateOTwo: TextFieldState,
secondInteractionSource: MutableInteractionSource,
) {
Column(
Modifier
.fillMaxSize()
.padding(50.dp)
) {
BasicTextField(
state = stateOne,
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
readOnly = true,
interactionSource = firstInteractionSource,
decorator = TextFieldDefaults.decorator(
state = stateOne,
enabled = true,
label = {
Text("First Name")
},
placeholder = {
Text("Example 1")
},
lineLimits = TextFieldLineLimits.Default,
interactionSource = firstInteractionSource,
outputTransformation = null
)
)
BasicTextField(
state = stateOTwo,
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
interactionSource = secondInteractionSource,
decorator = TextFieldDefaults.decorator(
state = stateOTwo,
enabled = true,
label = {
Text("Last Name")
},
placeholder = {
Text("Example 2")
},
lineLimits = TextFieldLineLimits.Default,
interactionSource = secondInteractionSource,
outputTransformation = null
)
)
}
}
}
I'm building a screen in Jetpack Compose where I want a BasicTextField
to be "active" as soon as the screen loads. By "active," I mean that the label should animate upward (just like it does when the field is focused) and the placeholder should be visible. However, I do not want the blinking cursor to appear until the user actually interacts with the BasicTextField
.
Currently, my implementation auto-focuses the BasicTextField on launch using a FocusRequester
, but this approach also brings up the cursor immediately, which is not the desired behavior.
Here’s a simplified version of my current code:
MainActivity.kt
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val stateOne = rememberTextFieldState(initialText = "Hellow")
val stateTwo = rememberTextFieldState()
BasicTextFieldExamples(
stateOne,
remember { MutableInteractionSource() },
stateTwo,
remember { MutableInteractionSource() },
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BasicTextFieldExamples(
stateOne: TextFieldState,
firstInteractionSource: MutableInteractionSource,
stateOTwo: TextFieldState,
secondInteractionSource: MutableInteractionSource,
) {
Column(
Modifier
.fillMaxSize()
.padding(50.dp)
) {
BasicTextField(
state = stateOne,
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
readOnly = true,
interactionSource = firstInteractionSource,
decorator = TextFieldDefaults.decorator(
state = stateOne,
enabled = true,
label = {
Text("First Name")
},
placeholder = {
Text("Example 1")
},
lineLimits = TextFieldLineLimits.Default,
interactionSource = firstInteractionSource,
outputTransformation = null
)
)
BasicTextField(
state = stateOTwo,
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
interactionSource = secondInteractionSource,
decorator = TextFieldDefaults.decorator(
state = stateOTwo,
enabled = true,
label = {
Text("Last Name")
},
placeholder = {
Text("Example 2")
},
lineLimits = TextFieldLineLimits.Default,
interactionSource = secondInteractionSource,
outputTransformation = null
)
)
}
}
}
Share
Improve this question
edited Feb 5 at 20:46
Vivek Modi
asked Feb 5 at 18:03
Vivek ModiVivek Modi
7,19118 gold badges101 silver badges224 bronze badges
7
|
Show 2 more comments
1 Answer
Reset to default 1I think this might be what you need, using TextField
rather than BasicTextField
. You can customize the cursor colors via the provided TextFieldColors
, so you can separately maintain a state that decides whether to show or hide the cursor, and hide it by setting the colors to transparent.
val focusRequester = remember { FocusRequester() }
LaunchedEffect("focus") { focusRequester.requestFocus() }
var showCursor by remember { mutableStateOf(false) }
val colors =
if (showCursor) {
TextFieldDefaults.colors()
} else {
TextFieldDefaults.colors()
.copy(cursorColor = Color.Transparent, errorCursorColor = Color.Transparent)
}
var text by remember { mutableStateOf("") }
TextField(
value = text,
colors = colors,
onValueChange = { text = it },
label = { Text(text = "Label") },
placeholder = { Text(text = "Placeholder") },
modifier =
Modifier.focusRequester(focusRequester).pointerInput("key") {
awaitPointerEventScope {
awaitFirstDown(requireUnconsumed = false)
showCursor = true
}
})
TextField()
and implement what you want. – CommonsWare Commented Feb 5 at 18:37TextFieldDefaults.TextFieldDecorationBox()
and use that for thedecorationBox
. That, in turn, is based onCommonDecorationBox()
, and you would need to change the placeholder behavior. – CommonsWare Commented Feb 5 at 19:15TextField()
for Material3. The placeholder and label are controlled byTextFieldDefaults.decorator()
. – CommonsWare Commented Feb 5 at 20:55