@@ -22,10 +22,8 @@ import androidx.compose.material.Button
2222import androidx.compose.material.Slider
2323import androidx.compose.material.Text
2424import androidx.compose.runtime.Composable
25- import androidx.compose.runtime.LaunchedEffect
2625import androidx.compose.runtime.getValue
2726import androidx.compose.runtime.mutableFloatStateOf
28- import androidx.compose.runtime.mutableIntStateOf
2927import androidx.compose.runtime.mutableStateListOf
3028import androidx.compose.runtime.mutableStateOf
3129import androidx.compose.runtime.remember
@@ -52,15 +50,9 @@ import androidx.compose.ui.unit.dp
5250import androidx.compose.ui.unit.sp
5351import com.smarttoolfactory.tutorial1_1basics.R
5452import com.smarttoolfactory.tutorial1_1basics.chapter2_material_widgets.CheckBoxWithTextRippleFullRow
55- import kotlinx.coroutines.delay
5653import kotlinx.coroutines.launch
5754import kotlin.random.Random
5855
59- enum class Direction {
60- Top , TopStart , TopEnd , Bottom , BottomStart , BottomEnd
61- }
62-
63-
6456@Preview
6557@Composable
6658fun GraphicsLayerToImageBitmapSample () {
@@ -145,16 +137,19 @@ fun GraphicsLayerToImageBitmapSample() {
145137data class Particle (
146138 val initialCenter : Offset ,
147139 val initialSize : Size ,
140+ val initialAlpha : Float ,
148141 val color : Color ,
149- val lifeSpan : Float = 1f ,
150- val scale : Float = 1f
142+ val scale : Float = 1f ,
143+ val decayFactor : Int ,
151144) {
152145
153146 val initialRadius: Float = initialSize.width.coerceAtMost(initialSize.height) / 2f
154147 var radius: Float = scale * initialRadius
155148
156- var alpha: Float = 1f
149+ var alpha: Float = initialAlpha
157150
151+ val active: Boolean
152+ get() = radius > 0 && alpha > 0
158153
159154 var center: Offset = initialCenter
160155
@@ -170,7 +165,7 @@ data class Particle(
170165 val rect: Rect
171166 get() = Rect (
172167 offset = Offset (center.x - radius, center.y - radius),
173- size = Size (radius, radius)
168+ size = Size (radius * 2 , radius * 2 )
174169 )
175170}
176171
@@ -192,10 +187,6 @@ fun GraphicsLayerToParticles() {
192187 Animatable (1f )
193188 }
194189
195- var ticker by remember {
196- mutableIntStateOf(0 )
197- }
198-
199190 var duration by remember {
200191 mutableFloatStateOf(3000f )
201192 }
@@ -208,13 +199,6 @@ fun GraphicsLayerToParticles() {
208199 mutableStateOf(false )
209200 }
210201
211- LaunchedEffect (Unit ) {
212- while (true ) {
213- delay(16 )
214- ticker++
215- }
216- }
217-
218202 Column (
219203 modifier = Modifier
220204 .fillMaxSize()
@@ -264,83 +248,114 @@ fun GraphicsLayerToParticles() {
264248 }
265249
266250 if (particleList.isEmpty().not ()) {
251+
267252 Canvas (
268253 modifier = Modifier
269- .border(1 .dp, Color .Blue )
254+ .border(2 .dp, if (animatable.isRunning) Color .Green else Color . Red )
270255 .clickable {
271256 coroutineScope.launch {
272257 animatable.snapTo(0f )
273- var currentTime = System .currentTimeMillis()
258+ val startTime = System .currentTimeMillis()
274259 animatable.animateTo(
275260 targetValue = 1f ,
276261 animationSpec = tween(duration.toInt()),
277262 block = {
278263
279264 val progress = this .value
280265
281- val timePassed = System .currentTimeMillis() - currentTime
282- println ( " Time passed: $timePassed " )
266+ val timePassed = System .currentTimeMillis() - startTime
267+
283268
284269 particleList.forEach { particle ->
285270
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)
289280
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
292283
293- particle.center = Offset (newX, newY)
284+ val particleDecayFactor = particle.decayFactor
294285
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+ }
300308//
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+ }
305314 }
315+
316+ val aliveParticle = particleList.filter { it.active }.size
317+
318+ println (" alive particle size: $aliveParticle , progress: $progress " )
306319 }
307320 )
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+ // }
320333 }
321334 }
322335 .size(widthDp)
323336 ) {
324337
325338 // TODO Remove this and invalidate Canvas more gracefully
326- drawCircle(color = Color .Transparent , radius = ticker.toFloat() )
339+ drawCircle(color = Color .Transparent , radius = animatable.value )
327340
328341 particleList.forEach { particle: Particle ->
329342
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
332346// drawRect(
333347// color = Color.Red,
334348// topLeft = rect.topLeft,
335349// size = rect.size,
336350// style = Stroke(1.dp.toPx())
337351// )
338352
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+ }
344359 }
345360 }
346361
@@ -406,17 +421,18 @@ fun createParticles(imageBitmap: ImageBitmap, particleSize: Int): List<Particle>
406421 for (posX in 0 until width step particleSize) {
407422 for (posY in 0 until height step particleSize) {
408423
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+
409430 // Get pixel at center of this pixel rectangle
410431 // If last pixel is out of image get it from end of the width or height
411432 // 🔥x must be < bitmap.width() and y must be < bitmap.height()
412433 val pixelCenterX = (posX + particleRadius).coerceAtMost(width - 1 )
413434 val pixelCenterY = (posY + particleRadius).coerceAtMost(height - 1 )
414435
415- // println(
416- // "posX: $posX, pixelCenterX: $pixelCenterX, " +
417- // "posY: $posY, pixelCenterY: $pixelCenterY"
418- // )
419-
420436 val pixel: Int = bitmap.getPixel(pixelCenterX, pixelCenterY)
421437 val color = Color (pixel)
422438
@@ -430,9 +446,10 @@ fun createParticles(imageBitmap: ImageBitmap, particleSize: Int): List<Particle>
430446 y = pixelCenterY.toFloat()
431447 ),
432448 initialSize = Size (size, size),
449+ initialAlpha = alpha,
433450 color = color,
434- scale = Random .nextInt( 50 , 150 ) / 100f ,
435- lifeSpan = Random .nextFloat().coerceAtLeast(. 5f )
451+ scale = scale ,
452+ decayFactor = decayFactor
436453 )
437454 )
438455 } else {
0 commit comments