Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[성빈랜드] Compose Canvas로 예쁜 인터렉션 구현하기

Kong
April 09, 2023

[성빈랜드] Compose Canvas로 예쁜 인터렉션 구현하기

Compose Canvas로 예쁜 인터렉션 구현하기 발표에 사용했던 자료입니다.
애니메이션이 핵심인데 안나와서 아쉽네요 ㅠㅠㅠ

Kong

April 09, 2023
Tweet

More Decks by Kong

Other Decks in Programming

Transcript

  1. Frame ചݶਸ Ӓܾ ٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ

    ٸ ೙ਃೠ ӝࠄ ѐ֛ 3 *Youtube@Andymation ࢎۈ੄ ׀җ ֱח োࣘ೧ࢲ ࡅܰѱ ࠁৈ૑ח
 ੉޷૑ܳ ز੘ਵ۽ ੋधೞח ҳઑ੉׮. ੿੸ੋ ੉޷૑ੋ ױࣽೠ Ӓܿޘ਺਷ ࢎۈীѱ ৔࢚୊ۢ ࠁੋ׮.
  2. Recomposition ചݶਸ Ӓܾ ٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ

    ٸ ೙ਃೠ ӝࠄ ѐ֛ 4 Composeীࢲח ױࣽೞѱ ઝ಴݅ਸ ߄Դ׮Ҋ ചݶ߸҃੉ ੌযա૑ ঋח׮. ചݶ ߸҃ਸ ঌܻ۰ݶ 
 Stateчਸ ߸҃೧ঠ ೠ׮.
  3. Animation API ചݶਸ Ӓܾٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ

    ٸ ೙ਃೠ ӝࠄ ѐ֛ 5 XML -> Compose о੢ ഄन੸ੋ ߸ചੋ Animation API
  4. Animation API ചݶਸ Ӓܾٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ

    ٸ ೙ਃೠ ӝࠄ ѐ֛ 6 XML -> Compose о੢ ഄन੸ੋ ߸ചੋ Animation API Ӓۢীب যڃٸীח ਗୡ੸ਵ۽ ೐ۨ੐݃׮ ઝ಴ܳ ߸҃दఃחѱ ಞೡٸب੓׮.
  5. Animation API ചݶਸ Ӓܾٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ

    ٸ ೙ਃೠ ӝࠄ ѐ֛ 7 1. ೐ۨ੐ী ୡ੼ਸ ݏ୹ٸ 2. State ߸ച হ੉ ޖೠ൤ ߈ࠂغח ചݶਸ Ӓܾ ٸ 3. যڃ ചݶ੉ٚ 60 ೐ۨ੐ਵ۽ Ҋ੿ೞ۰ ೡ ٸ
  6. Choreographer ചݶਸ Ӓܾٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ ٸ

    ೙ਃೠ ӝࠄ ѐ֛ 8 Stateী ୡ੼੉ ইצ ೐ۨ੐ী ୡ੼ਸ ݏ୶Ҋ ചݶਸ ߸ചೞҊ र׮.
  7. Choreographer ചݶਸ Ӓܾٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ ٸ

    ೙ਃೠ ӝࠄ ѐ֛ 9 Stateী ୡ੼੉ ইצ ೐ۨ੐ী ୡ੼ਸ ݏ୶Ҋ ചݶਸ ߸ചೞҊ र׮. Ӓ۞ӝ ਤ೧ࢲח ೐ۨ੐݃׮ ௒ߔਸ ߉ইঠ ೠ׮.
  8. Choreographer ചݶਸ Ӓܾٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ ٸ

    ೙ਃೠ ӝࠄ ѐ֛ 10 Stateী ୡ੼੉ ইצ ೐ۨ੐ী ୡ੼ਸ ݏ୶Ҋ ചݶਸ ߸ചೞҊ र׮. Ӓ۞ӝ ਤ೧ࢲח ೐ۨ੐݃׮ ௒ߔਸ ߉ইঠ ೠ׮. val callback = object : Choreographer.FrameCallback { override fun doFrame(frameTimeNanos: Long) { Choreographer.getInstance().postFrameCallback(this) } }
  9. WithFrameNamos ചݶਸ Ӓܾٸ ೙ਃೠ ӝࠄ ѐ֛ਸ ঌইࠁ੗ ചݶਸ Ӓܾ ٸ

    ೙ਃೠ ӝࠄ ѐ֛ 11 /** * Awaits the next animation frame and returns frame time in nanoseconds. */ public suspend fun awaitFrame(): Long { // fast path when choreographer is already known val choreographer = choreographer if (choreographer != null) { return suspendCancellableCoroutine { cont -> postFrameCallback(choreographer, cont) } } // post into looper thread to figure it out return suspendCancellableCoroutine { cont -> Dispatchers.Main.dispatch(EmptyCoroutineContext, Runnable { updateChoreographerAndPostFrameCallback(cont) }) } } choreographer.postFrameCallbackਸ wraping ೞҊ ੓ח awaitFrame
  10. ਋ܻо ҳഅೡ ചݶ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ ׀ ղܻח ചݶਸ

    Ӓ۰ࠁ੗ 14 Canvas۽ ׀࣠੉ Ӓܻӝ ݆਷ ׀࣠੉ Ӓ۰ࠁӝ ׀࣠੉ ਤীࢲ ইې۽ ղܻӝ ׀࣠੉о ׮ ղܻݶ ׮द ৢܻӝ ࢖пೣࣻܳ ੉ਊ೧ пب ٜ݅ӝ
  11. canvas۽ ׀࣠੉ Ӓܻӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ ׀ ղܻח ചݶਸ

    Ӓ۰ࠁ੗ 15 fun SnowScreen() { Canvas(modifier = Modifier.fillMaxSize().background(Color.Black)) { } }
  12. canvas۽ ׀࣠੉ Ӓܻӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ ׀ ղܻח ചݶਸ

    Ӓ۰ࠁ੗ 16 fun SnowScreen() { Canvas(modifier = Modifier.fillMaxSize().background(Color.Black)) {drawScope-> drawCircle(color = Color.White, radius = 20f) }
  13. canvas۽ ׀࣠੉ Ӓܻӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ ׀ ղܻח ചݶਸ

    Ӓ۰ࠁ੗: ؊ աইоӝ 17 drawScope().drawContext.Canvas drawScope() VS
  14. canvas۽ ׀࣠੉ Ӓܻӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 18 val paint:

    Paint = Paint().apply { isAntiAlias = true color = Color.White style = PaintingStyle.Fill } fun SnowScreen() { Canvas(modifier = Modifier.fillMaxSize().background(Color.Black)) { val canvas = drawContext.canvas canvas.drawCircle(center, 20f, paint) drawCircle(color = Color.White, radius = 20f) } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  15. ݆਷ ׀࣠੉ Ӓ۰ࠁӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 19 data class

    SnowState(val snows: List<Snow>) class Snow( val size: Float, position: Offset, ) { val paint: Paint = Paint().apply { isAntiAlias = true color = Color.White style = PaintingStyle.Fill } private var position by mutableStateOf(position) fun draw(canvas: Canvas) { canvas.drawCircle(position, size, paint) } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  16. ݆਷ ׀࣠੉ Ӓ۰ࠁӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 20 data class

    SnowState(val snows: List<Snow>) class Snow( val size: Float, position: Offset, ) { val paint: Paint = Paint().apply { isAntiAlias = true color = Color.White style = PaintingStyle.Fill } private var position by mutableStateOf(position) fun draw(canvas: Canvas) { canvas.drawCircle(position, size, paint) } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  17. ݆਷ ׀࣠੉ Ӓ۰ࠁӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 21 fun createSnowList(canvas:

    IntSize): List<Snow> { return List(10) { Snow( size = 20f, position = Offset(x = canvas.width.randomTest().toFloat(), y = 20f), ) } } fun Int.randomTest() = Random.nextInt(this) ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  18. ݆਷ ׀࣠੉ Ӓ۰ࠁӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 22 fun SnowScreen()

    { val screenWidth = (Dp(LocalConfiguration.current.screenWidthDp.toFloat())).dpToPx(density).toInt() var snowState by remember { mutableStateOf(SnowState(createSnowList(IntSize(screenWidth, 0)))) } Canvas( modifier = Modifier .fillMaxSize() .background(Color.Black), ) { val canvas = drawContext.canvas for (snow in snowState.snows) { snow.draw(canvas) } } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  19. ݆਷ ׀࣠੉ Ӓ۰ࠁӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 23 val screenWidth

    = (Dp(LocalConfiguration.current.screenWidthDp.toFloat())).dpToPx(density).toInt() ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ Canvas۽ Ӓܻח Ѣ੄ ݽٚ গפݫ੉ٜ࣌਷ ࠁৈ૑ח झ௼ܽ ࢎ੉ૉܳ ঌইঠೠ׮.
  20. ׀࣠੉ ਤীࢲ ইې۽ ղܻӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 25 class

    Snow( val size: Float, position: Offset, ) { val paint: Paint = Paint().apply { isAntiAlias = true color = Color.White style = PaintingStyle.Fill } // ୡӝ ਤ஖ח ੿೧ઉ੓૑݅, ч਷ ߸ച೧ঠೠ׮. private var position by mutableStateOf(position) fun draw(canvas: Canvas) { canvas.drawCircle(position, size, paint) } //୶о ػ ௏٘ fun update() { position = position.copy(y = position.y + 1) } } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  21. ׀࣠੉ ਤীࢲ ইې۽ ղܻӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 26 LaunchedEffect(Unit)

    { while (isActive) { awaitFrame() for (snow in snowState.snows) { snow.update() } } } } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  22. ׀࣠੉ ਤীࢲ ইې۽ ղܻӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 27 LaunchedEffect(Unit)

    { while (isActive) { awaitFrame() for (snow in snowState.snows) { snow.update() } } } } @Composable fun LaunchedEffect( key1 : Any?, block : suspend CorountineScope.() -> Unit ) {} ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  23. ׀࣠੉о ׮ ղܻݶ ׮द ৢܻӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 28

    class Snow( val size: Float, position: Offset, val screenSize: IntSize, ) { val paint: Paint = Paint().apply { isAntiAlias = true color = Color.White style = PaintingStyle.Fill } // ୡӝ ਤ஖ח ੿೧ઉ੓૑݅, ч਷ ߸ച೧ঠೠ׮. private var position by mutableStateOf(position) fun draw(canvas: Canvas) { canvas.drawCircle(position, size, paint) } fun update() { //ப߄झ੄ ࢎ੉ૉо ૑աݶ ׮द ৢܻ੗! position = if (position.y > screenSize.height) position.copy(y = 0f) else position.copy(y = position.y + 30) } } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  24. ࢖пೣࣻܳ ੉ਊ೧ пب ٜ݅ӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 30 ࡁ߸਷

    ׀੄ ૐо۝੉׮(਋ܻо ݅٘חч) ࡁ߸җ пبח ےؒೞѱ ૐоೠ׮. ࡁ߸ X Sin(A) = ֫੉(Y) ࡁ߸X Cos(A) = ߃߸(X) ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  25. ࢖пೣࣻܳ ੉ਊ೧ пب ٜ݅ӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 31 private

    const val angleSeed = 25.0f private val angleSeedRange = -angleSeed..angleSeed private val incrementRange = 0.4f..0.8f fun update() { // speed val increment = incrementRange.random() val angle = angleSeedRange.random() val xAngle = (increment * cos((angle))) val yAngle = (increment * sin((angle))) position = if (position.y > screenSize.height) { position.copy(y = 0f) } else { position.copy( x = position.x + xAngle, y = position.y + yAngle, ) } } } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  26. ࢖пೣࣻܳ ੉ਊ೧ пب ٜ݅ӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 32 private

    var angle by mutableStateOf(angle) fun update() { // speed val increment = incrementRange.random() val xAngle = (increment * cos((angle))) val yAngle = (increment * sin((angle))) angle += angleSeedRange.random() / 1000f position = if (position.y > screenSize.height) { position.copy(y = 0f) } else { position.copy( x = position.x + xAngle, y = position.y + yAngle, ) } } } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  27. ࢖пೣࣻܳ ੉ਊ೧ пب ٜ݅ӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 33 private

    var angle by mutableStateOf(angle) fun update() { // speed val increment = incrementRange.random() val xAngle = (increment * cos((angle))) val yAngle = (increment * sin((angle))) angle += angleSeedRange.random() / 1000f position = if (position.y > screenSize.height) { position.copy(y = 0f) } else { position.copy( x = position.x + xAngle, y = position.y + yAngle, ) } } } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  28. ࢖пೣࣻܳ ੉ਊ೧ пب ٜ݅ӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 40 90ب

    = π/2 Kotlin.mathী PIо ઓ੤ೠ׮. public const val PI: Double = 3.141592653589793 ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  29. ࢖пೣࣻܳ ੉ਊ೧ пب ٜ݅ӝ ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗ 41 fun

    createSnowList(canvas: IntSize): List<Snow> { return List(200) { Snow( size = snowSize.random(), position = Offset( x = canvas.width.randomTest().toFloat(), y = canvas.height.randomTest().toFloat(), ), canvas, incrementRange = incrementRange.random(), angle = (angleSeed.random() / angleSeed * 0.1f + (PI.toFloat() / 2.0.toFloat())).toDouble(), ) } } ׀ ղܻח ചݶਸ Ӓ۰ࠁ੗: ؊ աইоӝ
  30. ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 44 Canvas(modifier = Modifier.fillMaxSize()) { val path

    = Path() val amplitude = waveHeight for (x in 0..size.width.toInt()) { val y = size.height / 2 + amplitude * sin((2 * PI * (x + waveProgress.value * size.width)) / size.width) if (x == 0) { path.moveTo(x.toFloat(), y) } else { path.lineTo(x.toFloat(), y) } } path.lineTo(size.width, size.height) path.lineTo(0f, size.height) path.close() drawPath( path = path, color = color, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  31. ਋ܻо ҳഅೡ ചݶ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 47 ചݶࢎ੉ૉ ҳೞӝ ޛ

    ইې۽ хࣗदఃӝ ҋࢶ ٜ݅ӝ (߬૑য Ӓې೐) ౵ب ੗োझۣѱ ٜ݅ӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  32. ചݶ ࢎ੉ૉ ҳೞӝ ౵ب஖ח ചݶਸ Ӓ۰ࠁ੗ 48 ചݶ ࢎ੉ૉܳ ҳೞӝ

    ਤೠ 4о૑ ߑߨ 1. LocalConfiguration.current.screenXXDpܳ ഝਊೞӝ 2. Modifier.onSizeChanged((IntSize) -> Unit) ഝਊೞӝ 3. BoxConstraint ഝਊೞӝ 4. Canvas Composable ೣࣻ উীࢲ width৬ height ഐ୹ оמ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  33. ചݶ ࢎ੉ૉ ҳೞӝ ౵ب஖ח ചݶਸ Ӓ۰ࠁ੗ 49 Modifier.onSizeChanged((IntSize) -> Unit)

    ഝਊೞӝ Modi fi er.onSizeChanged(onSizeChanged: (IntSize) -> Unit) ਃࣗо ୊਺ ஏ੿غѢա ਃࣗ੄ ௼ӝо ߸҃ؼ ٸ ࣻ੿ػ Compose UI ਃࣗ੄ ௼ӝ۽ ഐ୹ؾפ׮. modifier = Modifier.onSizeChanged { with(density) { val width = it.width.toDp() } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  34. ചݶ ࢎ੉ૉ ҳೞӝ ౵ب஖ח ചݶਸ Ӓ۰ࠁ੗ 50 BoxConstraintۆ? Box੄ ۨ੉ইਓ

    ӝמਸ ನೣೞݶࢲ layout੄ Constraintী ੽Ӕೡࣻ ੓ب۾ ٜ݅য૓ ۨ੉ইਓ BoxWithConstraints { val density = LocalDensity.current val height = with(density) { maxHeight.roundToPx() } val width = with(density) { maxWidth.roundToPx() } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  35. ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 51 @Composable fun Water(

    waterLevel: Float, ) = BoxWithConstraints { require(waterLevel in 0f..1f) { "ৈӝ ࢎ੉ী ੓যঠೣ" } val density = LocalDensity.current val height = with(density) { maxHeight.toPx() } val width = with(density) { maxWidth.toPx() } val currentY = height * waterLevel Canvas(modifier = Modifier.fillMaxSize()) { val path = Path().apply { moveTo(0f, 0f) lineTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  36. ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 52 @Composable fun Water(

    waterLevel: Float, ) = BoxWithConstraints { require(waterLevel in 0f..1f) { "ৈӝ ࢎ੉ী ੓যঠೣ" } val density = LocalDensity.current val height = with(density) { maxHeight.toPx() } val width = with(density) { maxWidth.toPx() } val currentY = height * waterLevel Canvas(modifier = Modifier.fillMaxSize()) { val path = Path().apply { moveTo(0f, 0f) lineTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  37. ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 53 @Composable fun Water( waterLevel: Float, )

    = BoxWithConstraints { require(waterLevel in 0f..1f) { "ৈӝ ࢎ੉ী ੓যঠೣ" } val density = LocalDensity.current val height = with(density) { maxHeight.toPx() } val width = with(density) { maxWidth.toPx() } val currentY = height * waterLevel Canvas(modifier = Modifier.fillMaxSize()) { val path = Path().apply { moveTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  38. ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 54 @Preview @Composable fun WaterPreview() { val

    test = remember { Animatable(0f) } LaunchedEffect(Unit) { test.animateTo(1f, tween(30000)) } Surface( modifier = Modifier.fillMaxSize(), color = Color.White, ) { Water(test.value) } } ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  39. ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 55 val path = Path().apply { moveTo(0f,

    currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ pathח ੼ٜ੄ ૘೤੉ݴ, ੼ਸ ੉য بഋਸ ٜ݅ࣻ ੓׮.
  40. ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 56 val path =

    Path().apply { moveTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ pathח ੼ٜ੄ ૘೤੉ݴ, ੼ਸ ੉য بഋਸ ٜ݅ࣻ ੓׮.
  41. ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 57 val path =

    Path().apply { moveTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ pathח ੼ٜ੄ ૘೤੉ݴ, ੼ਸ ੉য بഋਸ ٜ݅ࣻ ੓׮.
  42. ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 58 val path =

    Path().apply { moveTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ pathח ੼ٜ੄ ૘೤੉ݴ, ੼ਸ ੉য بഋਸ ٜ݅ࣻ ੓׮.
  43. ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 59 val path =

    Path().apply { moveTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ pathח ੼ٜ੄ ૘೤੉ݴ, ੼ਸ ੉য بഋਸ ٜ݅ࣻ ੓׮.
  44. ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 60 val path =

    Path().apply { moveTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) + lineTo(0f, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ pathח ੼ٜ੄ ૘೤੉ݴ, ੼ਸ ੉য بഋਸ ٜ݅ࣻ ੓׮.
  45. ޛ ইې۽ ղ۰ࠁӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 61 val path =

    Path().apply { moveTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) + lineTo(0f, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  46. ౵ب ചݶਸ ࠙೧೧ࠁ੗ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 64 ݅ড ҋࢶਸ ࡐݶ

    ੉۠ ݽন੉ Ӓ۰૓׮. ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  47. ౵ب ചݶਸ ࠙೧೧ࠁ੗ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 65 ݅ড ҋࢶਸ ࡐݶ

    ੉۠ ݽন੉ Ӓ۰૓׮. ҳрࠗఠ ա־Ҋ п ҳ৉੄ ੼ਸ ҳ೧ࠁ੗. ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  48. ҳр ա־ӝ ౵ب஖ח ചݶਸ Ӓ۰ࠁ੗ 66 ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ ҳр਷

    Ҋ੿ػ ҃҅ чਵ۽ ա־ח Ѫҗ Ҋ੿ػ ҃҅ ࣻܳ ա־ח Ѫ ف о૑о ੓׮. Configuration change ٸޙী ചݶч਷ ঱ ઁٚ ߄Չࣻ ੓ӝী ҃҅ ࣻܳ Ҋ੿೧ ա־ח ѱ જ׮.
  49. Yч ҳೞӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 67 val currentY = height

    * waterLevel ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ ૑Әࠗఠ ೠ ҃҅ݶ੄ Yчਸ ҳ೧ࠄ׮. അ੤੄ ޛ֫੉ܳ ӝ߈ਵ۽ ࢖੗.
  50. Yч ҳೞӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 68 @Composable fun calculateY(height: Int,

    waterLevel: Float ): Int { var y by remember { mutableStateOf(0) } val duration = Random.nextInt(300) + 300 // ޖೠ গפݫ੉࣌ ҅ࣘ ߈ࠂೠ׮. val yNoiseAnimation = rememberInfiniteTransition() // ౵ب੄ ֫੉ ҅࢑ val yNoise by yNoiseAnimation.animateFloat( initialValue = 40f, targetValue = -40f, animationSpec = infiniteRepeatable( animation = tween( durationMillis = duration, easing = FastOutSlowInEasing, ), repeatMode = RepeatMode.Reverse, ), ) y = ((waterLevel * height).toInt() + yNoise).toInt() return y } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  51. Yч ҳೞӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 69 @Composable fun calculateY(height: Int,

    waterLevel: Float ): Int { var y by remember { mutableStateOf(0) } val duration = Random.nextInt(300) + 300 // ޖೠ গפݫ੉࣌ ҅ࣘ ߈ࠂೠ׮. val yNoiseAnimation = rememberInfiniteTransition() // ౵ب੄ ֫੉ ҅࢑ val yNoise by yNoiseAnimation.animateFloat( initialValue = 40f, targetValue = -40f, animationSpec = infiniteRepeatable( animation = tween( durationMillis = duration, easing = FastOutSlowInEasing, ), repeatMode = RepeatMode.Reverse, ), ) y = ((waterLevel * height).toInt() + yNoise).toInt() return y } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  52. Yч ҳೞӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 70 @Composable fun calculateY(height: Int,

    waterLevel: Float ): Int { var y by remember { mutableStateOf(0) } val duration = Random.nextInt(300) + 300 // ޖೠ গפݫ੉࣌ ҅ࣘ ߈ࠂೠ׮. val yNoiseAnimation = rememberInfiniteTransition() // ౵ب੄ ֫੉ ҅࢑ val yNoise by yNoiseAnimation.animateFloat( initialValue = 40f, targetValue = -40f, animationSpec = infiniteRepeatable( animation = tween( durationMillis = duration, easing = FastOutSlowInEasing, ), repeatMode = RepeatMode.Reverse, ), ) y = ((waterLevel * height).toInt() + yNoise).toInt() return y } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  53. Yч ҳೞӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 71 @Composable fun calculateY(height: Int,

    waterLevel: Float ): Int { var y by remember { mutableStateOf(0) } val duration = Random.nextInt(300) + 300 // ޖೠ গפݫ੉࣌ ҅ࣘ ߈ࠂೠ׮. val yNoiseAnimation = rememberInfiniteTransition() // ౵ب੄ ֫੉ ҅࢑ val yNoise by yNoiseAnimation.animateFloat( initialValue = 40f, targetValue = -40f, animationSpec = infiniteRepeatable( animation = tween( durationMillis = duration, easing = FastOutSlowInEasing, ), repeatMode = RepeatMode.Reverse, ), ) y = ((waterLevel * height).toInt() + yNoise).toInt() return y } ೨ब ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  54. Yч ܻझ౟ ҳೞӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 72 @Composable fun calculateYs2(height:

    Int, waterLevel: Float): List<Int> { // ౵ب੄ ѐࣻ val total = 6 return (0..total).map { calculateY( height = height, waterLevel = waterLevel, ) }.toList() } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  55. ౵ب Ӓܻӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 73 @Composable fun Water( waterLevel:

    Float, ) = BoxWithConstraints { require(waterLevel in 0f..1f) { "ৈӝ ࢎ੉ী ੓যঠೣ" } val density = LocalDensity.current val height = with(density) { maxHeight.toPx() } val width = with(density) { maxWidth.toPx() } val waterHeightList = calculateYs(height = height, waterLevel = waterLevel) val currentY = height * waterLevel Canvas(modifier = Modifier.fillMaxSize()) { val path = Path().apply { moveTo(0f, currentY) lineTo(width, currentY) lineTo(width, height) close() } drawPath( path = path, color = Color.Blue, ) } } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  56. ౵ب Ӓܻӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 74 fun DrawWave(yList: List<Int>, width:

    Int, waterHeight: Float): Path = Path().apply { moveTo(0f, waterHeight) // рѺ -> पઁ рѺ ࢎ੉ૉ val interval = width * (1 / (yList.size + 1).toFloat()) yList.forEachIndexed { idx, y -> val segmentIndex = (index + 1) / (aYs.size + 1).toFloat() val x = size.width * segmentIndex //੉ઁ ҋࢶਸ Ӓܾ ର۹ } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  57. ߬૑য ҋࢶ੉ۆ? ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 75 Qudratic Vezier(2ର) ׮਺ ઑ੺੼ਵ۽

    ੉زೞח ࢶਸ োѾೞৈ ҋࢶਸ ݅٘ח Ѫ উ٘۽੉٘ীࢲח 3ରө૑ ૑ਗ੉ оמೞ׮.
 ղо ਗೞח ബҗܳ ঳ਵ۰ݶ 4ѐ੄ ઑ੺੼ਵۿ 
 డহ੉ ࠗ઒ೞ׮. ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  58. ౵ب Ӓܻӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 77 ױࣽೞѱ ߬૑য Ӓې೐ܳ োѾदఃӝীח

    
 ৖җ э਷ ҋࢶ੉ Ӓ۰૕ ࣻ ੓׮. ҋࢶਸ ۣࠗ٘ѱ Ӓܾ ࣻ ੓ח ҕध੉ ೙ਃೞ׮. ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  59. ౵ب Ӓܻӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 79 B੄ ՘੼җ A੄ द੘੼੉

    ੌ஖ೞҊ, B੄ ݃૑݄ ઁয੼(Control Point)৬ A੄ द੘ ઁয੼੉ ੌ஖ೡ ٸ C1 োࣘࢿਸ ݅઒ೠ׮. (੽ࢶਸ ݅઒) C1(Continuity) ҕध ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  60. ౵ب Ӓܻӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 81 // рѺ ՘੄ ߧਤ

    val segmentIndex = (idx + 1) / (yList.size + 1).toFloat() // рѺ ՘੄ x val x = width * segmentIndex cubicTo( x1 = if (idx == 0) 0f else x - interval / 2f, y1 = yList.getOrNull(idx - 1)?.toFloat() ?: waterHeight, // ੺߈ x2 = x - interval / 2f, y2 = y.toFloat(), // ՘੼ x3 = x, y3 = y.toFloat(), ) } cubicTo( x1 = width - interval / 2f, y1 = yList.last().toFloat(), x2 = width.toFloat(), y2 = waterHeight, x3 = width.toFloat(), y3 = waterHeight, ) lineTo(width.toFloat(), 0f) close() } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  61. ౵ب Ӓܻӝ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 82 // рѺ ՘੄ ߧਤ

    val segmentIndex = (idx + 1) / (yList.size + 1).toFloat() // рѺ ՘੄ x val x = width * segmentIndex cubicTo( x1 = if (idx == 0) 0f else x - interval / 2f, y1 = yList.getOrNull(idx - 1)?.toFloat() ?: waterHeight, // ੺߈ x2 = x - interval / 2f, y2 = y.toFloat(), // ՘੼ x3 = x, y3 = y.toFloat(), ) } cubicTo( x1 = width - interval / 2f, y1 = yList.last().toFloat(), x2 = width.toFloat(), y2 = waterHeight, x3 = width.toFloat(), y3 = waterHeight, ) lineTo(width.toFloat(), 0f) close() } ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  62. Animation Specਸ ঌইࠁ੗ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 84 KeyFrame Spring Tween

    Snap Repeatable/InfiniteRepeatable ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  63. Springਵ۽ ࣻ੿೧ࠁ੗ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 88 val duration = Random.nextInt(300)

    + 300 // ޖೠ গפݫ੉࣌ ҅ࣘ ߈ࠂೠ׮. val yNoiseAnimation = rememberInfiniteTransition() // ౵ب੄ ֫੉ ҅࢑ val yNoise by yNoiseAnimation.animateFloat( initialValue = 25f, targetValue = -25f, animationSpec = infiniteRepeatable( animation = tween( durationMillis = duration, easing = FastOutSlowInEasing, ), repeatMode = RepeatMode.Reverse, ), ) y = ((waterLevel * height).toInt() + yNoise).toInt() val springY by animateIntAsState( targetValue = y, animationSpec = spring( dampingRatio = 0.5f, stiffness = 100f, // Spring.StiffnessVeryLow ), ) return springY ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  64. ৈ۞ ѐ੄ ౵بܳ ٜ݅য ੗োझۣѱ ٜ݅যࠁ੗ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 89

    https://www.youtube.com/watch?v=LLfhY4eVwDY ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  65. ৈ۞ ѐ੄ ౵بܳ ٜ݅য ੗োझۣѱ ٜ݅যࠁ੗ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 90

    val aYs = calculateY(height = height, waterLevel = waterLevel) val aYs2 = calculateY(height = height, waterLevel = waterLevel) val aYs3 = calculateY(height = height, waterLevel = waterLevel) drawPath( path = DrawWave( aYs, size, currentY, animatedY, ), color = White, ) drawPath( path = DrawWave( aYs2, size, currentY, animatedY, ), alpha = .5f, color = White, ) drawPath( path = DrawWave( aYs3, size, currentY, animatedY, ), alpha = .3f, color = White, ) https://www.youtube.com/watch?v=LLfhY4eVwDY ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  66. ৈ۞ ѐ੄ ౵بܳ ٜ݅য ੗োझۣѱ ٜ݅যࠁ੗ ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗ 91

    val aYs = calculateY(height = height, waterLevel = waterLevel) val aYs2 = calculateY(height = height, waterLevel = waterLevel) val aYs3 = calculateY(height = height, waterLevel = waterLevel) drawPath( path = DrawWave( aYs, size, currentY, animatedY, ), color = White, ) drawPath( path = DrawWave( aYs2, size, currentY, animatedY, ), alpha = .5f, color = White, ) drawPath( path = DrawWave( aYs3, size, currentY, animatedY, ), alpha = .3f, color = White, ) ޛ গפݫ੉࣌ਸ ٜ݅যࠁ੗
  67. ޤ ߓਕо?? (TL;DR) 94 1. যڃ ҳഅ੉ٚ ױ҅ܳ աׇ ҳഅೞݶ

    ޤٚ औ׮. 2. যڃ ҕध੉ա APIܳ ॄঠೡ૑ ݽܰѷ׮ݶ, AIܳ ഝਊ೧ۄ! ޤ ߓਕо??
  68. ޤ ߓਕо?? (TL;DR) 95 1. যڃ ҳഅ੉ٚ ױ҅ܳ աׇ ҳഅೞݶ

    ޤٚ औ׮. 2. যڃ ҕध੉ա APIܳ ॄঠೡ૑ ݽܰѷ׮ݶ, AIܳ ഝਊ೧ۄ! 3. compose animationਸ ׮ܖח ߑߨ ޤ ߓਕо??
  69. ޤ ߓਕо?? (TL;DR) 96 1. যڃ ҳഅ੉ٚ ױ҅ܳ աׇ ҳഅೞݶ

    ޤٚ औ׮. 2. যڃ ҕध੉ա APIܳ ॄঠೡ૑ ݽܰѷ׮ݶ, AIܳ ഝਊ೧ۄ! 3. compose animationਸ ׮ܖח ߑߨ 4. compose canvasܳ ࢎਊೞח ߑߨ ޤ ߓਕо??