I am trying to achieve a behaviour similar with the photo. A library that can do the same thing will be useful as well. I already tried https://github.com/dokar3/ChipTextField but run into some issues
java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/compose/ui/platform/LocalSoftwareKeyboardController;
You can implement it using FlowRow
, Chip
and BasicTextField
.
1- Create a data class that hold Uri and String
@Immutable
data class ChipData(
val uri: Uri,
val text: String,
val id: String = UUID.randomUUID().toString()
)
2- Create custom chip that displays image, string. I used Coil
library for painter to get Painter
from Uri
.
@Composable
private fun MyChip(
backgroundColor: Color,
data: ChipData,
onDeleteClick: () -> Unit
) {
Chip(
modifier = Modifier,
shape = RoundedCornerShape(50),
enabled = false,
onClick = {},
border = BorderStroke(1.dp, Green400.copy(alpha = .9f)),
colors = ChipDefaults.chipColors(
disabledBackgroundColor = backgroundColor,
disabledContentColor = Color.White
),
leadingIcon = {
Image(
painter = rememberAsyncImagePainter(data.uri),
modifier = Modifier
.padding(vertical = 4.dp)
.size(34.dp)
.clip(CircleShape),
contentScale = ContentScale.FillBounds,
contentDescription = null
)
}
) {
Text(
text = data.text,
modifier = Modifier.weight(1f, fill = false),
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Icon(
modifier = Modifier
.clip(CircleShape)
.clickable {
onDeleteClick()
}
.background(Color.Black.copy(alpha = .4f))
.size(16.dp)
.padding(2.dp),
imageVector = Icons.Filled.Close,
tint = Color(0xFFE0E0E0),
contentDescription = null
)
}
}
3- Use FlowRow
to align chips and put a BasicTextField to last item.
Also i used rememberLauncherForActivityResult for selecting image that you can add to gradle with
implementation("com.google.modernstorage:modernstorage-photopicker:1.0.0-alpha06")
You can use another or default image picker with SAF if you want to
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ChipAndTextFieldLayout(
modifier: Modifier = Modifier,
backgroundColor: Color,
list: List<ChipData> = emptyList(),
onChipCreated: (ChipData) -> Unit,
chip: @Composable (data: ChipData, index: Int) -> Unit
) {
var text by remember {
mutableStateOf("")
}
val focusRequester = remember {
FocusRequester()
}
val keyboardController: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current
val photoPicker =
rememberLauncherForActivityResult(PhotoPicker()) { uris ->
uris.firstOrNull()?.let { uri: Uri ->
onChipCreated(
ChipData(
uri = uri,
text = text
)
)
text = ""
// Open keyboard after new chip is added
keyboardController?.show()
}
}
LaunchedEffect(Unit) {
delay(100)
focusRequester.requestFocus()
}
FlowRow(
modifier = modifier
.drawWithContent {
drawContent()
drawLine(
Green400.copy(alpha = .6f),
start = Offset(0f, size.height),
end = Offset(size.width, size.height),
strokeWidth = 4.dp.toPx()
)
},
horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
list.forEachIndexed { index, item ->
key(item.id) {
chip(item, index)
}
}
Box(
modifier = Modifier.height(54.dp)
// This minimum width that TextField can have
// if remaining space in same row is smaller it's moved to next line
.widthIn(min = 80.dp)
// TextField can grow as big as Composable width
.weight(1f),
contentAlignment = Alignment.CenterStart
) {
BasicTextField(
modifier = Modifier.focusRequester(focusRequester),
value = text,
textStyle = TextStyle(
fontSize = 20.sp
),
cursorBrush = SolidColor(backgroundColor),
singleLine = true,
onValueChange = { text = it },
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done
),
keyboardActions = KeyboardActions(
onDone = {
if (text.isNotEmpty()) {
keyboardController?.hide()
photoPicker.launch(
PhotoPicker.Args(
PhotoPicker.Type.IMAGES_ONLY, 1
)
)
}
}
)
)
}
}
}
Usage
@Preview
@Composable
private fun ChipSampleAndTextLayoutSample() {
val backgroundColor = Green400.copy(alpha = .6f)
val chipDataSnapshotStateList = remember {
mutableStateListOf<ChipData>()
}
ChipAndTextFieldLayout(
modifier = Modifier.fillMaxWidth().padding(8.dp),
list = chipDataSnapshotStateList,
backgroundColor = backgroundColor,
onChipCreated = {
chipDataSnapshotStateList.add(it)
},
chip = { data: ChipData, index: Int->
MyChip(backgroundColor, data){
chipDataSnapshotStateList.removeAt(index)
}
}
)
}