Skip to content

Commit 4baf477

Browse files
Add pull to refresh snippets (#378)
* Add pull to refresh snippets * Apply Spotless
1 parent 591dfa5 commit 4baf477

File tree

1 file changed

+241
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)