Анимированный круг в Jetpack Compose

Вопрос или проблема

Я хочу сделать анимированный круг в Jetpack Compose, чтобы он анимировался, как на картинке. Я не смог сделать это так, как хотел. Код представлен ниже.
круг

    val transition = rememberInfiniteTransition()

    val scale = transition.animateFloat(
        initialValue = 1.9f,
        targetValue = if(isAnimating) 2.05f else 1.9f,
        animationSpec = infiniteRepeatable(
            animation = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
            repeatMode = RepeatMode.Reverse
        )
    )

    val deformFactor = transition.animateFloat(
            initialValue = 0f,
            targetValue = if(isAnimating) randomFloatInRange(0.1f, 0.2f) else 0f,
            animationSpec = infiniteRepeatable(
                animation = tween(durationMillis = 1000, easing = LinearOutSlowInEasing),
                repeatMode = RepeatMode.Reverse
            )
        )

    Canvas(modifier = Modifier
        .fillMaxSize()) {
        val center = Offset(size.width / 2, size.height / 2)
        val radius = 100f * scale.value

        val path = Path()
        path.moveTo(center.x + radius * (1 - deformFactor.value), center.y) 
        for (angle in 0..360 step 10) {
            val rad = Math.toRadians(angle.toDouble())
            val x = center.x + radius * (1 + deformFactor.value * Math.sin(rad * 5)) * Math.cos(rad)
            val y = center.y + radius * (1 + deformFactor.value * Math.sin(rad * 5)) * Math.sin(rad)
            path.lineTo(x.toFloat(), y.toFloat())
        }

        path.close()
        drawPath(path, color.value)
    }

Ответ или решение

Для создания анимированного круга в Jetpack Compose, который будет выглядеть так, как показано на изображении, можно использовать подход, основанный на анимации с помощью rememberInfiniteTransition и Canvas. Я приведу пример кода, который должен помочь вам достичь желаемого эффекта.

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.GeometricTransform
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.PI
import kotlin.random.Random

@Composable
fun AnimatedCircle(isAnimating: Boolean) {
    val transition = rememberInfiniteTransition()

    val scale = transition.animateFloat(
        initialValue = 1.9f,
        targetValue = if(isAnimating) 2.05f else 1.9f,
        animationSpec = infiniteRepeatable(
            animation = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
            repeatMode = RepeatMode.Reverse
        )
    )

    val deformFactor = transition.animateFloat(
        initialValue = 0f,
        targetValue = if(isAnimating) Random.nextFloat() * (0.2f - 0.1f) + 0.1f else 0f,
        animationSpec = infiniteRepeatable(
            animation = tween(durationMillis = 1000, easing = LinearOutSlowInEasing),
            repeatMode = RepeatMode.Reverse
        )
    )

    Canvas(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.background)) {
        val center = center
        val radius = 100f * scale.value
        val path = Path()

        path.moveTo(center.x + radius * (1 - deformFactor.value), center.y)
        for (angle in 0..360 step 10) {
            val rad = Math.toRadians(angle.toDouble())
            val x = center.x + radius * (1 + deformFactor.value * sin(rad * 5)) * cos(rad)
            val y = center.y + radius * (1 + deformFactor.value * sin(rad * 5)) * sin(rad)
            path.lineTo(x.toFloat(), y.toFloat())
        }

        path.close()
        drawPath(path, color = Color.Blue, style = Stroke(width = 4.dp.toPx()))
    }
}

@Composable
fun MainScreen() {
    var isAnimating by remember { mutableStateOf(true) }

    AnimatedCircle(isAnimating = isAnimating)

    // Для управления анимацией можно добавить логику, например, по клику
    Modifier.pointerInput(Unit) {
        detectTapGestures(onClick = {
            isAnimating = !isAnimating
        })
    }
}

Объяснение кода:

  1. Анимация: Мы используем rememberInfiniteTransition для создания двух анимаций: одна для изменения масштаба (вызывающая эффект увеличения и уменьшения размера), а другая – для деформации круга (создающая эффект волны на границе).

  2. Path: Мы строим Path, представляющий форму круга с использованием математических функций синуса и косинуса, изменяя радиус и добавляя деформацию в зависимости от текущего значения анимации.

  3. Canvas: Мы используем Canvas для рисования круга с анимацией.

  4. Обработка клика: Для управления запуском и остановкой анимации можно добавить обработчик кликов.

Этот код создаст анимированный круг, который будет плавно изменять форму и размер в зависимости от анимации. Вы можете изменить параметры анимации для достижения нужного эффекта.

Оцените материал
Добавить комментарий

Капча загружается...