I am trying to create a phone verification screen where the user must enter 5 numbers each in their own text field like below.
I have two questions:
Is there a way to limit a TextField to 1 character. I can set single line and max lines, but don't see a way to limit to character length like 'Ms' from the view system. I can easily limit the character length in code by ignoring characters after the first one, but this still lets the user 'scroll' to the left and right even though there is only 1 character.
Is there a way to wrap the width to the 1 character? Currently the only way I have found to limit the width is to set it specifically, but then if the system text size is changed it could break.
Here is some code incase it helps, this is some very jumbled together solution so apologies if something is incorrect:
#Composable
fun CodeTextFields(
modifier: Modifier = Modifier,
length: Int = 5,
onFilled: (code: String) -> Unit
) {
var code: List<Char> by remember {
mutableStateOf(listOf())
}
val focusRequesters: List<FocusRequester> = remember {
val temp = mutableListOf<FocusRequester>()
repeat(length) {
temp.add(FocusRequester())
}
temp
}
Row(modifier = modifier) {
(0 until length).forEach { index ->
OutlinedTextField(
modifier = Modifier
.weight(1f)
.padding(vertical = 2.dp)
.focusRequester(focusRequesters[index]),
textStyle = MaterialTheme.typography.h4.copy(textAlign = TextAlign.Center),
singleLine = true,
value = code.getOrNull(index)?.takeIf { it.isDigit() }?.toString() ?: "",
onValueChange = { value: String ->
if (focusRequesters[index].freeFocus()) { //For some reason this fixes the issue of focusrequestor causing on value changed to call twice
val temp = code.toMutableList()
if (value == "") {
if (temp.size > index) {
temp.removeAt(index)
code = temp
focusRequesters.getOrNull(index - 1)?.requestFocus()
}
} else {
if (code.size > index) {
temp[index] = value.getOrNull(0) ?: ' '
} else if (value.getOrNull(0)?.isDigit() == true) {
temp.add(value.getOrNull(0) ?: ' ')
code = temp
focusRequesters.getOrNull(index + 1)?.requestFocus() ?: onFilled(
code.joinToString(separator = "")
)
}
}
}
},
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
)
Spacer(modifier = Modifier.width(16.dp))
}
}
}
To solve this usecase you can use decoration box in BasicTextfield.
#Composable
fun InputField(
modifier: Modifier = Modifier,
text: String = "",
length: Int = 5,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
onFocusChanged: () -> Unit = {},
onTextChange: (String) -> Unit
) {
val BoxHeight = 40.dp
val BoxWidth = 40.dp
var textValue by remember { mutableStateOf(text) }
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
var isFocused by remember { mutableStateOf(false) }
if (text.length == length) {
textValue = text
}
BasicTextField(
modifier = modifier
.focusRequester(focusRequester)
.onFocusChanged {
isFocused = it.isFocused
onFocusChanged(it)
},
value = textValue,
singleLine = true,
onValueChange = {
textValue = it
if (it.length <= length) {
onTextChange.invoke(it)
}
},
enabled = enabled,
keyboardOptions = keyboardOptions,
decorationBox = {
Row(Modifier.fillMaxWidth()) {
repeat(length) { index ->
Text(
text = textValue,
modifier = Modifier
.size(
width =
BoxWidth,
height = BoxHeight
)
.clip(RoundedCornerShape(4.dp))
,
)
Spacer(modifier = Modifier.width(8.dp))
}
}
}
)
if (acquireFocus && textValue.length != length) {
focusRequester.requestFocus()
}
}
This will create a boxes as shown below length number of times(5 here). This is just a simple TextField with decoration as multiple boxes. You can use this text in viewModel.
TextField Examples: https://developer.android.com/jetpack/compose/text
To limit to 1 number you can use something like:
#Composable
fun Field (modifier: Modifier = Modifier,
onValueChange: (String, String) -> String = { _, new -> new }){
val state = rememberSaveable { mutableStateOf("") }
OutlinedTextField(
modifier = modifier.requiredWidth(75.dp),
singleLine = true,
value = state.value,
onValueChange = {
val value = onValueChange(state.value, it)
state.value = value
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next),
)
}
and then use:
Field(onValueChange = { old, new ->
if (new.length > 1 || new.any { !it.isDigit() }) old else new
})
set your keyboard option number password type see the below code
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword)
below the keyboard output
It might be late but hopefully, this will help someone.
I came here to find a solution but found #Nikhil code which gave me an idea of how to do it (But failed to do it). So based on his answer I have improved the composable and fixed the issues. Have not heavily tested it yet but it acts the same as you want:
#Composable
fun DecoratedTextField(
value: String,
length: Int,
modifier: Modifier = Modifier,
boxWidth: Dp = 38.dp,
boxHeight: Dp = 38.dp,
enabled: Boolean = true,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
keyboardActions: KeyboardActions = KeyboardActions(),
onValueChange: (String) -> Unit,
) {
val spaceBetweenBoxes = 8.dp
BasicTextField(modifier = modifier,
value = value,
singleLine = true,
onValueChange = {
if (it.length <= length) {
onValueChange(it)
}
},
enabled = enabled,
keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions,
decorationBox = {
Row(
Modifier.size(width = (boxWidth + spaceBetweenBoxes) * length, height = boxHeight),
horizontalArrangement = Arrangement.spacedBy(spaceBetweenBoxes),
) {
repeat(length) { index ->
Box(
modifier = Modifier
.size(boxWidth, boxHeight)
.border(
1.dp,
color = MaterialTheme.colors.primary,
shape = RoundedCornerShape(4.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = value.getOrNull(index)?.toString() ?: "",
textAlign = TextAlign.Center,
style = MaterialTheme.typography.h6
)
}
}
}
})
}
Here is what it looks like:
You can customize the size of the font + the border color to your liking.
The only drawback is that you don't have a cursor and cannot edit each box separately. Will try to fix these issues and if I do, I will update my answer
Related
I am try to learning text field in android jetpack compose, so I have two text field in a screen, and when I typing somethings in first text field, I want to close keyboard when I click the space on screen. I was using
.pointerInput(Unit) {
detectTapGestures(onTap = {
focusManager.clearFocus()
})
}
this line of code for it, it work, but it is not work for multi textfield like 10 textfield, when I click the 8.textfield for example, bottom screen looks black. I do not have any idea why it is black? Any idea?
#Composable
fun KeyboardSample(){
val focusManager = LocalFocusManager.current
Column(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectTapGestures(onTap = {
focusManager.clearFocus()
})
}
.padding(start = 16.dp, end = 16.dp),
) {
var name by rememberSaveable { mutableStateOf("") }
val updateName = { _name : String ->
name = _name
}
var amount by rememberSaveable { mutableStateOf("") }
val updateAmount = { _amount : String ->
amount = _amount
}
TextFiledsToExperiment(
name = name,
updateName = updateName,
amount = amount,
updateAmount = updateAmount
)
}
}
#Composable
fun TextFiledsToExperiment(
name : String,
updateName : (String) -> Unit,
amount : String,
updateAmount : (String) -> Unit
){
val focusManager = LocalFocusManager.current
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
OutlinedTextField(
value = name,
onValueChange = updateName ,
label = { Text("Name") },
placeholder = { Text(text = "Name") },
singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences,
autoCorrect = true,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next
),
keyboardActions = KeyboardActions(onNext = {
focusManager.moveFocus(FocusDirection.Down)
}),
modifier = Modifier
.fillMaxWidth()
.padding(top = 6.dp, start = 0.dp, end = 0.dp, bottom = 6.dp),
)
OutlinedTextField(
value = amount,
onValueChange = updateAmount ,
label = { Text("Amount") },
placeholder = { Text(text = "Amount") },
singleLine = true,
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences,
autoCorrect = true,
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(onDone = {
focusManager.clearFocus()
}),
modifier = Modifier
.fillMaxWidth()
.padding(top = 6.dp, start = 0.dp, end = 0.dp, bottom = 6.dp),
)
}
}
You can simply create clickable modifier in your column and run hide function in there.
val keyboardController = LocalSoftwareKeyboardController.current
Column(Modifier.clickable{keyboardController?.hide()}){
//
}
I keep getting this error whenever I try to use a textfield in Compose, I have tried both Textfield implementations, ie one with a String value and TextFieldValue arguments but still get the error, I have also tried using
var text = rememberSaveable{mutableStateOf("")}
and
var text by remember {mutableStateOf("")}. I have also tried hoisting the State which is what I wanted to do in the first place but still get the error
Here's the code
fun SearchAppBar(
query: String,
onQueryChanged: (String) -> Unit,
onExecuteSearch: () -> Unit,
scrollPosition: Int,
selectedCategory: FoodCategory?,
onSelectedCategoryChanged: (String) -> Unit,
onCategoryChangePosition: (Int) -> Unit,
) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = Color.White,
elevation = 8.dp,
) {
Column {
Row(
modifier = Modifier.fillMaxWidth(),
) {
TextField(
modifier = Modifier
.fillMaxWidth(.9f)
.padding(8.dp),
value = query,
onValueChange = {
onQueryChanged(it)
},
label = {
Text(text = "Search")
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done,
),
leadingIcon = {
Icon(Icons.Filled.Search)
},
onImeActionPerformed = { action, softKeyboardController ->
if (action == ImeAction.Done) {
onExecuteSearch()
softKeyboardController?.hideSoftwareKeyboard()
}
},
textStyle = TextStyle(color = MaterialTheme.colors.onSurface),
backgroundColor = MaterialTheme.colors.surface
)
}
val scrollState = rememberScrollState()
val scope = rememberCoroutineScope()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, bottom = 8.dp)
.horizontalScroll(scrollState)
) {
scope.launch {
scrollState.scrollTo(
scrollPosition
)
}
for (category in getAllFoodCategories()) {
FoodCategoryChip(
category = category.value,
isSelected = selectedCategory == category,
onSelectedCategoryChanged = {
onSelectedCategoryChanged(it)
onCategoryChangePosition(
getAllFoodCategories().indexOf(selectedCategory)
)
},
onExecuteSearch = {
onExecuteSearch()
}
)
}
}
}
}
}
You can only use the combination of parameters contained in only one of the implementations. You can't, for example, use keyboardOptions alongside onImeActionPerformed.
Turns out the backgroundColor property isnt valid anymore, use colors:. Also as pointed out by Richard Onslow Roper in his answer, look through the constructor of the TextField and see which properties are not part of your the Version of TextField you want to use
Don't do
TextField(
value = query,
onValueChange = {
onQueryChanged(it)
},
label = {
Text(text = "Search")
},
backgroundColor = MaterialTheme.colors.surface
)
do this instead
TextField(
value = query,
onValueChange = {
onQueryChanged(it)
},
label = {
Text(text = "Search")
},
colors= TextFieldDefaults.textFieldColors(
backgroundColor = MaterialTheme.colors.surface
)
)
I was making a login for my app in the new android jetpack's compose.
I want to make a OTP layout like in the given photo.
check full example here
const val PIN_VIEW_TYPE_UNDERLINE = 0
const val PIN_VIEW_TYPE_BORDER = 1
#Composable
fun PinView(
pinText: String,
onPinTextChange: (String) -> Unit,
digitColor: Color = MaterialTheme.colors.onBackground,
digitSize: TextUnit = 16.sp,
containerSize: Dp = digitSize.value.dp * 2,
digitCount: Int = 4,
type: Int = PIN_VIEW_TYPE_UNDERLINE,
) {
BasicTextField(value = pinText,
onValueChange = onPinTextChange,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
decorationBox = {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
repeat(digitCount) { index ->
DigitView(index, pinText, digitColor, digitSize, containerSize, type = type)
Spacer(modifier = Modifier.width(5.dp))
}
}
})
}
#Composable
private fun DigitView(
index: Int,
pinText: String,
digitColor: Color,
digitSize: TextUnit,
containerSize: Dp,
type: Int = PIN_VIEW_TYPE_UNDERLINE,
) {
val modifier = if (type == PIN_VIEW_TYPE_BORDER) {
Modifier
.width(containerSize)
.border(
width = 1.dp,
color = digitColor,
shape = MaterialTheme.shapes.medium
)
.padding(bottom = 3.dp)
} else Modifier.width(containerSize)
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center) {
Text(
text = if (index >= pinText.length) "" else pinText[index].toString(),
color = digitColor,
modifier = modifier,
style = MaterialTheme.typography.body1,
fontSize = digitSize,
textAlign = TextAlign.Center)
if (type == PIN_VIEW_TYPE_UNDERLINE) {
Spacer(modifier = Modifier.height(2.dp))
Box(
modifier = Modifier
.background(digitColor)
.height(1.dp)
.width(containerSize)
)
}
}
}
You can use a very simple layout for each char in the otp.
Something like
#Composable
fun OtpChar(){
var text by remember { mutableStateOf("1") }
val maxChar = 1
Column(Modifier.background(DarkGray),
horizontalAlignment = Alignment.CenterHorizontally){
TextField(
value =text,
onValueChange = {if (it.length <= maxChar) text = it},
modifier = Modifier.width(50.dp),
singleLine = true,
textStyle = LocalTextStyle.current.copy(
fontSize = 20.sp,
textAlign= TextAlign.Center),
colors= TextFieldDefaults.textFieldColors(
textColor = White,
backgroundColor = Transparent,
unfocusedIndicatorColor = Transparent,
focusedIndicatorColor = Transparent)
)
Divider(Modifier
.width(28.dp)
.padding(bottom = 2.dp)
.offset(y=-10.dp),
color = White,
thickness = 1.dp)
}
}
You can add some features like:
manage the focus in Next direction with the TAB key
manage the focus in Previous direction with the BACK SPACE key
how to move to the next textfield when a digit is entered
Something like:
fun OtpChar(
modifier: Modifier = Modifier
){
val pattern = remember { Regex("^[^\\t]*\$") } //to not accept the tab key as value
var (text,setText) = remember { mutableStateOf("") }
val maxChar = 1
val focusManager = LocalFocusManager.current
LaunchedEffect(
key1 = text,
) {
if (text.isNotEmpty()) {
focusManager.moveFocus(
focusDirection = FocusDirection.Next,
)
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally
){
TextField(
value =text,
onValueChange = {
if (it.length <= maxChar &&
((it.isEmpty() || it.matches(pattern))))
setText(it)
},
modifier = modifier
.width(50.dp)
.onKeyEvent {
if (it.key == Key.Tab) {
focusManager.moveFocus(FocusDirection.Next)
true
}
if (text.isEmpty() && it.key == Key.Backspace) {
focusManager.moveFocus(FocusDirection.Previous)
}
false
},
textStyle = LocalTextStyle.current.copy(
fontSize = 20.sp,
textAlign= TextAlign.Center),
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next
),
colors= TextFieldDefaults.textFieldColors(
backgroundColor = Transparent,
unfocusedIndicatorColor = Transparent,
focusedIndicatorColor = Transparent),
)
Divider(
Modifier
.width(28.dp)
.padding(bottom = 2.dp)
.offset(y = -10.dp),
color = Teal200,
thickness = 1.dp)
}
}
Then just use something like a Row to display 4 OtpChars
val (item1, item2, item3, item4) = FocusRequester.createRefs()
Row(horizontalArrangement = Arrangement.SpaceBetween){
OtpChar(
modifier = Modifier
.focusRequester(item1)
.focusProperties {
next = item2
previous = item1
}
)
OtpChar(
modifier = Modifier
.focusRequester(item2)
.focusProperties {
next = item3
previous = item1
}
)
OtpChar(
modifier = Modifier
.focusRequester(item3)
.focusProperties {
next = item4
previous = item2
}
)
OtpChar(
modifier = Modifier
.focusRequester(item4)
.focusProperties {
previous = item3
next = item4
}
)
//....
}
If you faced keyboard issues try the code below:
#Composable
fun OtpCell(
modifier: Modifier,
value: String,
isCursorVisible: Boolean = false
) {
val scope = rememberCoroutineScope()
val (cursorSymbol, setCursorSymbol) = remember { mutableStateOf("") }
LaunchedEffect(key1 = cursorSymbol, isCursorVisible) {
if (isCursorVisible) {
scope.launch {
delay(350)
setCursorSymbol(if (cursorSymbol.isEmpty()) "|" else "")
}
}
}
Box(
modifier = modifier
) {
Text(
text = if (isCursorVisible) cursorSymbol else value,
style = MaterialTheme.typography.body1,
modifier = Modifier.align(Alignment.Center)
)
}
}
#ExperimentalComposeUiApi
#Composable
fun PinInput(
modifier: Modifier = Modifier,
length: Int = 5,
value: String = "",
onValueChanged: (String) -> Unit
) {
val focusRequester = remember { FocusRequester() }
val keyboard = LocalSoftwareKeyboardController.current
TextField(
value = value,
onValueChange = {
if (it.length <= length) {
if (it.all { c -> c in '0'..'9' }) {
onValueChanged(it)
}
if (it.length >= length) {
keyboard?.hide()
}
}
},
// Hide the text field
modifier = Modifier
.size(0.dp)
.focusRequester(focusRequester),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number
)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
repeat(length) {
OtpCell(
modifier = modifier
.size(width = 45.dp, height = 60.dp)
.clip(MaterialTheme.shapes.large)
.background(MaterialTheme.colors.surface)
.clickable {
focusRequester.requestFocus()
keyboard?.show()
},
value = value.getOrNull(it)?.toString() ?: "",
isCursorVisible = value.length == it
)
if (it != length - 1) Spacer(modifier = Modifier.size(8.dp))
}
}
}
Result:
First make the common TextField for OTP Screen
#Composable
fun CommonOtpTextField(otp: MutableState<String>) {
val max = 1
OutlinedTextField(
value = otp.value,
singleLine = true,
onValueChange = { if (it.length <= max) otp.value = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
shape = RoundedCornerShape(20.dp),
modifier = Modifier
.width(60.dp)
.height(60.dp),
maxLines = 1,
textStyle = LocalTextStyle.current.copy(
textAlign = TextAlign.Center
)
)
}
And Now use above CommonTextField to make four otp Field
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 15.dp, start = 15.dp, end = 15.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
CommonOtpTextField(otp = otpOne)
CommonOtpTextField(otp = otpTwo)
CommonOtpTextField(otp = otpThree)
CommonOtpTextField(otp = otpFour)
}
Simple solution that uses one TextField and different code length
#Composable
fun RegistrationCodeInput(codeLength: Int) {
val code = remember { mutableStateOf("") }
val focusRequester = FocusRequester()
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
BasicTextField(
value = code.value,
onValueChange = { if (it.length <= codeLength) code.value = it },
Modifier.focusRequester(focusRequester = focusRequester),
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number),
decorationBox = {
CodeInputDecoration(code.value, codeLength)
})
}
}
#Composable
private fun CodeInputDecoration(code: String, length: Int) {
Box(
modifier = Modifier
.padding(16.dp)
.border(
border = BorderStroke(2.dp, color = borderColor),
shape = Shapes.small
)
) {
Row(
) {
for (i in 0 until length) {
val text = if (i < code.length) code[i].toString() else ""
CodeEntry(text)
}
}
}
}
#Composable
private fun CodeEntry(text: String) {
Box(
modifier = Modifier
.width(42.dp)
.height(42.dp),
contentAlignment = Alignment.Center
) {
Text(text = text)
}
}
#Preview
#Composable
fun PreviewInput() {
RegistrationCodeInput(4)
}
I have five TextFields. After something entered in first field focus moves for next textfield.
If i deleted something in text field - focus moves for pervious textfield.
All work with focus goes through onValueChanged section
But if value in textfield blank("") - if i press backspace in keyboard nothing happened with onValueChanged, because value in field not changed. And I needed somehow set focus on previous textfield
So how i can use listener for back press in soft-keyboard for text field in compose?
I tried use KeyboardActions,
keyboardActions = KeyboardActions(
onPrevious = {
//some work with foucs
},
),
but it not working
And second question:
if textfield got clicked(or get focus) how to set cursor in field on end text?
I needed even if user click middle of string cursor sets on end.
With solution #nglauber
Textfield for enter sms(for now):
#Composable
fun SMSTextFields(
modifier: Modifier,
smsCodeLength: Int = 5,
whenFull: (smsCode: String) -> Unit
) {
val enteredNumbers = remember {
mutableStateListOf(
*((0 until smsCodeLength).map { "" }.toTypedArray())
)
}
val focusRequesters: List<FocusRequester> = remember {
(0 until smsCodeLength).map { FocusRequester() }
}
Row(modifier = modifier.padding(start = 60.dp, end = 60.dp)) {
(0 until smsCodeLength).forEach { index ->
TextField(
modifier = Modifier
.weight(1f)
.size(120.dp, 80.dp)
.onKeyEvent { event ->
val cellValue = enteredNumbers[index]
if (event.type == KeyEventType.KeyUp) {
if (event.key == Key.Backspace && cellValue == "") {
focusRequesters
.getOrNull(index - 1)
?.requestFocus()
enteredNumbers[index - 1] = ""
} else if (cellValue != "") {
focusRequesters
.getOrNull(index + 1)
?.requestFocus()
}
}
false
}
.padding(vertical = 2.dp)
.focusOrder(focusRequesters[index])
.focusRequester(focusRequesters[index]),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = whiteBackground,
unfocusedIndicatorColor = greyColor,
focusedIndicatorColor = signUpColorButton,
cursorColor = greyColor,
textColor = greyColor
),
textStyle = smsCodeEnterStyle,
singleLine = true,
value = enteredNumbers[index],
onValueChange = { value: String ->
if (value.isDigitsOnly()) {
if (value.length > 1) {
enteredNumbers[index] = value.last().toString()
return#TextField
}
if (focusRequesters[index].freeFocus()) {
enteredNumbers[index] = value
if (enteredNumbers[index].isBlank() && index > 0 && index <5) {
focusRequesters[index - 1].requestFocus()
} else if (index < smsCodeLength - 1) {
focusRequesters[index + 1].requestFocus()
}
else if (enteredNumbers.size == 5){
whenFull(enteredNumbers.joinToString(separator = ""))
}
}
}
},
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
)
Spacer(modifier = Modifier.width(4.dp))
}
}
}
#ExperimentalComposeUiApi
#Composable
fun EnterOtpView() {
val scrollState = rememberScrollState()
val focusManager = LocalFocusManager.current
val digits = remember {
mutableStateListOf(
*((0 until 4).map { "" }.toTypedArray())
)
}
val focusRequesters: List<FocusRequester> = remember {
(0 until 4).map { FocusRequester() }
}
Column(
Modifier.padding(horizontal = 70.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = "123 456 8550", style = MaterialTheme.typography.h3, color = Purple,
modifier = Modifier.padding(top = 20.dp, bottom = 30.dp)
)
Row(
modifier = Modifier
.fillMaxWidth(1f)
.wrapContentHeight(),
Arrangement.SpaceBetween,
Alignment.CenterVertically
) {
(0 until 4).forEach { index ->
TextField(
modifier = Modifier
.weight(0.2f)
.padding(end = 4.dp)
.onKeyEvent {
if (it.nativeKeyEvent.keyCode == 67) {
if (digits[index].isEmpty()) {
focusManager.moveFocus(FocusDirection.Left)
}
digits[index] = ""
}
true
}
.padding(vertical = 2.dp)
.focusOrder(focusRequesters[index])
.focusRequester(focusRequesters[index]),
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent
),
singleLine = true,
textStyle = MaterialTheme.typography.body1.copy(textAlign = TextAlign.Center),
value = digits[index],
onValueChange = {
if (digits[index].isEmpty() && it.isDigitsOnly()) {
digits[index] = it
focusManager.moveFocus(FocusDirection.Right)
}
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next,
keyboardType = KeyboardType.Number
),
keyboardActions = KeyboardActions(
onNext = {
focusManager.moveFocus(FocusDirection.Right)
}
)
)
}
}
}}
Worked for me using nativeKeyEvent and focusManager.
I want to create textfield with hint text in jetpackcompose. Any example how create textfield using jectpack? Thanks
compose_version = '1.0.0-beta07'
Use parameter placeholder to show hint
TextField(value = "", onValueChange = {}, placeholder = { Text("Enter Email") })
Use parameter label to show floating label
TextField(value = "", onValueChange = {}, label = { Text("Enter Email") })
You can use
the label parameter in the TextField composable to display a floating label
the placeholder parameter to display a placeholder when the text field is in focus and the input text is empty.
You can also use them together.
Something like:
var text by remember { mutableStateOf("text") }
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
label = {
Text("Label")
}
)
or
TextField(
value = text,
onValueChange = {
text = it
},
label = {
Text("Label")
},
placeholder = {
Text("Placeholder")
}
)
You can create hintTextField in jetpackCompose like below code:
#Composable
fun HintEditText(hintText: #Composable() () -> Unit) {
val state = state { "" } // The unary plus is no longer needed. +state{""}
val inputField = #Composable {
TextField(
value = state.value,
onValueChange = { state.value = it }
)
}
if (state.value.isNotEmpty()) {
inputField()
} else {
Layout(inputField, hintText) { measurable, constraints ->
val inputfieldPlacable = measurable[inputField].first().measure(constraints)
val hintTextPlacable = measurable[hintText].first().measure(constraints)
layout(inputfieldPlacable.width, inputfieldPlacable.height) {
inputfieldPlacable.place(0.ipx, 0.ipx)
hintTextPlacable.place(0.ipx, 0.ipx)
} }
}
}
Call #Compose function like below:
HintEditText #Composable {
Text(
text = "Enter Email",
style = TextStyle(
color = Color.White,
fontSize = 18.sp
)
)
}
Jetpack compose version: dev08
The benefit of compose is that we can easily create our widgets by composing current composable functions.
We can just create a function with all parameters of the current TextField and add a
hint: String parameter.
#Composable
fun TextFieldWithHint(
value: String,
modifier: Modifier = Modifier.None,
hint: String,
onValueChange: (String) -> Unit,
textStyle: TextStyle = currentTextStyle(),
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Unspecified,
onFocus: () -> Unit = {},
onBlur: () -> Unit = {},
focusIdentifier: String? = null,
onImeActionPerformed: (ImeAction) -> Unit = {},
visualTransformation: VisualTransformation? = null,
onTextLayout: (TextLayoutResult) -> Unit = {}
) {
Stack(Modifier.weight(1f)) {
TextField(value = value,
modifier = modifier,
onValueChange = onValueChange,
textStyle = textStyle,
keyboardType = keyboardType,
imeAction = imeAction,
onFocus = onFocus,
onBlur = onBlur,
focusIdentifier = focusIdentifier,
onImeActionPerformed = onImeActionPerformed,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout)
if (value.isEmpty()) Text(hint)
}
}
We can use it like this:
#Model
object model { var text: String = "" }
TextFieldWithHint(value = model.text, onValueChange = { data -> model.text = data },
hint= "Type book name or author")
The pitfall of this approach is we are passing the hint as a string so if we want to style the hint we should add extra parameters to the TextFieldWithHint (e.g hintStyle, hintModifier, hintSoftWrap, ...)
The better approach is to accept a composable lambda instead of string:
#Composable
fun TextFieldWithHint(
value: String,
modifier: Modifier = Modifier.None,
hint: #Composable() () -> Unit,
onValueChange: (String) -> Unit,
textStyle: TextStyle = currentTextStyle(),
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Unspecified,
onFocus: () -> Unit = {},
onBlur: () -> Unit = {},
focusIdentifier: String? = null,
onImeActionPerformed: (ImeAction) -> Unit = {},
visualTransformation: VisualTransformation? = null,
onTextLayout: (TextLayoutResult) -> Unit = {}
) {
Stack(Modifier.weight(1f)) {
TextField(value = value,
modifier = modifier,
onValueChange = onValueChange,
textStyle = textStyle,
keyboardType = keyboardType,
imeAction = imeAction,
onFocus = onFocus,
onBlur = onBlur,
focusIdentifier = focusIdentifier,
onImeActionPerformed = onImeActionPerformed,
visualTransformation = visualTransformation,
onTextLayout = onTextLayout)
if (value.isEmpty()) hint()
}
}
We can use it like this:
#Model
object model { var text: String = "" }
TextFieldWithHint(value = model.text, onValueChange = { data -> model.text = data },
hint= { Text("Type book name or author", style = TextStyle(color = Color(0xFFC7C7C7))) })
var textState by remember { mutableStateOf(TextFieldValue()) }
var errorState by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf("") }
TextField(
value = textState,
onValueChange = {
textState = it
when {
textState.text.isEmpty() -> {
errorState = true
errorMessage = "Please Enter Site Code"
}
else -> {
errorState = false
errorMessage = ""
}
}
},
isError = errorState,
label = {
Text(
text = if (errorState) errorMessage
else "You Hint"
)
},
modifier = Modifier
.padding(top = 20.dp, start = 30.dp, end = 30.dp)
.fillMaxWidth())
If you want to create a text field with hint text and custom color here is the code for that.
#Composable
fun PhoneOrEmail() {
var text by remember { mutableStateOf("") }
TextField(
value = text,
onValueChange = { text = it },
label = { Text("Phone/email", style = TextStyle(color =
Color.Red)) }
)//End of TextField
}//End of Function
Here's what works for me (I think it's a bit simpler than what Anas posted since it's using the same component:
#Composable
fun TextBox(
loginInput: LoginInput,
hint: String = "enter value",
color: Color = Color.LightGray,
height: Dp = 50.dp
) {
val state = +state { "" }
state.value = if (loginInput.usernameEntered) loginInput.username else hint
Surface(color = color) {
Row {
Container(modifier = Expanded, height = height) {
Clip(shape = RoundedCornerShape(15.dp)) {
Padding(padding = 15.dp) {
TextField(
value = state.value,
keyboardType = KeyboardType.Text,
onFocus = {
if (!loginInput.usernameEntered)
state.value = ""
},
onValueChange = {
loginInput.usernameEntered = true
loginInput.username = it
state.value = loginInput.username
}
)
}
}
}
}
}
}
the label parameter will be displayed as text if text is empty and moves above the textfield (as label) on typing input:
#Composable
fun SearchField() {
val (text, setText) = remember { mutableStateOf(TextFieldValue("")) }
Box(modifier = Modifier.width(180.dp).padding(2.dp)) {
TextField(
modifier = Modifier.fillMaxWidth(),
value = text,
onValueChange = { setText(it) },
label = { Text("quick do:") },
)
}
}