Skip to content

Commit ff30a8b

Browse files
canerkaselercanerkaseler
authored andcommitted
Completed version of shimmer loading animation.
1 parent 6760c75 commit ff30a8b

File tree

4 files changed

+375
-19
lines changed

4 files changed

+375
-19
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.canerkaseler.shimmerloadinganimation
2+
3+
import androidx.compose.animation.core.LinearEasing
4+
import androidx.compose.animation.core.RepeatMode
5+
import androidx.compose.animation.core.animateFloat
6+
import androidx.compose.animation.core.infiniteRepeatable
7+
import androidx.compose.animation.core.rememberInfiniteTransition
8+
import androidx.compose.animation.core.tween
9+
import androidx.compose.foundation.background
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.composed
12+
import androidx.compose.ui.geometry.Offset
13+
import androidx.compose.ui.graphics.Brush
14+
import androidx.compose.ui.graphics.Color
15+
16+
fun Modifier.shimmerLoadingAnimation(
17+
isLoadingCompleted: Boolean = true,
18+
isLightModeActive: Boolean = true,
19+
widthOfShadowBrush: Int = 500,
20+
angleOfAxisY: Float = 270f,
21+
durationMillis: Int = 1000,
22+
): Modifier {
23+
if (isLoadingCompleted) {
24+
return this
25+
}
26+
else {
27+
return composed {
28+
val shimmerColors = ShimmerAnimationData(isLightMode = isLightModeActive).getColours()
29+
30+
val transition = rememberInfiniteTransition(label = "")
31+
32+
val translateAnimation = transition.animateFloat(
33+
initialValue = 0f,
34+
targetValue = (durationMillis + widthOfShadowBrush).toFloat(),
35+
animationSpec = infiniteRepeatable(
36+
animation = tween(
37+
durationMillis = durationMillis,
38+
easing = LinearEasing,
39+
),
40+
repeatMode = RepeatMode.Restart,
41+
),
42+
label = "Shimmer loading animation",
43+
)
44+
45+
this.background(
46+
brush = Brush.linearGradient(
47+
colors = shimmerColors,
48+
start = Offset(x = translateAnimation.value - widthOfShadowBrush, y = 0.0f),
49+
end = Offset(x = translateAnimation.value, y = angleOfAxisY),
50+
),
51+
)
52+
}
53+
}
54+
}
55+
56+
data class ShimmerAnimationData(
57+
private val isLightMode: Boolean
58+
) {
59+
fun getColours(): List<Color> {
60+
return if (isLightMode) {
61+
val color = Color.White
62+
63+
listOf(
64+
color.copy(alpha = 0.3f),
65+
color.copy(alpha = 0.5f),
66+
color.copy(alpha = 1.0f),
67+
color.copy(alpha = 0.5f),
68+
color.copy(alpha = 0.3f),
69+
)
70+
} else {
71+
val color = Color.Black
72+
73+
listOf(
74+
color.copy(alpha = 0.0f),
75+
color.copy(alpha = 0.3f),
76+
color.copy(alpha = 0.5f),
77+
color.copy(alpha = 0.3f),
78+
color.copy(alpha = 0.0f),
79+
)
80+
}
81+
}
82+
}
83+
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
package com.canerkaseler.shimmerloadinganimation
2+
3+
import androidx.compose.foundation.BorderStroke
4+
import androidx.compose.foundation.background
5+
import androidx.compose.foundation.border
6+
import androidx.compose.foundation.layout.Box
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.Spacer
10+
import androidx.compose.foundation.layout.fillMaxSize
11+
import androidx.compose.foundation.layout.fillMaxWidth
12+
import androidx.compose.foundation.layout.height
13+
import androidx.compose.foundation.layout.padding
14+
import androidx.compose.foundation.layout.size
15+
import androidx.compose.foundation.shape.CircleShape
16+
import androidx.compose.foundation.shape.RoundedCornerShape
17+
import androidx.compose.material3.Button
18+
import androidx.compose.material3.ButtonDefaults
19+
import androidx.compose.material3.Text
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.setValue
25+
import androidx.compose.ui.Alignment
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.draw.clip
28+
import androidx.compose.ui.graphics.Color
29+
import androidx.compose.ui.tooling.preview.Preview
30+
import androidx.compose.ui.unit.dp
31+
32+
@Composable
33+
fun HomeScreen() {
34+
35+
var isLoadingCompleted by remember { mutableStateOf(true) }
36+
var isLightModeActive by remember { mutableStateOf(true) }
37+
38+
Box(
39+
modifier = Modifier
40+
.fillMaxSize()
41+
.background(color = if (isLightModeActive) Color.White else Color.Black)
42+
.border(border = BorderStroke(width = 4.dp, color = Color.Black))
43+
.padding(48.dp)
44+
) {
45+
Column(
46+
modifier = Modifier.align(alignment = Alignment.TopCenter)
47+
) {
48+
49+
Column {
50+
ComponentRectangle(
51+
isLoadingCompleted = isLoadingCompleted,
52+
isLightModeActive = isLightModeActive,
53+
)
54+
Spacer(modifier = Modifier.padding(8.dp))
55+
ComponentRectangleLineLong(
56+
isLoadingCompleted = isLoadingCompleted,
57+
isLightModeActive = isLightModeActive,
58+
)
59+
Spacer(modifier = Modifier.padding(4.dp))
60+
ComponentRectangleLineShort(
61+
isLoadingCompleted = isLoadingCompleted,
62+
isLightModeActive = isLightModeActive,
63+
)
64+
}
65+
66+
Spacer(modifier = Modifier.padding(24.dp))
67+
68+
Row {
69+
ComponentCircle(
70+
isLoadingCompleted = isLoadingCompleted,
71+
isLightModeActive = isLightModeActive,
72+
)
73+
Spacer(modifier = Modifier.padding(4.dp))
74+
Column {
75+
Spacer(modifier = Modifier.padding(8.dp))
76+
ComponentRectangleLineLong(
77+
isLoadingCompleted = isLoadingCompleted,
78+
isLightModeActive = isLightModeActive,
79+
)
80+
Spacer(modifier = Modifier.padding(4.dp))
81+
ComponentRectangleLineShort(
82+
isLoadingCompleted = isLoadingCompleted,
83+
isLightModeActive = isLightModeActive,
84+
)
85+
}
86+
}
87+
Spacer(modifier = Modifier.padding(24.dp))
88+
89+
Row {
90+
ComponentSquare(
91+
isLoadingCompleted = isLoadingCompleted,
92+
isLightModeActive = isLightModeActive,
93+
)
94+
Spacer(modifier = Modifier.padding(4.dp))
95+
Column {
96+
Spacer(modifier = Modifier.padding(8.dp))
97+
ComponentRectangleLineLong(
98+
isLoadingCompleted = isLoadingCompleted,
99+
isLightModeActive = isLightModeActive,
100+
)
101+
Spacer(modifier = Modifier.padding(4.dp))
102+
ComponentRectangleLineShort(
103+
isLoadingCompleted = isLoadingCompleted,
104+
isLightModeActive = isLightModeActive,
105+
)
106+
}
107+
}
108+
}
109+
110+
Column(
111+
modifier = Modifier.align(alignment = Alignment.BottomCenter),
112+
) {
113+
114+
ContentLoadingButton(
115+
modifier = Modifier.align(alignment = Alignment.CenterHorizontally),
116+
isLightMode = isLightModeActive,
117+
isLoadingCompleted = isLoadingCompleted,
118+
onClick = {
119+
isLoadingCompleted = !isLoadingCompleted
120+
}
121+
)
122+
123+
Spacer(modifier = Modifier.padding(8.dp))
124+
125+
ContentModeButton(
126+
modifier = Modifier.align(alignment = Alignment.CenterHorizontally),
127+
onClick = {
128+
isLightModeActive = !isLightModeActive
129+
},
130+
isLightMode = isLightModeActive
131+
)
132+
}
133+
}
134+
}
135+
136+
@Composable
137+
fun ComponentCircle(
138+
isLoadingCompleted: Boolean,
139+
isLightModeActive: Boolean,
140+
) {
141+
Box(
142+
modifier = Modifier
143+
.background(color = Color.LightGray, shape = CircleShape)
144+
.size(100.dp)
145+
.shimmerLoadingAnimation(
146+
isLoadingCompleted = isLoadingCompleted,
147+
isLightModeActive = isLightModeActive,
148+
)
149+
)
150+
}
151+
152+
@Composable
153+
fun ComponentSquare(
154+
isLoadingCompleted: Boolean,
155+
isLightModeActive: Boolean,
156+
) {
157+
Box(
158+
modifier = Modifier
159+
.clip(shape = RoundedCornerShape(24.dp))
160+
.background(color = Color.LightGray)
161+
.size(100.dp)
162+
.shimmerLoadingAnimation(
163+
isLoadingCompleted = isLoadingCompleted,
164+
isLightModeActive = isLightModeActive,
165+
)
166+
)
167+
}
168+
169+
@Composable
170+
fun ComponentRectangle(
171+
isLoadingCompleted: Boolean,
172+
isLightModeActive: Boolean,
173+
) {
174+
Box(
175+
modifier = Modifier
176+
.clip(shape = RoundedCornerShape(24.dp))
177+
.background(color = Color.LightGray)
178+
.height(200.dp)
179+
.fillMaxWidth()
180+
.shimmerLoadingAnimation(
181+
isLoadingCompleted = isLoadingCompleted,
182+
isLightModeActive = isLightModeActive,
183+
)
184+
)
185+
}
186+
187+
@Composable
188+
fun ComponentRectangleLineLong(
189+
isLoadingCompleted: Boolean,
190+
isLightModeActive: Boolean,
191+
) {
192+
Box(
193+
modifier = Modifier
194+
.clip(shape = RoundedCornerShape(8.dp))
195+
.background(color = Color.LightGray)
196+
.size(height = 30.dp, width = 200.dp)
197+
.shimmerLoadingAnimation(
198+
isLoadingCompleted = isLoadingCompleted,
199+
isLightModeActive = isLightModeActive,
200+
)
201+
)
202+
}
203+
204+
@Composable
205+
fun ComponentRectangleLineShort(
206+
isLoadingCompleted: Boolean,
207+
isLightModeActive: Boolean,
208+
) {
209+
Box(
210+
modifier = Modifier
211+
.clip(shape = RoundedCornerShape(8.dp))
212+
.background(color = Color.LightGray)
213+
.size(height = 30.dp, width = 100.dp)
214+
.shimmerLoadingAnimation(
215+
isLoadingCompleted = isLoadingCompleted,
216+
isLightModeActive = isLightModeActive,
217+
)
218+
)
219+
}
220+
221+
@Composable
222+
fun ContentLoadingButton(
223+
modifier: Modifier,
224+
onClick: () -> Unit,
225+
isLoadingCompleted: Boolean,
226+
isLightMode: Boolean,
227+
) {
228+
var isStartMode by remember { mutableStateOf(true) }
229+
230+
Button(
231+
modifier = modifier,
232+
onClick = {
233+
isStartMode = !isStartMode
234+
onClick()
235+
},
236+
colors = ButtonDefaults.buttonColors(
237+
containerColor =
238+
if (isLightMode) {
239+
if (isStartMode) Color.Blue
240+
else Color.Red
241+
} else {
242+
if (isStartMode) Color.Green
243+
else Color.Red
244+
}
245+
)
246+
) {
247+
Text(
248+
text = if (isLoadingCompleted) {
249+
"Start Shimmer Loading Animation ▶\uFE0F"
250+
} else {
251+
"Stop Shimmer Loading Animation ⏹\uFE0F"
252+
},
253+
color = if (isLightMode) Color.White else {
254+
if (isStartMode) Color.Black
255+
else Color.White
256+
}
257+
)
258+
}
259+
}
260+
261+
@Composable
262+
fun ContentModeButton(
263+
modifier: Modifier,
264+
onClick: () -> Unit,
265+
isLightMode: Boolean,
266+
) {
267+
Button(
268+
modifier = modifier,
269+
onClick = { onClick() },
270+
colors = ButtonDefaults.buttonColors(
271+
containerColor = if (isLightMode) Color.Blue else Color.Green
272+
)
273+
) {
274+
Text(
275+
text = if (isLightMode) {
276+
"Display Dark Mode \uD83C\uDF19"
277+
} else {
278+
"Display Light Mode ☀\uFE0F"
279+
},
280+
color = if (isLightMode) Color.White else Color.Black
281+
)
282+
}
283+
}
284+
285+
@Preview
286+
@Composable
287+
private fun HomeScreenPreview() {
288+
HomeScreen()
289+
}

0 commit comments

Comments
 (0)