Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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.GreenelseColor.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)

AltStyle によって変換されたページ (->オリジナル) /