I have simple LazyColumn where each row contains a BasicTextField (the rows contain label names which can be changed). The row's state is set to "editMode" when it is clicked. Only then I want the BasicTextField to be editable, which makes the edit icons visible. However, I have to click two times on the row, but I want it to set focus on the TextField when the row is clicked for the first time. Using the focusRequest just does not work (nothing happens). Where could the problem be? Here is the code:
val focusRequester = FocusRequester()
val inputService = LocalTextInputService.current
Row(
modifier = modifier
.fillMaxWidth()
.height(48.dp)
.clickable(enabled = true) {
onLabelClick() // here I set the editMode = true
inputService?.showSoftwareKeyboard()
focusRequester.requestFocus()
}
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Box(
modifier = Modifier.weight(1f)
) {
BasicTextField(
modifier = Modifier
.fillMaxSize()
.focusRequester(focusRequester)
.focusable(enabled = true),
value = tmpLabelName,
onValueChange = {
tmpLabelName = it
},
enabled = editMode,
singleLine = true,
textStyle = MaterialTheme.typography.body1,
decorationBox = { innerTextField ->
Box(
contentAlignment = Alignment.CenterStart
) {
if (tmpLabelName.isEmpty()) {
// show hint if text is empty
Text(
text = stringResource(id = R.string.enter_label_name)
)
} else {
innerTextField()
}
}
}
)
}
AnimatedVisibility(
visible = editMode,
enter = slideInHorizontally(
initialOffsetX = { fullWidth ->
fullWidth / 2
}
),
exit = fadeOut()
) {
Row {
IconButton(onClick = {
onLabelSaveClick(
label.name, tmpLabelName
)
}) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = stringResource(id = R.string.save_label_changes)
)
}
IconButton(onClick = {
onLabelDeleteClick(label.name)
}) {
Icon(
imageVector = Icons.Filled.Delete,
contentDescription = stringResource(id = R.string.delete_label)
)
}
}
}
}
Related
I have nested column, when I click add button the goal is add another text field and when I click delete button (which still hidden because first index) the goal is remove its text field. It seems doesn't recompose but the list size is changed.
I have tried using LazyColumn and foreach inside leads to force close, still no luck.
Any help appreciated, thank you!
My current code :
#Composable
fun ProblemScreen() {
val list = remember {
mutableStateListOf<MutableList<String>>()
}
LaunchedEffect(key1 = Unit, block = {
repeat(3) {
val listDesc = mutableListOf<String>()
repeat(1) {
listDesc.add("")
}
list.add(listDesc)
}
})
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
list.forEachIndexed { indexParent, parent ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Parent ${indexParent + 1}", fontSize = 18.sp)
Spacer(modifier = Modifier.weight(1f))
Button(onClick = {
parent.add("")
println("PARENT SIZE : ${parent.size}")
}) {
Icon(imageVector = Icons.Rounded.Add, contentDescription = "Add")
}
}
parent.forEachIndexed { indexChild, child ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = "",
onValueChange = {
},
colors = TextFieldDefaults.textFieldColors(),
maxLines = 1,
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(16.dp))
Button(
onClick = {
parent.removeAt(indexChild)
},
modifier = Modifier.alpha(if (indexChild != 0) 1f else 0f)
) {
Icon(
imageVector = Icons.Rounded.Delete,
contentDescription = "Delete"
)
}
}
}
}
}
}
}
As said in docs, mutable objects that are not observable, such as mutableListOf(), are not observable by Compose and don't trigger a recomposition.
So instead of
val list = remember {
mutableStateListOf<MutableList<String>>()
}
Use:
val list = remember {
mutableStateListOf<List<String>>()
}
And when you need to update the List, create a new one:
//parent.add("")
list[indexParent] = parent + ""
I am making a project with Jetpack Compose. I want to display comments like there are in Instagram. There is an array that contains comments.
This is a code used to display comments:
val i : Int
for(i in 1..user.count) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Image1(
painter = painterResource(id = user.pp),
contentDescription = "PP",
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
)
Spacer(modifier = Modifier.width(10.dp))
Column() {
Row() {
Text(text = user.name, color = Color.Black, fontSize = 20.sp)
Spacer(modifier = Modifier.width(10.dp))
}
Spacer(modifier = Modifier.width(10.dp))
Text(text = "Public", color = Color.DarkGray, fontSize = 13.sp)
}
}
IconButton(onClick = { /*TODO*/ }) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_more_vert_24),
contentDescription = "More"
)
}
}
Row() {
Spacer(modifier = Modifier.width(10.dp))
Text(
text = user.c[i-1],
color = Color.Black,
fontSize = 16.sp,
modifier = Modifier.padding(end = 10.dp)
)
}
Spacer(modifier = Modifier.height(10.dp))
Row() {
var isClicked by remember {
mutableStateOf(false)
}
Spacer(modifier = Modifier.width(10.dp))
Icon(
painter = painterResource(
id =
if (!isClicked) R.drawable.like_in_comments else R.drawable.like
),
contentDescription = "Like",
tint = Color.Blue,
modifier = Modifier
.size(25.dp)
.clickable { isClicked = !isClicked }
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "Like",
color = Color.DarkGray,
fontSize = 16.sp,
)
}
Spacer(modifier = Modifier.height(10.dp))
Divider()
}
I want to make it scrollable. I can use LazyRow. When I use it, I get some errors. How do I implement it? Please help.
You can make an ordinary Row scrollable by supplying its Modifier.horizontalScroll() with a scroll state like the following
val scrollState = rememberScrollState()
Row (modifier = Modifier.horizontalScroll(scrollState)) {
...
}
But for a simple LazyRow without having a unique key, you don't need to iterate each item via a loop construct (e.g a for-loop), you just have to simply call items and pass the list.
val itemList = listOf("Item1", "Item2")
LazyRow {
items(itemList) { item ->
// your composable here
}
}
For a LazyRow with a unique key (assuming you have a class with an "id" attribute)
val people = listOf(Person(1, "person1"), Person(2, "person2"))
LazyRow {
items(items = people, key = { item -> person.id }) { person->
// your composable here
}
}
I wrote an AutoCompletet TextField in Jetpack Compose. I used this view in a Row and between two others' views. The side views move down when autocomplete dropdown is opened. How can I fix this issue?
The Link of GIF file is end of page. in the GIF you can see side views of Autocomplete move to bottom when the suggest items is shows.
My AutoComplete codes:
#Composable
fun AutoCompleteTextField(
modifier: Modifier = Modifier,
list: List<Product>,
searchTerm: TextFieldValue,
updateSearchTerm: (TextFieldValue) -> Unit,
) {
var product by remember {
mutableStateOf("")
}
var heightTextFields by remember {
mutableStateOf(55.dp)
}
var textFieldSize by remember {
mutableStateOf(Size.Zero)
}
var expanded by remember {
mutableStateOf(false)
}
val interactionSource = remember {
MutableInteractionSource()
}
Column(
modifier = modifier
.width(200.dp)
// .padding(30.dp)
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = {
expanded = false
}
)
) {
Column(modifier = modifier.fillMaxWidth()) {
Row(
modifier = modifier
.border(BorderStroke(width = 1.dp, color = Color.LightGray))
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = {product = "" }) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Add",
modifier = modifier
.padding(4.dp)
.align(Alignment.CenterVertically)
)
}
TextField(
modifier = Modifier
.fillMaxWidth()
.height(heightTextFields)
.onGloballyPositioned { coordinates ->
textFieldSize = coordinates.size.toSize()
},
value = product,
onValueChange = {
product = it
expanded = true
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
cursorColor = Color.Black
),
textStyle = TextStyle(
fontFamily = shabnamFontFamily,
color = MaterialTheme.colorScheme.secondary,
fontWeight = FontWeight.Bold,
fontSize = 14.sp,
),
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Done
),
singleLine = true,
)
}
AnimatedVisibility(visible = expanded) {
Card(
modifier = modifier
.padding(horizontal = 4.dp)
.width(textFieldSize.width.dp),
elevation = 10.dp,
shape = RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp)
) {
LazyColumn(
modifier = Modifier.heightIn(max = 150.dp),
) {
if (product.isNotEmpty()) {
items(
list.filter {
it.productName.lowercase()
.contains(product.lowercase())
}
) { item ->
ProductItems(title = item.productName) { title ->
product = title
expanded = false
}
}
} else {
items(
items =
list
) { item ->
ProductItems(title = item.productName) { title ->
product = title
expanded = false
}
}
}
}
}
}
}
}
}
}
Screen GIF file
I want to create an alert dialog with Jetpack Compose. In this alert dialog include a TextField component from Compose.
I created that alert dialog but there is a problem here that when I press enter button where is on the device keyboard to add a new line my content is moving to up.
How can I solve this UI issue?
Thanks in advance.
The Problem;
Here is my code;
#Composable
fun SimpleAlert(
title: String = "Ask a Question",
onDismiss: () -> Unit,
onSubmit: (text: String) -> Unit
) {
val text = remember { mutableStateOf("") }
val textHint = "Write your question..."
val textLength = remember { mutableStateOf(0) }
val isAnonymous = remember { mutableStateOf(false) }
AlertDialog(
onDismissRequest = {
onDismiss()
},
title = {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.drawable.ic_exit),
contentDescription = "",
modifier = Modifier.
background(Color.Gray, CircleShape).clickable {
onDismiss()
}
)
}
Text(
text = title,
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center
)
}
},
text = {
Column(
verticalArrangement = Arrangement.Center
) {
TextField(
value = text.value,
onValueChange = {
if (it.length <= 200) {
textLength.value = it.length
text.value = it
}
},
label = {
Text(text = textHint)
},
modifier = Modifier
.padding(top = 16.dp)
.sizeIn(minHeight = 120.dp)
.background(Color.Transparent)
)
}
},
buttons = {
Row(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Switch(checked = isAnonymous.value, onCheckedChange = {
isAnonymous.value = it
})
Text(text = "Anonymously")
}
Text(text = "${textLength.value}/200")
}
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, top = 0.dp, end = 16.dp, bottom = 24.dp),
onClick = {
onSubmit(text.value)
}) { Text(text = "Submit") }
}
)
}
SOLUTION ✅
To solve this issue just use Dialog instead of AlertDialog
This is definitely what you are looking for
TextField(
value = text.value,
onValueChange = {
if (it.length <= 200) {
textLength.value = it.length
text.value = it
}
},
label = {
Text(text = textHint)
},
modifier = Modifier
.padding(top = 16.dp)
.sizeIn(minHeight = 120.dp)
.background(Color.Transparent),
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
//Do something here
},
),
)
How do I create a rounded checkbox in Jetpackcompose like this. I tried using a Shape composable on it but it doesn't work.
I was looking on how to do the same thing you were asking, your question helped me on my journey so it is only fair I share. Add some animations and you are set my friend.
Make a round looking icon by using a box and an icon
Box(
modifier = Modifier
.clip(CircleShape)
.size(40.dp)
.background(Color.Black)
.padding(3.dp)
.clip(CircleShape)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
2.Place the newly made rounded icon and some text next to each other by using a Row
Row(
verticalAlignment = Alignment.CenterVertically,
){
Box(
modifier = Modifier
.clip(CircleShape)
.size(40.dp)
.background(Color.Black)
.padding(3.dp)
.clip(CircleShape)
.background(Color.White),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
3.Replace whatever you want with variables so you can customize
it
#Composable
fun RoundedCheckView(
) {
val isChecked = remember { mutableStateOf(false) }
val checkedText = remember { mutableStateOf("unChecked") }
val circleSize = remember { mutableStateOf(20.dp) }
val circleThickness = remember { mutableStateOf(2.dp) }
val color = remember { mutableStateOf(Color.Gray) }
Row(
verticalAlignment = Alignment.CenterVertically,
{
Box(
modifier = Modifier
.clip(CircleShape)
.size(circleSize.value)
.background(color.value)
.padding(circleThickness.value)
.clip(CircleShape)
.background(Color.White) ,
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
}
4.Finally add Modifier.toggleable to the row, basically making it a clickable item that toggles (between true and false) a variable in this case isChecked. Then just customize the variables according to what you need
#Composable
fun RoundedCheckView()
{
val isChecked = remember { mutableStateOf(false) }
val checkedText = remember { mutableStateOf("unChecked") }
val circleSize = remember { mutableStateOf(20.dp) }
val circleThickness = remember { mutableStateOf(2.dp) }
val color = remember { mutableStateOf(Color.Gray) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.toggleable(value = isChecked.value,role = Role.Checkbox) {
isChecked.value = it
if (isChecked.value) {
checkedText.value = "Checked"
circleSize.value = 40.dp
circleThickness.value = 3.dp
color.value = Color.Black
} else {
checkedText.value = "unChecked"
circleSize.value = 20.dp
circleThickness.value = 2.dp
color.value = Color.Gray
}
}) {
Box(
modifier = Modifier
.clip(CircleShape)
.size(circleSize.value)
.background(color.value)
.padding(circleThickness.value)
.clip(CircleShape)
.background(Color.White) ,
contentAlignment = Alignment.Center
) {
if(isChecked.value){
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
}
Text(
text = checkedText.value,
color = color.value,
fontSize = 20.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(start = 5.dp)
)
}
}
This is how we can make a custom check box in jetpack compose
val isCheck = remember { mutableStateOf(false) }
Row {
Card(
modifier = Modifier.background(Color.White),
elevation = 0.dp,
shape = RoundedCornerShape(6.dp),
border = BorderStroke(1.5.dp, color = titleColor)
) {
Box(
modifier = Modifier
.size(25.dp)
.background(if (isCheck.value) titleColor else Color.White)
.clickable {
isCheck.value = !isCheck.value
},
contentAlignment = Center
) {
if(isCheck.value)
Icon(Icons.Default.Check, contentDescription = "", tint = Color.White)
}
}
Text(
modifier = Modifier
.align(CenterVertically)
.padding(start = 10.dp),
text = "I agree with the terms & condition",
)
}
You can try to make it use Box with modifier content alignment center. and put an icon on there.
#Preview
#Composable
fun Check() {
Box(
modifier = Modifier
.clip(CircleShape)
.size(50.dp)
.background(Color.Red)
.padding(5.dp)
.clip(CircleShape)
.background(Color.Blue),
contentAlignment = Alignment.Center
) {
Icon(imageVector = Icons.Default.Check, contentDescription = "")
}
}
We can use animations to make it behave similar to the default Checkbox. Using Modifier.toggleable on the top-level Row, the entire thing is clickable, including the label. This also creates the proper semantics for screen reader users. You can change the shape of the card to get a circular checkbox.
#Composable
fun PrimaryCheckbox(
label: String,
modifier: Modifier = Modifier,
size: Float = 24f,
checkedColor: Color = DarkGray,
uncheckedColor: Color = White,
checkmarkColor: Color = White,
onValueChange: () -> Unit
) {
var isChecked by remember { mutableStateOf(false) }
val checkboxColor: Color by animateColorAsState(if (isChecked) checkedColor else uncheckedColor)
val density = LocalDensity.current
val duration = 200
Row(
modifier = modifier
.toggleable(
value = isChecked,
role = Role.Checkbox,
onValueChange = {
isChecked = !isChecked
onValueChange.invoke()
}
)
) {
Card(
elevation = 0.dp,
shape = RoundedCornerShape(4.dp),
border = BorderStroke(1.5.dp, color = checkedColor),
) {
Box(
modifier = Modifier
.size(size.dp)
.background(checkboxColor),
contentAlignment = Alignment.Center
) {
androidx.compose.animation.AnimatedVisibility(
visible = isChecked,
enter = slideInHorizontally(
animationSpec = tween(duration)
) {
with(density) { (size * -0.5).dp.roundToPx() }
} + expandHorizontally(
expandFrom = Alignment.Start,
animationSpec = tween(duration)
),
exit = fadeOut()
) {
Icon(
Icons.Default.Check,
contentDescription = null,
tint = checkmarkColor
)
}
}
}
Text(
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(start = 8.dp),
text = label,
)
}
}