Skip to content

Commit 513f3f6

Browse files
Add pull to refresh snippets
1 parent f67b849 commit 513f3f6

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package com.example.compose.snippets.components
2+
3+
import androidx.compose.animation.Crossfade
4+
import androidx.compose.animation.core.tween
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.fillMaxSize
7+
import androidx.compose.foundation.layout.size
8+
import androidx.compose.foundation.lazy.LazyColumn
9+
import androidx.compose.foundation.lazy.items
10+
import androidx.compose.material.icons.Icons
11+
import androidx.compose.material.icons.filled.CloudDownload
12+
import androidx.compose.material3.CircularProgressIndicator
13+
import androidx.compose.material3.ExperimentalMaterial3Api
14+
import androidx.compose.material3.Icon
15+
import androidx.compose.material3.ListItem
16+
import androidx.compose.material3.MaterialTheme
17+
import androidx.compose.material3.Text
18+
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
19+
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
20+
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator
21+
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.PositionalThreshold
22+
import androidx.compose.material3.pulltorefresh.PullToRefreshState
23+
import androidx.compose.material3.pulltorefresh.pullToRefreshIndicator
24+
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
25+
import androidx.compose.runtime.Composable
26+
import androidx.compose.runtime.getValue
27+
import androidx.compose.runtime.mutableIntStateOf
28+
import androidx.compose.runtime.mutableStateOf
29+
import androidx.compose.runtime.remember
30+
import androidx.compose.runtime.rememberCoroutineScope
31+
import androidx.compose.runtime.setValue
32+
import androidx.compose.ui.Alignment
33+
import androidx.compose.ui.Modifier
34+
import androidx.compose.ui.graphics.graphicsLayer
35+
import androidx.compose.ui.tooling.preview.Preview
36+
import androidx.compose.ui.unit.dp
37+
import com.example.compose.snippets.components.PullToRefreshIndicatorConstants.CROSSFADE_DURATION_MILLIS
38+
import com.example.compose.snippets.components.PullToRefreshIndicatorConstants.SPINNER_SIZE
39+
import kotlinx.coroutines.delay
40+
import kotlinx.coroutines.launch
41+
42+
private object PullToRefreshIndicatorConstants {
43+
const val CROSSFADE_DURATION_MILLIS = 100
44+
val SPINNER_SIZE = 16.dp
45+
}
46+
47+
@Preview
48+
@Composable
49+
fun PullToRefreshBasicPreview() {
50+
PullToRefreshStatefulWrapper { itemCount, isRefreshing, onRefresh ->
51+
val items = List(itemCount) { "Item ${itemCount - it}" }
52+
PullToRefreshBasicSample(items, isRefreshing, onRefresh)
53+
}
54+
}
55+
56+
@Preview
57+
@Composable
58+
fun PullToRefreshCustomStylePreview() {
59+
PullToRefreshStatefulWrapper { itemCount, isRefreshing, onRefresh ->
60+
val items = List(itemCount) { "Item ${itemCount - it}" }
61+
PullToRefreshCustomStyleSample(items, isRefreshing, onRefresh)
62+
}
63+
}
64+
65+
@Preview
66+
@Composable
67+
fun PullToRefreshCustomIndicatorPreview() {
68+
PullToRefreshStatefulWrapper { itemCount, isRefreshing, onRefresh ->
69+
val items = List(itemCount) { "Item ${itemCount - it}" }
70+
PullToRefreshCustomIndicatorSample(items, isRefreshing, onRefresh)
71+
}
72+
}
73+
74+
@OptIn(ExperimentalMaterial3Api::class)
75+
// [START android_compose_components_pull_to_refresh_basic]
76+
@Composable
77+
fun PullToRefreshBasicSample(
78+
items: List<String>,
79+
isRefreshing: Boolean,
80+
onRefresh: () -> Unit,
81+
modifier: Modifier = Modifier
82+
) {
83+
PullToRefreshBox(
84+
isRefreshing = isRefreshing,
85+
onRefresh = onRefresh,
86+
modifier = modifier
87+
) {
88+
LazyColumn(Modifier.fillMaxSize()) {
89+
items(items) {
90+
ListItem({ Text(text = it) })
91+
}
92+
}
93+
}
94+
}
95+
// [END android_compose_components_pull_to_refresh_basic]
96+
97+
@OptIn(ExperimentalMaterial3Api::class)
98+
// [START android_compose_components_pull_to_refresh_custom_style]
99+
@Composable
100+
fun PullToRefreshCustomStyleSample(
101+
items: List<String>,
102+
isRefreshing: Boolean,
103+
onRefresh: () -> Unit,
104+
modifier: Modifier = Modifier
105+
) {
106+
val state = rememberPullToRefreshState()
107+
108+
PullToRefreshBox(
109+
isRefreshing = isRefreshing,
110+
onRefresh = onRefresh,
111+
modifier = modifier,
112+
state = state,
113+
indicator = {
114+
Indicator(
115+
modifier = Modifier.align(Alignment.TopCenter),
116+
isRefreshing = isRefreshing,
117+
containerColor = MaterialTheme.colorScheme.primaryContainer,
118+
color = MaterialTheme.colorScheme.onPrimaryContainer,
119+
state = state
120+
)
121+
},
122+
) {
123+
LazyColumn(Modifier.fillMaxSize()) {
124+
items(items) {
125+
ListItem({ Text(text = it) })
126+
}
127+
}
128+
}
129+
}
130+
// [END android_compose_components_pull_to_refresh_custom_style]
131+
132+
@OptIn(ExperimentalMaterial3Api::class)
133+
// [START android_compose_components_pull_to_refresh_custom_indicator]
134+
@Composable
135+
fun PullToRefreshCustomIndicatorSample(
136+
items: List<String>,
137+
isRefreshing: Boolean,
138+
onRefresh: () -> Unit,
139+
modifier: Modifier = Modifier
140+
) {
141+
val state = rememberPullToRefreshState()
142+
143+
PullToRefreshBox(
144+
isRefreshing = isRefreshing,
145+
onRefresh = onRefresh,
146+
modifier = modifier,
147+
state = state,
148+
indicator = {
149+
MyCustomIndicator(
150+
state = state,
151+
isRefreshing = isRefreshing,
152+
modifier = Modifier.align(Alignment.TopCenter)
153+
)
154+
}
155+
) {
156+
LazyColumn(Modifier.fillMaxSize()) {
157+
items(items) {
158+
ListItem({ Text(text = it) })
159+
}
160+
}
161+
}
162+
}
163+
164+
// [START_EXCLUDE]
165+
@OptIn(ExperimentalMaterial3Api::class)
166+
// [END_EXCLUDE]
167+
@Composable
168+
fun MyCustomIndicator(
169+
state: PullToRefreshState,
170+
isRefreshing: Boolean,
171+
modifier: Modifier = Modifier,
172+
) {
173+
Box(
174+
modifier = modifier.pullToRefreshIndicator(
175+
state = state,
176+
isRefreshing = isRefreshing,
177+
containerColor = PullToRefreshDefaults.containerColor,
178+
threshold = PositionalThreshold
179+
),
180+
contentAlignment = Alignment.Center
181+
) {
182+
Crossfade(
183+
targetState = isRefreshing,
184+
animationSpec = tween(durationMillis = CROSSFADE_DURATION_MILLIS),
185+
modifier = Modifier.align(Alignment.Center)
186+
) { refreshing ->
187+
if (refreshing) {
188+
CircularProgressIndicator(Modifier.size(SPINNER_SIZE))
189+
} else {
190+
val distanceFraction = { state.distanceFraction.coerceIn(0f, 1f) }
191+
Icon(
192+
imageVector = Icons.Filled.CloudDownload,
193+
contentDescription = "Refresh",
194+
modifier = Modifier
195+
.size(18.dp)
196+
.graphicsLayer {
197+
val progress = distanceFraction()
198+
this.alpha = progress
199+
this.scaleX = progress
200+
this.scaleY = progress
201+
}
202+
)
203+
}
204+
}
205+
}
206+
}
207+
// [END android_compose_components_pull_to_refresh_custom_indicator]
208+
209+
@Composable
210+
fun PullToRefreshStatefulWrapper(
211+
content: @Composable (itemCount: Int, isRefreshing: Boolean, onRefresh: () -> Unit) -> Unit
212+
) {
213+
var itemCount by remember { mutableIntStateOf(15) }
214+
var isRefreshing by remember { mutableStateOf(false) }
215+
val coroutineScope = rememberCoroutineScope()
216+
val onRefresh: () -> Unit = {
217+
isRefreshing = true
218+
coroutineScope.launch {
219+
delay(1500)
220+
itemCount += 5
221+
isRefreshing = false
222+
}
223+
}
224+
content(itemCount, isRefreshing, onRefresh)
225+
}

0 commit comments

Comments
 (0)