Функция spring и эффект отскока

Последнее обновление: 13.04.2024

Встроенная функция spring() позволяет добавить к анимации эффект отскока, подобно тому, как мяч ударяется об землю и после этого еще несколько раз подпрыгивает, делая несколько отскоков, пока совсем не остановит свое движение. Функция spring() имеет следующие параметры:

public fun <T> spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
): SpringSpec<T>
  • dampingRatio: степень затухания - определяет скорость, с которой затухает эффект подпрыгивания. Определяется как значение типа Float где 1.0 представляет отсутствие отскока, а 0.1 — самый высокий отскок. Вместо использования значений Float при настройке степени затухания также доступны следующие предопределенные константы:

    • DampingRatioHighBouncy

    • DampingRatioLowBouncy

    • DampingRatioMediumBouncy

    • DampingRatioNoBouncy

  • stiffness: жесткость при отскоке. При использовании меньшей жесткости диапазон движения при подпрыгивании будет больше. Для определения жесткости можно использовать ряд встроенных констант:

    • StiffnessHigh

    • StiffnessLow

    • StiffnessMedium

    • StiffnessMediumLow

    • StiffnessVeryLow

  • visibilityThreshold: предел видимости

Для демонстрации эффекта отскока рассмотрим следующее приложение:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val circleHeight = 150     // высота (диаметр) круга
            val startOffset = 10      // начальный отступ
            val endOffset = LocalConfiguration.current.screenHeightDp - circleHeight     // предельная позиция
            var circleOffset by remember { mutableStateOf(startOffset)}
            val offset: Dp by animateDpAsState(
                targetValue = circleOffset.dp,
                animationSpec =if (circleOffset==endOffset) {
                    spring(dampingRatio = 0.3f)     // сильный отскок
                } else {
                    spring(dampingRatio = 1.0f)     // отсутствие отскока
                }
            )

            Row(Modifier.fillMaxSize()) {
                Button({circleOffset = if (circleOffset==startOffset) endOffset else startOffset },
                    Modifier.padding(10.dp)) {
                    Text("Start", fontSize = 22.sp)
                }
                Box(Modifier.padding(top=offset).size(circleHeight.dp).clip(CircleShape).background(Color.DarkGray))
            }
        }
    }
}

Здесь определяется компонент Box в виде круга, и по нажатию на кнопку запускается анимация, в результате которой компонент движется вниз, пока не ударится о низ устройства.

Эффект отскока и spring в Jetpack Compose на Kotlin в Android

Для целей анимации определяем ряд переменных, как начальная и конечная позиция круга и его диаметр (высота)

val circleHeight = 150     // высота (диаметр) круга
val startOffset = 10      // начальный отступ
val endOffset = LocalConfiguration.current.screenHeightDp - circleHeight     // предельная позиция

Далее определем состояние, которое будет хранить текущий отступ круга:

var circleOffset by remember { mutableStateOf(startOffset)}

Затем создаем анимацию с помощью функции animateDpAsState(), которая будет определять отступ:

val offset: Dp by animateDpAsState(
    targetValue = circleOffset.dp,
    
    animationSpec =if (circleOffset==endOffset) {
        spring(dampingRatio = 0.3f)     // сильный отскок
    } else {
        spring(dampingRatio = 1.0f)     // отсутствие отскока
    }
)

Здесь прежде всего определяем targetValue - значение, к которому надо перейти в процессе анимации. Ему передается значение переменной circleOffset, преобразованное в значение Dp. Так как, поскольку по нажатию на кнопку мы будем изменять значение circleOffset со startOffset на endOffset и обратно, то впоследствии circleOffset будет хранить следующее значение, к которому надо перейти.

Параметр animationSpec, который задает анимацию, также использует условное выражение. Если значение, к которому надо перейти, является конечным - endOffset, то с помощью функции spring() в конце формируем отскок. Значение 0.3f можно характеризовать как сильный отскок. Иначе если мы находимся на конечной позиции, когда круг упал вниз, и его движением с отскоком завершилось, то просто возвращаем круг в начальную точку. В этом случае нам отскок не нужен, поэтому передаем в функцию spring() значение 1.0f

Для запуска анимации определяем кнопку, которая меняет значение circleOffset и тем самым вызывает анимацию:

Button({circleOffset = if (circleOffset==startOffset) endOffset else startOffset },

И в конце идет собственно компонент Box, стилизованный под круг:

Box(Modifier.padding(top=offset).size(circleHeight.dp).clip(CircleShape).background(Color.DarkGray))

Его значение отступа с верху привязано к переменной offset, которая генерируется функцией animateDpAsState()

В примере выше в функцию spring() передавались числовые значения, и также можно передавать предустановленные константы:

import androidx.compose.animation.core.Spring.DampingRatioHighBouncy
import androidx.compose.animation.core.Spring.DampingRatioNoBouncy
...........................................

val offset: Dp by animateDpAsState(
    targetValue = circleOffset.dp,
    animationSpec =if (circleOffset==endOffset) {
        spring(dampingRatio = DampingRatioHighBouncy)     // сильный отскок
    } else {
        spring(dampingRatio = DampingRatioNoBouncy)     // отсутствие отскока
    }
)

Аналогичным образом можно использовать другие параметры функции spring(). Например, установим жесткость:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.Spring.DampingRatioMediumBouncy
import androidx.compose.animation.core.Spring.DampingRatioNoBouncy
import androidx.compose.animation.core.Spring.StiffnessHigh
import androidx.compose.animation.core.Spring.StiffnessVeryLow
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val circleHeight = 150 
            val startOffset = 10 
            val endOffset = LocalConfiguration.current.screenHeightDp - circleHeight 
            var circleOffset by remember { mutableStateOf(startOffset)}
            val offset: Dp by animateDpAsState(
                targetValue = circleOffset.dp,
                animationSpec =if (circleOffset==endOffset) {
                    spring(dampingRatio = DampingRatioMediumBouncy, stiffness = StiffnessVeryLow)  // жесткость StiffnessVeryLow
                } else {
                    spring(dampingRatio = DampingRatioNoBouncy) 
                }
            )
            val offset1: Dp by animateDpAsState(
                targetValue = circleOffset.dp,
                animationSpec =if (circleOffset==endOffset) {
                    spring(dampingRatio = DampingRatioMediumBouncy, stiffness = StiffnessHigh)  // жесткость StiffnessHigh
                } else {
                    spring(dampingRatio = DampingRatioNoBouncy) 
                }
            )
            Row(Modifier.fillMaxSize()) {
                println("Offset: ${offset.value}")
                Button({circleOffset = if (circleOffset==startOffset) endOffset else startOffset },
                    Modifier.padding(10.dp)) {
                    Text("Start", fontSize = 22.sp)
                }
                Box(Modifier.padding(top=offset).size(circleHeight.dp).clip(CircleShape).background(Color.DarkGray))
                Box(Modifier.padding(top=offset1).size(circleHeight.dp).clip(CircleShape).background(Color.DarkGray))
            }
        }
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850