Skip to content

Commit 1bf48e0

Browse files
update particle sample and add easing sample
1 parent 6db2565 commit 1bf48e0

File tree

2 files changed

+339
-69
lines changed

2 files changed

+339
-69
lines changed

Tutorial1-1Basics/src/main/java/com/smarttoolfactory/tutorial1_1basics/chapter6_graphics/Tutorial6_39_1GraphicsLayer1.kt

Lines changed: 86 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ import androidx.compose.material.Button
2222
import androidx.compose.material.Slider
2323
import androidx.compose.material.Text
2424
import androidx.compose.runtime.Composable
25-
import androidx.compose.runtime.LaunchedEffect
2625
import androidx.compose.runtime.getValue
2726
import androidx.compose.runtime.mutableFloatStateOf
28-
import androidx.compose.runtime.mutableIntStateOf
2927
import androidx.compose.runtime.mutableStateListOf
3028
import androidx.compose.runtime.mutableStateOf
3129
import androidx.compose.runtime.remember
@@ -52,15 +50,9 @@ import androidx.compose.ui.unit.dp
5250
import androidx.compose.ui.unit.sp
5351
import com.smarttoolfactory.tutorial1_1basics.R
5452
import com.smarttoolfactory.tutorial1_1basics.chapter2_material_widgets.CheckBoxWithTextRippleFullRow
55-
import kotlinx.coroutines.delay
5653
import kotlinx.coroutines.launch
5754
import kotlin.random.Random
5855

59-
enum class Direction {
60-
Top, TopStart, TopEnd, Bottom, BottomStart, BottomEnd
61-
}
62-
63-
6456
@Preview
6557
@Composable
6658
fun GraphicsLayerToImageBitmapSample() {
@@ -145,16 +137,19 @@ fun GraphicsLayerToImageBitmapSample() {
145137
data 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

Comments
 (0)