1
+ package com.smarttoolfactory.tutorial1_1basics.chapter6_graphics
2
+
3
+ import android.content.res.Resources
4
+ import androidx.compose.foundation.Canvas
5
+ import androidx.compose.foundation.border
6
+ import androidx.compose.foundation.layout.Arrangement
7
+ import androidx.compose.foundation.layout.BoxWithConstraints
8
+ import androidx.compose.foundation.layout.Column
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.material.Slider
16
+ import androidx.compose.material.Text
17
+ import androidx.compose.runtime.Composable
18
+ import androidx.compose.runtime.getValue
19
+ import androidx.compose.runtime.mutableFloatStateOf
20
+ import androidx.compose.runtime.remember
21
+ import androidx.compose.runtime.setValue
22
+ import androidx.compose.ui.Alignment
23
+ import androidx.compose.ui.Modifier
24
+ import androidx.compose.ui.geometry.Offset
25
+ import androidx.compose.ui.graphics.Color
26
+ import androidx.compose.ui.platform.LocalDensity
27
+ import androidx.compose.ui.tooling.preview.Preview
28
+ import androidx.compose.ui.unit.Dp
29
+ import androidx.compose.ui.unit.dp
30
+ import androidx.compose.ui.unit.sp
31
+ import java.util.Random
32
+
33
+ @Preview
34
+ @Composable
35
+ fun ControlledExplosion () {
36
+ Column (
37
+ modifier = Modifier .fillMaxSize().padding(16 .dp),
38
+ horizontalAlignment = Alignment .CenterHorizontally
39
+ ) {
40
+ var progress by remember { mutableFloatStateOf(0f ) }
41
+
42
+ Explosion (progress)
43
+
44
+ Spacer (Modifier .height(16 .dp))
45
+ Text (text = " Progress: $progress " , fontSize = 18 .sp)
46
+ Slider (
47
+ modifier = Modifier .fillMaxWidth(),
48
+ value = progress,
49
+ onValueChange = {
50
+ progress = it
51
+ }
52
+ )
53
+ Spacer (Modifier .height(16 .dp))
54
+ }
55
+ }
56
+
57
+ @Composable
58
+ fun Explosion (
59
+ progress : Float
60
+ ) {
61
+ BoxWithConstraints {
62
+
63
+ val sizeDp = maxWidth
64
+ val density = LocalDensity .current
65
+ val sizePx = with (density) {
66
+ sizeDp.toPx()
67
+ }
68
+ val sizePxHalf = sizePx / 2
69
+ val particles = remember {
70
+ List (150 ) {
71
+ ExplodingParticle (
72
+ color = Color (listOf (0xffea4335 , 0xff4285f4 , 0xfffbbc05 , 0xff34a853 ).random()),
73
+ startXPosition = sizePxHalf.toInt(),
74
+ startYPosition = sizePxHalf.toInt(),
75
+ maxHorizontalDisplacement = sizePxHalf,
76
+ maxVerticalDisplacement = sizePxHalf
77
+ )
78
+ }
79
+ }
80
+ particles.forEach { it.updateProgress(progress) }
81
+
82
+ Canvas (
83
+ modifier = Modifier
84
+ .border(width = 1 .dp, color = Color (0x26000000 ))
85
+ .size(sizeDp)
86
+ ) {
87
+ drawLine(
88
+ color = Color .Black ,
89
+ start = Offset (sizePxHalf, 0f ),
90
+ end = Offset (sizePxHalf, sizePx),
91
+ strokeWidth = 2 .dp.toPx()
92
+ )
93
+ drawLine(
94
+ color = Color .Black ,
95
+ start = Offset (0f , sizePxHalf),
96
+ end = Offset (sizePx, sizePxHalf),
97
+ strokeWidth = 2 .dp.toPx()
98
+ )
99
+ particles.forEach { particle ->
100
+ drawCircle(
101
+ alpha = particle.alpha,
102
+ color = particle.color,
103
+ radius = 5 .dp.toPx(),
104
+ center = Offset (particle.currentXPosition, particle.currentYPosition),
105
+ )
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ class ExplodingParticle (
112
+ val color : Color ,
113
+ val startXPosition : Int ,
114
+ val startYPosition : Int ,
115
+ val maxHorizontalDisplacement : Float ,
116
+ val maxVerticalDisplacement : Float
117
+ ) {
118
+ private val velocity = 4 * maxVerticalDisplacement
119
+ private val acceleration = - 2 * velocity
120
+ var currentXPosition = 0f
121
+ var currentYPosition = 0f
122
+
123
+ var alpha = 1f
124
+
125
+ fun updateProgress (explosionProgress : Float ) {
126
+ val currentTime = explosionProgress * 1f
127
+
128
+ val verticalDisplacement =
129
+ currentTime * velocity + 0.5 * acceleration * currentTime * currentTime
130
+
131
+ currentXPosition = startXPosition + maxHorizontalDisplacement * explosionProgress
132
+ currentYPosition = (startYPosition - verticalDisplacement).toFloat()
133
+
134
+ }
135
+ }
136
+
137
+ fun Float.mapInRange (inMin : Float , inMax : Float , outMin : Float , outMax : Float ): Float {
138
+ return outMin + (((this - inMin) / (inMax - inMin)) * (outMax - outMin))
139
+ }
140
+
141
+ fun Int.dpToPx () = toFloat().dpToPx()
142
+ fun Dp.toPx () = value.dpToPx()
143
+
144
+ fun Float.dpToPx () = this * Resources .getSystem().displayMetrics.density
145
+
146
+
147
+ private val random = Random ()
148
+ fun Float.randomTillZero () = this * random.nextFloat()
149
+ fun randomInRange (min : Float , max : Float ) = min + (max - min).randomTillZero()
150
+ fun randomBoolean (trueProbabilityPercentage : Int ) =
151
+ random.nextFloat() < trueProbabilityPercentage / 100f
0 commit comments