This repository has been archived by the owner on Nov 12, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 884
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Try and make iOS fling more 'native'
At the moment Compose MP using the same spline decay animation that Android uses, which isn't great. This PR changes this by using our own fling behavior throughout the app. This allows us to tweak the decay animation to better match iOS. It isn't perfect, but it's a lot better than default.
- Loading branch information
1 parent
7408224
commit a14ff28
Showing
11 changed files
with
153 additions
and
5 deletions.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
common/ui/compose/src/androidMain/kotlin/app/tivi/common/compose/FlingBehavior.android.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2023, Christopher Banes and the Tivi project contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package app.tivi.common.compose | ||
|
||
import androidx.compose.animation.core.DecayAnimationSpec | ||
import androidx.compose.animation.rememberSplineBasedDecay | ||
import androidx.compose.runtime.Composable | ||
|
||
@Composable | ||
actual fun decayAnimationSpec(): DecayAnimationSpec<Float> = rememberSplineBasedDecay() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
common/ui/compose/src/commonMain/kotlin/app/tivi/common/compose/FlingBehavior.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// Copyright 2021, Google LLC, Christopher Banes and the Tivi project contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package app.tivi.common.compose | ||
|
||
import androidx.compose.animation.core.AnimationState | ||
import androidx.compose.animation.core.DecayAnimationSpec | ||
import androidx.compose.animation.core.LinearEasing | ||
import androidx.compose.animation.core.Spring | ||
import androidx.compose.animation.core.animateDecay | ||
import androidx.compose.animation.core.spring | ||
import androidx.compose.animation.core.tween | ||
import androidx.compose.foundation.ExperimentalFoundationApi | ||
import androidx.compose.foundation.gestures.FlingBehavior | ||
import androidx.compose.foundation.gestures.ScrollScope | ||
import androidx.compose.foundation.gestures.snapping.SnapFlingBehavior | ||
import androidx.compose.foundation.gestures.snapping.SnapLayoutInfoProvider | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.remember | ||
import androidx.compose.ui.MotionDurationScale | ||
import androidx.compose.ui.platform.LocalDensity | ||
import kotlin.math.abs | ||
import kotlinx.coroutines.withContext | ||
|
||
@Composable | ||
fun rememberTiviFlingBehavior(): FlingBehavior { | ||
val decayAnimationSpec = decayAnimationSpec() | ||
return remember(decayAnimationSpec) { | ||
DefaultFlingBehavior(decayAnimationSpec) | ||
} | ||
} | ||
|
||
@ExperimentalFoundationApi | ||
@Composable | ||
fun rememberTiviSnapFlingBehavior( | ||
snapLayoutInfoProvider: SnapLayoutInfoProvider, | ||
): SnapFlingBehavior { | ||
val density = LocalDensity.current | ||
val highVelocityApproachSpec: DecayAnimationSpec<Float> = rememberTiviDecayAnimationSpec() | ||
return remember(snapLayoutInfoProvider, highVelocityApproachSpec, density) { | ||
SnapFlingBehavior( | ||
snapLayoutInfoProvider = snapLayoutInfoProvider, | ||
lowVelocityAnimationSpec = tween(easing = LinearEasing), | ||
highVelocityAnimationSpec = highVelocityApproachSpec, | ||
snapAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), | ||
density = density, | ||
) | ||
} | ||
} | ||
|
||
@Composable | ||
fun rememberTiviDecayAnimationSpec(): DecayAnimationSpec<Float> { | ||
val spec = decayAnimationSpec() | ||
return remember { spec } | ||
} | ||
|
||
@Composable | ||
internal expect fun decayAnimationSpec(): DecayAnimationSpec<Float> | ||
|
||
internal val DefaultScrollMotionDurationScale = object : MotionDurationScale { | ||
override val scaleFactor: Float get() = 1f | ||
} | ||
|
||
internal class DefaultFlingBehavior( | ||
private val flingDecay: DecayAnimationSpec<Float>, | ||
private val motionDurationScale: MotionDurationScale = DefaultScrollMotionDurationScale, | ||
) : FlingBehavior { | ||
override suspend fun ScrollScope.performFling(initialVelocity: Float): Float { | ||
// come up with the better threshold, but we need it since spline curve gives us NaNs | ||
return withContext(motionDurationScale) { | ||
if (abs(initialVelocity) > 1f) { | ||
var velocityLeft = initialVelocity | ||
var lastValue = 0f | ||
AnimationState( | ||
initialValue = 0f, | ||
initialVelocity = initialVelocity, | ||
).animateDecay(flingDecay) { | ||
val delta = value - lastValue | ||
val consumed = scrollBy(delta) | ||
lastValue = value | ||
velocityLeft = this.velocity | ||
// avoid rounding errors and stop if anything is unconsumed | ||
if (abs(delta - consumed) > 0.5f) cancelAnimation() | ||
} | ||
velocityLeft | ||
} else { | ||
initialVelocity | ||
} | ||
} | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
common/ui/compose/src/iosMain/kotlin/app/tivi/common/compose/FlingBehavior.ios.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Copyright 2023, Christopher Banes and the Tivi project contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package app.tivi.common.compose | ||
|
||
import androidx.compose.animation.core.DecayAnimationSpec | ||
import androidx.compose.animation.core.exponentialDecay | ||
import androidx.compose.runtime.Composable | ||
import androidx.compose.runtime.remember | ||
|
||
@Composable | ||
actual fun decayAnimationSpec(): DecayAnimationSpec<Float> { | ||
return remember { exponentialDecay(frictionMultiplier = 0.85f) } | ||
} |
11 changes: 11 additions & 0 deletions
11
common/ui/compose/src/jvmMain/kotlin/app/tivi/common/compose/FlingBehavior.desktop.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2023, Christopher Banes and the Tivi project contributors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package app.tivi.common.compose | ||
|
||
import androidx.compose.animation.core.DecayAnimationSpec | ||
import androidx.compose.animation.rememberSplineBasedDecay | ||
import androidx.compose.runtime.Composable | ||
|
||
@Composable | ||
actual fun decayAnimationSpec(): DecayAnimationSpec<Float> = rememberSplineBasedDecay() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters