[go: nahoru, domu]

blob: 30955a3c6a7428b7a3902834a5aead6654539d02 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.foundation.animation
import androidx.compose.ui.unit.Density
import kotlin.math.exp
import kotlin.math.ln
import kotlin.math.sign
/**
* Earth's gravity in SI units (m/s^2); used to compute deceleration based on friction.
*/
private const val GravityEarth = 9.80665f
private const val InchesPerMeter = 39.37f
/**
* The default rate of deceleration for a fling if not specified in the
* [AndroidFlingCalculator] constructor.
*/
private val DecelerationRate = (ln(0.78) / ln(0.9)).toFloat()
/**
* Compute the rate of deceleration based on pixel density, physical gravity
* and a [coefficient of friction][friction].
*/
private fun computeDeceleration(friction: Float, density: Float): Float =
GravityEarth * InchesPerMeter * density * 160f * friction
/**
* Configuration for Android-feel flinging motion at the given density.
*
* @param friction scroll friction.
* @param density density of the screen. Use LocalDensity to get current density in composition.
*/
internal class AndroidFlingCalculator(
private val friction: Float,
val density: Density
) {
/**
* A density-specific coefficient adjusted to physical values.
*/
private val magicPhysicalCoefficient: Float = computeDeceleration(density)
/**
* Computes the rate of deceleration in pixels based on
* the given [density].
*/
private fun computeDeceleration(density: Density) =
computeDeceleration(0.84f, density.density)
private fun getSplineDeceleration(velocity: Float): Double = AndroidFlingSpline.deceleration(
velocity,
friction * magicPhysicalCoefficient
)
/**
* Compute the duration in milliseconds of a fling with an initial velocity of [velocity]
*/
fun flingDuration(velocity: Float): Long {
val l = getSplineDeceleration(velocity)
val decelMinusOne = DecelerationRate - 1.0
return (1000.0 * exp(l / decelMinusOne)).toLong()
}
/**
* Compute the distance of a fling in units given an initial [velocity] of units/second
*/
fun flingDistance(velocity: Float): Float {
val l = getSplineDeceleration(velocity)
val decelMinusOne = DecelerationRate - 1.0
return (
friction * magicPhysicalCoefficient
* exp(DecelerationRate / decelMinusOne * l)
).toFloat()
}
/**
* Compute all interesting information about a fling of initial velocity [velocity].
*/
fun flingInfo(velocity: Float): FlingInfo {
val l = getSplineDeceleration(velocity)
val decelMinusOne = DecelerationRate - 1.0
return FlingInfo(
initialVelocity = velocity,
distance = (
friction * magicPhysicalCoefficient
* exp(DecelerationRate / decelMinusOne * l)
).toFloat(),
duration = (1000.0 * exp(l / decelMinusOne)).toLong()
)
}
/**
* Info about a fling started with [initialVelocity]. The units of [initialVelocity]
* determine the distance units of [distance] and the time units of [duration].
*/
data class FlingInfo(
val initialVelocity: Float,
val distance: Float,
val duration: Long
) {
fun position(time: Long): Float {
val splinePos = if (duration > 0) time / duration.toFloat() else 1f
return distance * sign(initialVelocity) *
AndroidFlingSpline.flingPosition(splinePos).distanceCoefficient
}
fun velocity(time: Long): Float {
val splinePos = if (duration > 0) time / duration.toFloat() else 1f
return AndroidFlingSpline.flingPosition(splinePos).velocityCoefficient *
sign(initialVelocity) * distance / duration * 1000.0f
}
}
}