@@ -22,10 +22,8 @@ import androidx.compose.material.Button
22
22
import androidx.compose.material.Slider
23
23
import androidx.compose.material.Text
24
24
import androidx.compose.runtime.Composable
25
- import androidx.compose.runtime.LaunchedEffect
26
25
import androidx.compose.runtime.getValue
27
26
import androidx.compose.runtime.mutableFloatStateOf
28
- import androidx.compose.runtime.mutableIntStateOf
29
27
import androidx.compose.runtime.mutableStateListOf
30
28
import androidx.compose.runtime.mutableStateOf
31
29
import androidx.compose.runtime.remember
@@ -52,15 +50,9 @@ import androidx.compose.ui.unit.dp
52
50
import androidx.compose.ui.unit.sp
53
51
import com.smarttoolfactory.tutorial1_1basics.R
54
52
import com.smarttoolfactory.tutorial1_1basics.chapter2_material_widgets.CheckBoxWithTextRippleFullRow
55
- import kotlinx.coroutines.delay
56
53
import kotlinx.coroutines.launch
57
54
import kotlin.random.Random
58
55
59
- enum class Direction {
60
- Top , TopStart , TopEnd , Bottom , BottomStart , BottomEnd
61
- }
62
-
63
-
64
56
@Preview
65
57
@Composable
66
58
fun GraphicsLayerToImageBitmapSample () {
@@ -145,16 +137,19 @@ fun GraphicsLayerToImageBitmapSample() {
145
137
data class Particle (
146
138
val initialCenter : Offset ,
147
139
val initialSize : Size ,
140
+ val initialAlpha : Float ,
148
141
val color : Color ,
149
- val lifeSpan : Float = 1f ,
150
- val scale : Float = 1f
142
+ val scale : Float = 1f ,
143
+ val decayFactor : Int ,
151
144
) {
152
145
153
146
val initialRadius: Float = initialSize.width.coerceAtMost(initialSize.height) / 2f
154
147
var radius: Float = scale * initialRadius
155
148
156
- var alpha: Float = 1f
149
+ var alpha: Float = initialAlpha
157
150
151
+ val active: Boolean
152
+ get() = radius > 0 && alpha > 0
158
153
159
154
var center: Offset = initialCenter
160
155
@@ -170,7 +165,7 @@ data class Particle(
170
165
val rect: Rect
171
166
get() = Rect (
172
167
offset = Offset (center.x - radius, center.y - radius),
173
- size = Size (radius, radius)
168
+ size = Size (radius* 2 , radius* 2 )
174
169
)
175
170
}
176
171
@@ -192,10 +187,6 @@ fun GraphicsLayerToParticles() {
192
187
Animatable (1f )
193
188
}
194
189
195
- var ticker by remember {
196
- mutableIntStateOf(0 )
197
- }
198
-
199
190
var duration by remember {
200
191
mutableFloatStateOf(3000f )
201
192
}
@@ -208,13 +199,6 @@ fun GraphicsLayerToParticles() {
208
199
mutableStateOf(false )
209
200
}
210
201
211
- LaunchedEffect (Unit ) {
212
- while (true ) {
213
- delay(16 )
214
- ticker++
215
- }
216
- }
217
-
218
202
Column (
219
203
modifier = Modifier
220
204
.fillMaxSize()
@@ -264,83 +248,114 @@ fun GraphicsLayerToParticles() {
264
248
}
265
249
266
250
if (particleList.isEmpty().not ()) {
251
+
267
252
Canvas (
268
253
modifier = Modifier
269
- .border(1 .dp, Color .Blue )
254
+ .border(2 .dp, if (animatable.isRunning) Color .Green else Color . Red )
270
255
.clickable {
271
256
coroutineScope.launch {
272
257
animatable.snapTo(0f )
273
- var currentTime = System .currentTimeMillis()
258
+ val startTime = System .currentTimeMillis()
274
259
animatable.animateTo(
275
260
targetValue = 1f ,
276
261
animationSpec = tween(duration.toInt()),
277
262
block = {
278
263
279
264
val progress = this .value
280
265
281
- val timePassed = System .currentTimeMillis() - currentTime
282
- println ( " Time passed: $timePassed " )
266
+ val timePassed = System .currentTimeMillis() - startTime
267
+
283
268
284
269
particleList.forEach { particle ->
285
270
286
- val oldCenter = particle.center
287
- val posX = oldCenter.x
288
- val posY = oldCenter.y
271
+ if (particle.active) {
272
+ val oldCenter = particle.center
273
+ val posX = oldCenter.x
274
+ val posY = oldCenter.y
275
+
276
+ val newX = posX + 5f * progress * Random .nextFloat()
277
+ val newY = posY - 15f * progress * Random .nextFloat()
278
+
279
+ particle.center = Offset (newX, newY)
289
280
290
- val newX = posX + 5f * progress * Random .nextFloat()
291
- val newY = posY - 5f * progress * Random .nextFloat()
281
+ // TODO Decide whether to use time or progress
282
+ val timeFraction = timePassed / duration
292
283
293
- particle.center = Offset (newX, newY)
284
+ val particleDecayFactor = particle.decayFactor
294
285
295
- // if (animateSize) {
296
- // val newRadius =
297
- // particle.radius * (particle.lifeSpan - progress)
298
- // .coerceAtLeast(0f)
299
- // }
286
+ val decayFactor =
287
+ if (progress < .80f ) particleDecayFactor
288
+ else if (progress < .9f ) particleDecayFactor + 1
289
+ else if (progress < .98f ) particleDecayFactor + 3
290
+ else
291
+ particleDecayFactor
292
+ .coerceAtLeast(5 ) + 1
293
+
294
+ if (animateSize) {
295
+ val radius = particle.radius
296
+ val newRadius =
297
+ radius - progress * decayFactor * particle.initialRadius / 100f
298
+
299
+ particle.radius = newRadius.coerceAtLeast(0f )
300
+
301
+ // println(
302
+ // "Time passed: $timePassed, " +
303
+ // "timeFraction: $timeFraction, " +
304
+ // "newRadius: $newRadius, " +
305
+ // "center: ${particle.center}"
306
+ // )
307
+ }
300
308
//
301
- // if (animateAlpha) {
302
- // particle.alpha -= (1 - progress) * Random.nextFloat()
303
- // .coerceAtMost(.2f)
304
- // }
309
+ // if (animateAlpha) {
310
+ // particle.alpha -= (1 - progress) * Random.nextFloat()
311
+ // .coerceAtMost(.2f)
312
+ // }
313
+ }
305
314
}
315
+
316
+ val aliveParticle = particleList.filter { it.active }.size
317
+
318
+ println (" alive particle size: $aliveParticle , progress: $progress " )
306
319
}
307
320
)
308
- animatable.snapTo(1f )
309
- graphicsLayer.toImageBitmap().let {
310
- particleList.clear()
311
- particleList.addAll(
312
- createParticles(
313
- imageBitmap = it.asAndroidBitmap()
314
- .copy(Bitmap .Config .ARGB_8888 , false )
315
- .asImageBitmap(),
316
- particleSize = particleSize.toInt()
317
- )
318
- )
319
- }
321
+ // animatable.snapTo(1f)
322
+ // graphicsLayer.toImageBitmap().let {
323
+ // particleList.clear()
324
+ // particleList.addAll(
325
+ // createParticles(
326
+ // imageBitmap = it.asAndroidBitmap()
327
+ // .copy(Bitmap.Config.ARGB_8888, false)
328
+ // .asImageBitmap(),
329
+ // particleSize = particleSize.toInt()
330
+ // )
331
+ // )
332
+ // }
320
333
}
321
334
}
322
335
.size(widthDp)
323
336
) {
324
337
325
338
// TODO Remove this and invalidate Canvas more gracefully
326
- drawCircle(color = Color .Transparent , radius = ticker.toFloat() )
339
+ drawCircle(color = Color .Transparent , radius = animatable.value )
327
340
328
341
particleList.forEach { particle: Particle ->
329
342
330
- // For debugging borders of particles
331
- // val rect = particle.initialRect
343
+ if (particle.active) {
344
+ // For debugging borders of particles
345
+ // val rect = particle.rect
332
346
// drawRect(
333
347
// color = Color.Red,
334
348
// topLeft = rect.topLeft,
335
349
// size = rect.size,
336
350
// style = Stroke(1.dp.toPx())
337
351
// )
338
352
339
- drawCircle(
340
- color = particle.color.copy(alpha = particle.alpha),
341
- radius = particle.radius,
342
- center = particle.center,
343
- )
353
+ drawCircle(
354
+ color = particle.color.copy(alpha = particle.alpha),
355
+ radius = particle.radius,
356
+ center = particle.center,
357
+ )
358
+ }
344
359
}
345
360
}
346
361
@@ -406,17 +421,18 @@ fun createParticles(imageBitmap: ImageBitmap, particleSize: Int): List<Particle>
406
421
for (posX in 0 until width step particleSize) {
407
422
for (posY in 0 until height step particleSize) {
408
423
424
+ // TODO Assign these params
425
+ val scale = Random .nextInt(90 , 250 ) / 100f
426
+ // val scale = 1f
427
+ val decayFactor = Random .nextInt(10 )
428
+ val alpha = Random .nextFloat().coerceAtLeast(.5f )
429
+
409
430
// Get pixel at center of this pixel rectangle
410
431
// If last pixel is out of image get it from end of the width or height
411
432
// 🔥x must be < bitmap.width() and y must be < bitmap.height()
412
433
val pixelCenterX = (posX + particleRadius).coerceAtMost(width - 1 )
413
434
val pixelCenterY = (posY + particleRadius).coerceAtMost(height - 1 )
414
435
415
- // println(
416
- // "posX: $posX, pixelCenterX: $pixelCenterX, " +
417
- // "posY: $posY, pixelCenterY: $pixelCenterY"
418
- // )
419
-
420
436
val pixel: Int = bitmap.getPixel(pixelCenterX, pixelCenterY)
421
437
val color = Color (pixel)
422
438
@@ -430,9 +446,10 @@ fun createParticles(imageBitmap: ImageBitmap, particleSize: Int): List<Particle>
430
446
y = pixelCenterY.toFloat()
431
447
),
432
448
initialSize = Size (size, size),
449
+ initialAlpha = alpha,
433
450
color = color,
434
- scale = Random .nextInt( 50 , 150 ) / 100f ,
435
- lifeSpan = Random .nextFloat().coerceAtLeast(. 5f )
451
+ scale = scale ,
452
+ decayFactor = decayFactor
436
453
)
437
454
)
438
455
} else {
0 commit comments