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