How to select multiple items in LazyColumn and finally add the selected items in a seperate list.
GettingTags(tagsContent ={ productTags ->
val flattenList = productTags.flatMap {
it.tags_list
}
Log.i(TAG,"Getting the flattenList $flattenList")
LazyColumn{
items(flattenList){
ListItem(text = {Text(it) })
if(selectedTagItem) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Selected",
tint = Color.Green,
modifier = Modifier.size(20.dp)
)
}
}
}
})
Mutable variable state
var selectedTagItem by remember{
mutableStateOf(false)
}
First create a class with selected variable to toggle
#Immutable
data class MyItem(val text: String, val isSelected: Boolean = false)
Then create a SnapshotStateList via mutableStateListOf that contains all of the items, and can trigger recomposition when we update any item with new instance, add or remove items also. I used a ViewModel but it's not mandatory. We can toggle items using index or get selected items by filtering isSelected flag
class MyViewModel : ViewModel() {
val myItems = mutableStateListOf<MyItem>()
.apply {
repeat(15) {
add(MyItem(text = "Item$it"))
}
}
fun getSelectedItems() = myItems.filter { it.isSelected }
fun toggleSelection(index: Int) {
val item = myItems[index]
val isSelected = item.isSelected
if (isSelected) {
myItems[index] = item.copy(isSelected = false)
} else {
myItems[index] = item.copy(isSelected = true)
}
}
}
Create LazyColumn with key, key makes sure that only updated items are recomposed, as can be seen in performance document
#Composable
private fun SelectableLazyListSample(myViewModel: MyViewModel) {
val selectedItems = myViewModel.getSelectedItems().map { it.text }
Text(text = "Selected items: $selectedItems")
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(8.dp)
) {
itemsIndexed(
myViewModel.myItems,
key = { _, item: MyItem ->
item.hashCode()
}
) { index, item ->
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.Red, RoundedCornerShape(8.dp))
.clickable {
myViewModel.toggleSelection(index)
}
.padding(8.dp)
) {
Text("Item $index", color = Color.White, fontSize = 20.sp)
if (item.isSelected) {
Icon(
modifier = Modifier
.align(Alignment.CenterEnd)
.background(Color.White, CircleShape),
imageVector = Icons.Default.Check,
contentDescription = "Selected",
tint = Color.Green,
)
}
}
}
}
}
Result
Related
PROBLEM ::: I want to create a lazy column where I can select or deselect only one option at a time. Right now, whenever I click on row component inside lazy column, all the rows get selected.
CODE :::
#Composable
fun LazyColumnWithSelection() {
var isSelected by remember {
mutableStateOf(false)
}
var selectedIndex by remember { mutableStateOf(0) }
val onItemClick = { index: Int -> selectedIndex = index }
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
items(100) { index ->
Row(modifier = Modifier
.fillMaxWidth()
.clickable {
onItemClick.invoke(index)
if (selectedIndex == index) {
isSelected = !isSelected
}
}
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically) {
Text(text = "Item $index", modifier = Modifier.padding(12.dp), color = Color.White)
if (isSelected) {
Icon(imageVector = Icons.Default.Check,
contentDescription = "Selected",
tint = Color.Green,
modifier = Modifier.size(20.dp))
}
}
}
}
}
CURRENT RESULT :::
Before Clicking ->
After Clicking ->
You can see all the items are getting selected but I should be able to select or deselect one item at a time not all.
I tried to use remember state for selection but I think I'm doing wrong something in the index selection or maybe if statement.
This should probably give you a head start.
So we have 4 components here:
Data Class
Class state holder
Item Composable
ItemList Composable
ItemData
data class ItemData(
val id : Int,
val display: String,
val isSelected: Boolean = false
)
State holder
class ItemDataState {
val itemDataList = mutableStateListOf(
ItemData(1, "Item 1"),
ItemData(2, "Item 2"),
ItemData(3, "Item 3"),
ItemData(4, "Item 4"),
ItemData(5, "Item 5")
)
// were updating the entire list in a single pass using its iterator
fun onItemSelected(selectedItemData: ItemData) {
val iterator = itemDataList.listIterator()
while (iterator.hasNext()) {
val listItem = iterator.next()
iterator.set(
if (listItem.id == selectedItemData.id) {
selectedItemData
} else {
listItem.copy(isSelected = false)
}
)
}
}
}
Item Composable
#Composable
fun ItemDisplay(
itemData: ItemData,
onCheckChanged: (ItemData) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(80.dp)
.border(BorderStroke(Dp.Hairline, Color.Gray)),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = if (itemData.isSelected) "I'm selected!" else itemData.display)
Checkbox(
checked = itemData.isSelected,
onCheckedChange = {
onCheckChanged(itemData.copy(isSelected = !itemData.isSelected))
}
)
}
}
Finally the ItemList (LazyColumn)
#Composable
fun ItemList() {
val itemDataState = remember { ItemDataState() }
LazyColumn {
items(itemDataState.itemDataList, key = { it.id } ) { item ->
ItemDisplay(
itemData = item,
onCheckChanged = itemDataState::onItemSelected
)
}
}
}
All of these are copy-and-pasteable so you can run it quickly. The codes should be simple enough for you to dissect them easily and use them as a reference for your own use-case.
Notice that we use a data class here which has an id property to be unique and we're using it as a key parameter for LazyColumn's item.
I usually implement my UI collection components with a unique identifier to save me from potential headaches such as UI showing/removing/recycling wrong items.
Remember index instead of Boolean (isSelected).
I have a list with 10 items one of them have this elements "rankingCurrentPlace", "rankingPastPlace" and "isUser:true".
What i need to do its an animation on the lazycolumn if the api esponse is like this
"isUser:true", "rankingPastPlace:3" , "rankingCurrentPlace:7"
i need to show an animation in the list where the row starts in the third place and descend to the seventh place
is there a way to do this?
this is what I actually have
LazyColumn(
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
) {
items(
items = leaderboard,
key = { leaderBoard ->
leaderBoard.rankingPlace
}
) { leaderBoard ->
RowComposable( modifier = Modifier
.fillMaxWidth(),
topicsItem = leaderBoard,)
}
This answer works except when swapping first item with any item even with basic swap function without animation. I think it would be better to ask a new question about why swapping first item doesn't work or if it is bug. Other than that works as expected. If you need to move to items that are not in screen you can lazyListState.layoutInfo.visibleItemsInfo and compare with initial item and scroll to it before animation
1.Have a SnapshotStateList of data to trigger recomposition when we swap 2 items
class MyData(val uuid: String, val value: String)
val items: SnapshotStateList<MyData> = remember {
mutableStateListOf<MyData>().apply {
repeat(20) {
add(MyData( uuid = UUID.randomUUID().toString(), "Row $it"))
}
}
}
2.Function to swap items
private fun swap(list: SnapshotStateList<MyData>, from: Int, to: Int) {
val size = list.size
if (from in 0 until size && to in 0 until size) {
val temp = list[from]
list[from] = list[to]
list[to] = temp
}
}
3.Function to swap items one by one. There is a bug with swapping first item. Even if it's with function above when swapping first item other one moves up without showing animation via Modififer.animateItemPlacement().
#Composable
private fun animatedSwap(
lazyListState: LazyListState,
items: SnapshotStateList<MyData>,
from: Int,
to: Int,
onFinish: () -> Unit
) {
LaunchedEffect(key1 = Unit) {
val difference = from - to
val increasing = difference < 0
var currentValue: Int = from
repeat(abs(difference)) {
val temp = currentValue
if (increasing) {
currentValue++
} else {
currentValue--
}
swap(items, temp, currentValue)
if (!increasing && currentValue == 0) {
delay(300)
lazyListState.scrollToItem(0)
}
delay(350)
}
onFinish()
}
}
4.List with items that have Modifier.animateItemPlacement()
val lazyListState = rememberLazyListState()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
state = lazyListState,
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(
items = items,
key = {
it.uuid
}
) {
Row(
modifier = Modifier
.animateItemPlacement(
tween(durationMillis = 200)
)
.shadow(1.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.size(50.dp),
painter = painterResource(id = R.drawable.landscape1),
contentScale = ContentScale.FillBounds,
contentDescription = null
)
Spacer(modifier = Modifier.width(10.dp))
Text(it.value, fontSize = 18.sp)
}
}
}
Demo
#OptIn(ExperimentalFoundationApi::class)
#Composable
private fun AnimatedList() {
Column(modifier = Modifier.fillMaxSize()) {
val items: SnapshotStateList<MyData> = remember {
mutableStateListOf<MyData>().apply {
repeat(20) {
add(MyData(uuid = UUID.randomUUID().toString(), "Row $it"))
}
}
}
val lazyListState = rememberLazyListState()
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
state = lazyListState,
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(
items = items,
key = {
it.uuid
}
) {
Row(
modifier = Modifier
.animateItemPlacement(
tween(durationMillis = 200)
)
.shadow(1.dp, RoundedCornerShape(8.dp))
.background(Color.White)
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
modifier = Modifier
.clip(RoundedCornerShape(10.dp))
.size(50.dp),
painter = painterResource(id = R.drawable.landscape1),
contentScale = ContentScale.FillBounds,
contentDescription = null
)
Spacer(modifier = Modifier.width(10.dp))
Text(it.value, fontSize = 18.sp)
}
}
}
var fromString by remember {
mutableStateOf("7")
}
var toString by remember {
mutableStateOf("3")
}
var animate by remember { mutableStateOf(false) }
if (animate) {
val from = try {
Integer.parseInt(fromString)
} catch (e: Exception) {
0
}
val to = try {
Integer.parseInt(toString)
} catch (e: Exception) {
0
}
animatedSwap(
lazyListState = lazyListState,
items = items,
from = from,
to = to
) {
animate = false
}
}
Row(modifier = Modifier.fillMaxWidth()) {
TextField(
value = fromString,
onValueChange = {
fromString = it
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
TextField(
value = toString,
onValueChange = {
toString = it
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
)
}
Button(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
onClick = {
animate = true
}
) {
Text("Swap")
}
}
}
Edit: Animating with Animatable
Another method for animating is using Animatable with Integer vector.
val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })
val coroutineScope = rememberCoroutineScope()
val animatable = remember { Animatable(0, IntToVector) }
And can be used as
private fun alternativeAnimate(
from: Int,
to: Int,
coroutineScope: CoroutineScope,
animatable: Animatable<Int, AnimationVector1D>,
items: SnapshotStateList<MyData>
) {
val difference = from - to
var currentValue: Int = from
coroutineScope.launch {
animatable.snapTo(from)
animatable.animateTo(to,
tween(350 * abs(difference), easing = LinearEasing),
block = {
val nextValue = this.value
if (abs(currentValue -nextValue) ==1) {
swap(items, currentValue, nextValue)
currentValue = nextValue
}
}
)
}
}
on button click, i'm getting values from TextField fo i convert from String
Button(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
onClick = {
val from = try {
Integer.parseInt(fromString)
} catch (e: Exception) {
0
}
val to = try {
Integer.parseInt(toString)
} catch (e: Exception) {
0
}
alternativeAnimate(from, to, coroutineScope, animatable, items)
}
) {
Text("Swap")
}
Result
I suggest you to get your items from a data class. If your other items does not contain the variables you mentioned you can make them nullable in data class and put a condition checker in your lazycolumn
Like this
data class Items(
val otherItems: Other,
val rankingCurrentPlace: Int?,
val rankingLastPlace: Int?,
val isUser: Boolean?
)
Then you can make a list from this data class and pass it to lazycolumn
LazyColumn{
items(list){
(elements with condition)
}
}
I have a shopping list item composable that is not taking up the entire width of the parent, as you can see below with the red border. I want it to be flush against the parent's edge. And why is there some space or padding just before the checkbox? What needs to be modified?
Composable
#Composable
fun ShoppingListScreenItem(
itemName: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(
modifier = Modifier.fillMaxWidth().border(2.dp, Red),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
modifier = Modifier.padding(0.dp),
checked = checked,
onCheckedChange = onCheckedChange
)
Text(
modifier = Modifier.padding(start = 8.dp),
fontWeight = FontWeight.Bold,
text = itemName
)
}
}
Parent Composable
#Composable
fun ShoppingListScreen(
navController: NavHostController,
shoppingListScreenViewModel: ShoppingListScreenViewModel
) {
val scope = rememberCoroutineScope()
val name = stringResource(id = R.string.item_name)
val nameError = stringResource(id = R.string.item_IsNameError)
val category = stringResource(id = R.string.item_category)
val categoryError = stringResource(id = R.string.item_IsCategoryError)
val focusManager = LocalFocusManager.current
val allItems =
shoppingListScreenViewModel.shoppingListItemsState.value?.collectAsLazyPagingItems()
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = "AppBar") },
backgroundColor = Color.White,
navigationIcon = if (navController.previousBackStackEntry != null) {
{
IconButton(onClick = { navController.navigateUp() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back"
)
}
}
} else {
null
}
)
},
floatingActionButton = {
FloatingActionButton(
onClick = {
shoppingListScreenViewModel.setStateValue("ShowAddItemDialog", true)
},
backgroundColor = Color.Blue,
contentColor = Color.White
) {
Icon(Icons.Filled.Add, "")
}
},
// Defaults to false
isFloatingActionButtonDocked = false,
bottomBar = { BottomNavigationBar(navController = navController) }
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
LazyColumn(
contentPadding = PaddingValues(
vertical = 8.dp,
horizontal = 8.dp
)
) {
//todo change it to non null
items(allItems!!) { item ->
ShoppingListScreenItem(
itemName = item?.name!!,
checked = item.isInCart
) { isChecked ->
scope.launch {
shoppingListScreenViewModel.changeItemChecked(item, isChecked)
}
}
}
}
if (shoppingListScreenViewModel.shoppingListScreenState.value.showAddItemDialog) {
OnTheFlyAddItemDialog(
shoppingListScreenViewModel = shoppingListScreenViewModel,
focusManager = focusManager,
navController = navController,
onDismiss = {
shoppingListScreenViewModel.setStateValue(name, "")
shoppingListScreenViewModel.setStateValue(nameError, false)
shoppingListScreenViewModel.setStateValue(category, "")
shoppingListScreenViewModel.setStateValue(categoryError, false)
shoppingListScreenViewModel.setStateValue("ShowAddItemDialog", false)
}
)
{
scope.launch {
shoppingListScreenViewModel.addShoppingListItemToDb()
shoppingListScreenViewModel.setStateValue("ShowAddItemDialog", false)
shoppingListScreenViewModel.setStateValue(name, "")
shoppingListScreenViewModel.setStateValue(nameError, false)
shoppingListScreenViewModel.setStateValue(category, "")
shoppingListScreenViewModel.setStateValue(categoryError, false)
// triggerCount++
}
}
}
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = {
navController.navigate(NavScreens.AddItemScreen.route) {
popUpTo(NavScreens.AddItemScreen.route) {
inclusive = true
}
}
}
) {
Text("Goto add item screen")
}
}
}
}
You set a contentPadding in your LazyColumn that is responsible for the spaces. Remove it, or set it to zero.
LazyColumn(
contentPadding = PaddingValues(
vertical = 8.dp,
horizontal = 0.dp
)
)
The padding around the Checkbox depends on the minimumTouchTargetSize defined when the onClick is not null with a hardcoded value of 48.dp.
You can override it using:
CompositionLocalProvider(
LocalMinimumTouchTargetEnforcement provides false,
) {
Checkbox(
modifier = Modifier.padding(0.dp), //4.dp
checked = false,
onCheckedChange = {}
)
}
but it is not the best choice in term of accessibility.
.
The space around the Row depends on the contentPadding defined in the LazyColumn.
var noteListState by remember { mutableStateOf(listOf("Drink water", "Walk")) }
This is my list of items. Any leads would be appreciated
To Get a Main UI with list and searchview
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen()
}
}
For Main()
#Composable
fun MainScreen() {
val textState = remember { mutableStateOf(TextFieldValue("")) }
Column {
SearchView(textState)
ItemList(state = textState)
}
}
For Serchview and list
#Composable
fun SearchView(state: MutableState<TextFieldValue>) {
TextField(
value = state.value,
onValueChange = { value ->
state.value = value
},
modifier = Modifier.fillMaxWidth(),
textStyle = TextStyle(color = Color.White, fontSize = 18.sp),
leadingIcon = {
Icon(
Icons.Default.Search,
contentDescription = "",
modifier = Modifier
.padding(15.dp)
.size(24.dp)
)
},
trailingIcon = {
if (state.value != TextFieldValue("")) {
IconButton(
onClick = {
state.value =
TextFieldValue("") // Remove text from TextField when you press the 'X' icon
}
) {
Icon(
Icons.Default.Close,
contentDescription = "",
modifier = Modifier
.padding(15.dp)
.size(24.dp)
)
}
}
},
singleLine = true,
shape = RectangleShape, // The TextFiled has rounded corners top left and right by default
colors = TextFieldDefaults.textFieldColors(
textColor = Color.White,
cursorColor = Color.White,
leadingIconColor = Color.White,
trailingIconColor = Color.White,
backgroundColor = MaterialTheme.colors.primary,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent
)
)
}
#Composable
fun ItemList(state: MutableState<TextFieldValue>) {
val items by remember { mutableStateOf(listOf("Drink water", "Walk")) }
var filteredItems: List<String>
LazyColumn(modifier = Modifier.fillMaxWidth()) {
val searchedText = state.value.text
filteredItems = if (searchedText.isEmpty()) {
items
} else {
val resultList = ArrayList<String>()
for (item in items) {
if (item.lowercase(Locale.getDefault())
.contains(searchedText.lowercase(Locale.getDefault()))
) {
resultList.add(item)
}
}
resultList
}
items(filteredItems) { filteredItem ->
ItemListItem(
ItemText = filteredItem,
onItemClick = { /*Click event code needs to be implement*/
}
)
}
}
}
#Composable
fun ItemListItem(ItemText: String, onItemClick: (String) -> Unit) {
Row(
modifier = Modifier
.clickable(onClick = { onItemClick(ItemText) })
.background(colorResource(id = R.color.purple_700))
.height(57.dp)
.fillMaxWidth()
.padding(PaddingValues(8.dp, 16.dp))
) {
Text(text = ItemText, fontSize = 18.sp, color = Color.White)
}
}
For More Detailed answer you can refer this link
Please could you explain more, this question is a bit short and unclear.
One way (i think you could do) is just to loop through the list and check if a element (of your list) contains that text.
val filterPattern = text.toString().lowercase(Locale.getDefault()) // text you are looking for
for (item in copyList) { // first make a copy of the list
if (item.name.lowercase(Locale.getDefault()).contains(filterPattern)) {
filteredList.add(item) // if the item contains that text add it to the list
}
}
... // here you then override noteListState
After you have made the filteredList you can just override the noteListState. Make sure to make a copy of that list beforehand and set it back when you don't want to show the filtered results.
I get lists from API and display them in LazyColumn with the expendable scroll. All list items are initially in a not expended state and while the user clicks them, I want to show the detail of that item in the expendable item view.
When my list items are in expended state, scrolling is smooth but when the initial state ( not expended ) scrolling is not smooth. I saved expendable state with rememberSavable and update when user clicks the item.
These are my code:
My viewModel
#HiltViewModel
class MainViewModel #Inject constructor(
private val appRepository: AppRepository
) : ViewModel() {
val breeds: Flow<PagingData<BreedItem>> = Pager(
config = PagingConfig(pageSize = 3)
) {
BreedsPagingDataSource(appRepository)
}.flow.cachedIn(viewModelScope)
}
My ListScreen
#Composable
fun DoggoListScreen() {
val vm: MainViewModel = hiltViewModel()
DoggoListView(
breedItems = vm.breeds,
)
}
My ListView
#Composable
fun DoggoListView(
breedItems: Flow<PagingData<BreedItem>>,
) {
val breed: LazyPagingItems<BreedItem> = breedItems.collectAsLazyPagingItems()
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(4.dp)
) {
items(breed) { item ->
FoldAbleItem(
item!!.toVo(),
onClick = {
//todo : do some click action
})
}
breed.apply {
when {
loadState.refresh is LoadState.Loading -> {
item {
LoadingView(modifier = Modifier.fillParentMaxSize())
}
}
loadState.append is LoadState.Loading -> {
item {
LoadingItem()
}
}
loadState.refresh is LoadState.Error -> {
val e = breed.loadState.refresh as LoadState.Error
item {
ErrorItem(
message = e.error.localizedMessage!!,
modifier = Modifier.fillParentMaxSize(),
onClickRetry = { retry() }
)
}
}
loadState.append is LoadState.Error -> {
val e = breed.loadState.append as LoadState.Error
item {
ErrorItem(
message = e.error.localizedMessage!!,
onClickRetry = { retry() }
)
}
}
}
}
}
}
My ItemView
#Composable
fun FoldAbleItem(
breed: Breed,
onClick: () -> Unit
) {
//you can save expandedState by remember if you don't want to save it across scrolling
var expandedState by rememberSaveable {
mutableStateOf(breed.isExpended)
}
val rotationState by animateFloatAsState(
targetValue = if (expandedState) 180f else 0f
)
val image = rememberCoilPainter(
request = breed.url,
fadeIn = true,
fadeInDurationMs = 500
)
Card(
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
.animateContentSize(
animationSpec = tween(
durationMillis = 300,
easing = LinearOutSlowInEasing
)
)
.clickable(
onClick = onClick
),
shape = MaterialTheme.shapes.medium,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(12.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.weight(1f),
text = breed.name,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.h5
)
IconButton(
modifier = Modifier.rotate(rotationState),
onClick = { expandedState = !expandedState }
) {
Icon(
imageVector = Icons.Default.ArrowDropDown,
contentDescription = "Drop Arrow"
)
}
}
if (expandedState) {
Text(
text = breed.temperament ?: "movie.bred_for",
style = MaterialTheme.typography.body2
)
Spacer(modifier = Modifier.height(8.dp))
Image(
painter = image,
contentDescription = null,
//16:9 = 1.7f
modifier = Modifier
.aspectRatio(1.7f, false)
.clip(MaterialTheme.shapes.medium)
,
contentScale = ContentScale.FillWidth
)
breed.description?.let {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = it,
style = MaterialTheme.typography.body2
)
}
}
}
}
}
I think I am doing well with implementation LazyColumn and DataSet. But I am not sure with the expendable state in rememberSavable. Please emphersize the part.
//you can save expandedState by remember if you don't want to save it across scrolling
var expandedState by rememberSaveable {
mutableStateOf(breed.isExpended)
}
val rotationState by animateFloatAsState(
targetValue = if (expandedState) 180f else 0f
)
val image = rememberCoilPainter(
request = breed.url,
fadeIn = true,
fadeInDurationMs = 500
)