Skip to content

Commit 2af26d9

Browse files
Snippet for animated sorted list with add/remove buttons. (#381)
* Snippet for animated sorted list with add/remove buttons. * Apply Spotless * Simplify adding an item to displayedItems * Use ViewModel to correctly extract business logic from UI --------- Co-authored-by: jakeroseman <jakeroseman@users.noreply.github.com> Co-authored-by: Jolanda Verhoef <JolandaVerhoef@users.noreply.github.com>
1 parent e9116f5 commit 2af26d9

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
* Copyright 2024 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.compose.snippets.lists
18+
19+
import android.util.Log
20+
import androidx.compose.foundation.layout.Arrangement
21+
import androidx.compose.foundation.layout.Column
22+
import androidx.compose.foundation.layout.Row
23+
import androidx.compose.foundation.layout.Spacer
24+
import androidx.compose.foundation.layout.fillMaxSize
25+
import androidx.compose.foundation.layout.fillMaxWidth
26+
import androidx.compose.foundation.layout.padding
27+
import androidx.compose.foundation.lazy.LazyColumn
28+
import androidx.compose.foundation.lazy.items
29+
import androidx.compose.material3.Button
30+
import androidx.compose.material3.ListItem
31+
import androidx.compose.material3.Scaffold
32+
import androidx.compose.material3.SegmentedButton
33+
import androidx.compose.material3.SegmentedButtonDefaults
34+
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
35+
import androidx.compose.material3.Text
36+
import androidx.compose.runtime.Composable
37+
import androidx.compose.runtime.getValue
38+
import androidx.compose.runtime.mutableIntStateOf
39+
import androidx.compose.runtime.remember
40+
import androidx.compose.runtime.setValue
41+
import androidx.compose.ui.Modifier
42+
import androidx.compose.ui.tooling.preview.Preview
43+
import androidx.compose.ui.unit.dp
44+
import androidx.lifecycle.ViewModel
45+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
46+
import kotlinx.coroutines.flow.MutableStateFlow
47+
import kotlinx.coroutines.flow.StateFlow
48+
49+
class AnimatedOrderedListViewModel : ViewModel() {
50+
private val _data = listOf("One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten")
51+
private val _displayedItems: MutableStateFlow<List<String>> = MutableStateFlow(_data)
52+
val displayedItems: StateFlow<List<String>> = _displayedItems
53+
54+
fun resetOrder() {
55+
_displayedItems.value = _data.filter { it in _displayedItems.value }
56+
}
57+
58+
fun sortAlphabetically() {
59+
_displayedItems.value = _displayedItems.value.sortedBy { it }
60+
}
61+
62+
fun sortByLength() {
63+
_displayedItems.value = _displayedItems.value.sortedBy { it.length }
64+
}
65+
66+
fun addItem() {
67+
// Avoid duplicate items
68+
val remainingItems = _data.filter { it !in _displayedItems.value }
69+
if (remainingItems.isNotEmpty()) _displayedItems.value += remainingItems.first()
70+
}
71+
72+
fun removeItem() {
73+
_displayedItems.value = _displayedItems.value.dropLast(1)
74+
}
75+
}
76+
77+
@Composable
78+
fun AnimatedOrderedListScreen(
79+
viewModel: AnimatedOrderedListViewModel,
80+
modifier: Modifier = Modifier,
81+
) {
82+
val displayedItems by viewModel.displayedItems.collectAsStateWithLifecycle()
83+
84+
ListAnimatedItemsExample(
85+
displayedItems,
86+
onAddItem = viewModel::addItem,
87+
onRemoveItem = viewModel::removeItem,
88+
resetOrder = viewModel::resetOrder,
89+
onSortAlphabetically = viewModel::sortAlphabetically,
90+
onSortByLength = viewModel::sortByLength,
91+
modifier = modifier
92+
)
93+
}
94+
95+
// [START android_compose_layouts_list_listanimateditems]
96+
@Composable
97+
fun ListAnimatedItems(
98+
items: List<String>,
99+
modifier: Modifier = Modifier
100+
) {
101+
LazyColumn(modifier) {
102+
// Use a unique key per item, so that animations work as expected.
103+
items(items, key = { it }) {
104+
ListItem(
105+
headlineContent = { Text(it) },
106+
modifier = Modifier
107+
.animateItem(
108+
// Optionally add custom animation specs
109+
)
110+
.fillParentMaxWidth()
111+
.padding(horizontal = 8.dp, vertical = 0.dp),
112+
)
113+
}
114+
}
115+
}
116+
// [END android_compose_layouts_list_listanimateditems]
117+
118+
// [START android_compose_layouts_list_listanimateditemsexample]
119+
@Composable
120+
private fun ListAnimatedItemsExample(
121+
data: List<String>,
122+
modifier: Modifier = Modifier,
123+
onAddItem: () -> Unit = {},
124+
onRemoveItem: () -> Unit = {},
125+
resetOrder: () -> Unit = {},
126+
onSortAlphabetically: () -> Unit = {},
127+
onSortByLength: () -> Unit = {},
128+
) {
129+
val canAddItem = data.size < 10
130+
val canRemoveItem = data.isNotEmpty()
131+
132+
Scaffold(modifier) { paddingValues ->
133+
Column(
134+
modifier = Modifier
135+
.padding(paddingValues)
136+
.fillMaxSize()
137+
) {
138+
// Buttons that change the value of displayedItems.
139+
AddRemoveButtons(canAddItem, canRemoveItem, onAddItem, onRemoveItem)
140+
OrderButtons(resetOrder, onSortAlphabetically, onSortByLength)
141+
142+
// List that displays the values of displayedItems.
143+
ListAnimatedItems(data)
144+
}
145+
}
146+
}
147+
// [END android_compose_layouts_list_listanimateditemsexample]
148+
149+
// [START android_compose_layouts_list_addremovebuttons]
150+
@Composable
151+
private fun AddRemoveButtons(
152+
canAddItem: Boolean,
153+
canRemoveItem: Boolean,
154+
onAddItem: () -> Unit,
155+
onRemoveItem: () -> Unit
156+
) {
157+
Row(
158+
modifier = Modifier.fillMaxWidth(),
159+
horizontalArrangement = Arrangement.Center
160+
) {
161+
Button(enabled = canAddItem, onClick = onAddItem) {
162+
Text("Add Item")
163+
}
164+
Spacer(modifier = Modifier.padding(25.dp))
165+
Button(enabled = canRemoveItem, onClick = onRemoveItem) {
166+
Text("Delete Item")
167+
}
168+
}
169+
}
170+
// [END android_compose_layouts_list_addremovebuttons]
171+
172+
// [START android_compose_layouts_list_orderbuttons]
173+
@Composable
174+
private fun OrderButtons(
175+
resetOrder: () -> Unit,
176+
orderAlphabetically: () -> Unit,
177+
orderByLength: () -> Unit
178+
) {
179+
Row(
180+
modifier = Modifier.fillMaxWidth(),
181+
horizontalArrangement = Arrangement.Center
182+
) {
183+
var selectedIndex by remember { mutableIntStateOf(0) }
184+
val options = listOf("Reset", "Alphabetical", "Length")
185+
186+
SingleChoiceSegmentedButtonRow {
187+
options.forEachIndexed { index, label ->
188+
SegmentedButton(
189+
shape = SegmentedButtonDefaults.itemShape(
190+
index = index,
191+
count = options.size
192+
),
193+
onClick = {
194+
Log.d("AnimatedOrderedList", "selectedIndex: $selectedIndex")
195+
selectedIndex = index
196+
when (options[selectedIndex]) {
197+
"Reset" -> resetOrder()
198+
"Alphabetical" -> orderAlphabetically()
199+
"Length" -> orderByLength()
200+
}
201+
},
202+
selected = index == selectedIndex
203+
) {
204+
Text(label)
205+
}
206+
}
207+
}
208+
}
209+
}
210+
// [END android_compose_layouts_list_orderbuttons]
211+
212+
@Preview
213+
@Composable
214+
fun AnimatedOrderedListScreenPreview() {
215+
val viewModel = remember { AnimatedOrderedListViewModel() }
216+
AnimatedOrderedListScreen(viewModel)
217+
}

0 commit comments

Comments
 (0)