Connect with us

Программирование

Круговое раскрытие как в WhatsApp с помощью Jetpack Compose

Несмотря на экономическую ситуацию и быстро меняющийся рынок.

Опубликовано

/

     
     

В этой статье я покажу вам, как создать анимацию кругового раскрытия, которую использует WhatsApp, с помощью Jetpack Compose.

1. Использование AnimatedVisibility

Моей первой попыткой было использование AnimatedVisibility для достижения чего-то подобного.

AnimatedVisibility(
    visible = visible,
) {
    BottomSheet()
}

Этот код эквивалентен:

AnimatedVisibility(
    visible = visible,
    enter = expandVertically(),
    exit = shrinkVertically()
) {
    BottomSheet()
}

Однако мне не удалось добиться кругового движения, которого я искал.

2. Использование GenericShape

Моя вторая попытка заключалась в том, чтобы использовать фигуру, к которой будет прикреплен мой composable. GenericShape позволяет мне написать любую форму, но нет ничего, что позволило бы мне нарисовать круг, поэтому я начал с прямоугольника.

GenericShape { size, direction ->
    val center = size.width / 2f

    this.addRect(
        Rect(
            left = center - center * it,
            top = center - center * it,
            right = center + center * it,
            bottom = size.height
        )
    )

    close()
}

У меня получилось кое-что анимировать, но это все еще не круговое движение, которое я искал.

3. Использование addArc

Учитывая, что прямоугольник — это не то, что мне нужно, я попробовал использовать addArc для рисования дуги вокруг прямоугольника.

GenericShape { size, direction ->
    val centerV = size.height / 2f
    val centerH = size.width / 2f

    addArc(
        Rect(
            left = centerH - centerH * it * 2,
            top = size.height - size.height * it,
            right = centerH + centerH * it * 2,
            bottom = size.height + size.height * it
        ),
        0f,
        -180f
    )

    close()
}

Результат был намного лучше, однако я не смог добиться полного раскрытия, поэтому часть компонента всегда оставалась обрезанной.

4. Использование cubicTo

Что если вместо обычной дуги использовать более «сложную»? Я попробовал использовать cubicTo, он использует кубическую кривую Безье. Она имеет 4 контрольные точки, что позволяет мне нарисовать что-то похожее на полукруг.

Круговое раскрытие как в WhatsApp с помощью Jetpack Compose

Если я реализую кубическую кривую Безье того же размера, что и композит, то верхние углы все равно будут обрезаны, поэтому мне нужен способ сделать так, чтобы кривая охватывала весь composable.

Для этого я сделал кривую в два раза больше, чем композит. Для оси x это делается путем умножения ширины на 2, для оси y я использую функцию lerp, которая переходит от height к -height.

GenericShape { size, direction ->
    val centerH = size.width / 2f

    moveTo(
        x = centerH - centerH * it * 2,
        y = size.height,
    )

    val currentHeight = lerp(size.height, -size.height, it)

    cubicTo(
        x1 = centerH - centerH * it,
        y1 = currentHeight,
        x2 = centerH + centerH * it,
        y2 = currentHeight,
        x3 = centerH + centerH * it * 2,
        y3 = size.height,
    )

    close()
}

Результат получился довольно близким к тому, что я хотел, но кривая растет не пропорционально.

Я продолжал корректировать код, пока не пришел к чему-то, что было довольно близко к анимации, которую использует WhatsApp.

GenericShape { size, _ ->
    val centerH = size.width / 2f
    val multiplierW = 1.5f + size.height / size.width

    moveTo(
        x = centerH - centerH * progress * multiplierW,
        y = size.height,
    )

    val currentWidth = (centerH * progress * multiplierW * 2.5f)

    cubicTo(
        x1 = centerH - centerH * progress * 1.5f,
        y1 = size.height - currentWidth * 0.5f,
        x2 = centerH + centerH * progress * 1.5f,
        y2 = size.height - currentWidth * 0.5f,
        x3 = centerH + centerH * progress * multiplierW,
        y3 = size.height,
    )

    close()
}

Бонус

Если вы снова посмотрите на анимацию WhatsApp, вы можете заметить, что предметы внутри карты также немного анимируются. Их масштаб в начале, вероятно, составляет 90%, затем он увеличивается до 110% и, наконец, уменьшается до 100%.

Чтобы реализовать это, я использовал animateFloatAsState.

var scale by remember { mutableStateOf(0.9f) }
val animation = animateFloatAsState(
    targetValue = scale,
    animationSpec = FloatSpringSpec(
        dampingRatio = 0.3f,
    )
)

LaunchedEffect(Unit) {
    delay(20 + position.toLong() * 20)
    scale = 1f
}

Image(
    modifier = Modifier
       ...
        .graphicsLayer {
            scaleX = animation.value
            scaleY = animation.value
        }
)

Это простая анимация, которая изменяется от 0.9 до 1 и использует пружинную анимацию. Я добавляю задержку 20 мс + еще одну задержку, основанную на позиции элемента, чтобы все не анимировалось одновременно. Если вы хотите, чтобы что-то появилось позже, просто задайте ему более высокую позицию.

Это не совсем похоже на то, что использует WhatsApp, но если вы можете продолжать настраивать задержку и значение dampingRatio, то, вероятно, получите то, что вас устраивает.

Исходники находятся здесь.

А оригинал статьи тут.

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.

Наши партнеры:

LEGALBET

Мобильные приложения для ставок на спорт
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: