浏览代码

Fix scroll animation when system animation is disabled (#7509)

Ivan Iskandar 2 年之前
父节点
当前提交
ba93060e59

+ 1 - 1
app/src/main/java/eu/kanade/presentation/browse/ExtensionLangFilterScreen.kt

@@ -3,7 +3,6 @@ package eu.kanade.presentation.browse
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.asPaddingValues
 import androidx.compose.foundation.layout.navigationBars
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.material3.Switch
 import androidx.compose.material3.Text
@@ -15,6 +14,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalContext
 import eu.kanade.presentation.components.EmptyScreen
+import eu.kanade.presentation.components.LazyColumn
 import eu.kanade.presentation.components.LoadingScreen
 import eu.kanade.presentation.components.PreferenceRow
 import eu.kanade.tachiyomi.R

+ 1 - 1
app/src/main/java/eu/kanade/presentation/category/components/CategoryContent.kt

@@ -2,12 +2,12 @@ package eu.kanade.presentation.category.components
 
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.unit.dp
 import eu.kanade.domain.category.model.Category
+import eu.kanade.presentation.components.LazyColumn
 
 @Composable
 fun CategoryContent(

+ 34 - 4
app/src/main/java/eu/kanade/presentation/components/LazyList.kt

@@ -1,11 +1,9 @@
 package eu.kanade.presentation.components
 
 import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.calculateEndPadding
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListScope
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.rememberLazyListState
@@ -17,6 +15,38 @@ import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.dp
 import eu.kanade.presentation.util.drawVerticalScrollbar
+import eu.kanade.presentation.util.flingBehaviorIgnoringMotionScale
+
+/**
+ * LazyColumn with fling animation fix
+ *
+ * @see flingBehaviorIgnoringMotionScale
+ */
+@Composable
+fun LazyColumn(
+    modifier: Modifier = Modifier,
+    state: LazyListState = rememberLazyListState(),
+    contentPadding: PaddingValues = PaddingValues(0.dp),
+    reverseLayout: Boolean = false,
+    verticalArrangement: Arrangement.Vertical =
+        if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
+    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
+    flingBehavior: FlingBehavior = flingBehaviorIgnoringMotionScale(),
+    userScrollEnabled: Boolean = true,
+    content: LazyListScope.() -> Unit,
+) {
+    androidx.compose.foundation.lazy.LazyColumn(
+        modifier = modifier,
+        state = state,
+        contentPadding = contentPadding,
+        reverseLayout = reverseLayout,
+        verticalArrangement = verticalArrangement,
+        horizontalAlignment = horizontalAlignment,
+        flingBehavior = flingBehavior,
+        userScrollEnabled = userScrollEnabled,
+        content = content,
+    )
+}
 
 /**
  * LazyColumn with scrollbar.
@@ -30,7 +60,7 @@ fun ScrollbarLazyColumn(
     verticalArrangement: Arrangement.Vertical =
         if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
     horizontalAlignment: Alignment.Horizontal = Alignment.Start,
-    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+    flingBehavior: FlingBehavior = flingBehaviorIgnoringMotionScale(),
     userScrollEnabled: Boolean = true,
     content: LazyListScope.() -> Unit,
 ) {
@@ -69,7 +99,7 @@ fun FastScrollLazyColumn(
     verticalArrangement: Arrangement.Vertical =
         if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
     horizontalAlignment: Alignment.Horizontal = Alignment.Start,
-    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
+    flingBehavior: FlingBehavior = flingBehaviorIgnoringMotionScale(),
     userScrollEnabled: Boolean = true,
     content: LazyListScope.() -> Unit,
 ) {

+ 1 - 1
app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt

@@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.only
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.systemBars
-import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListScope
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.lazy.rememberLazyListState
@@ -52,6 +51,7 @@ import com.google.accompanist.swiperefresh.SwipeRefresh
 import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
 import eu.kanade.domain.chapter.model.Chapter
 import eu.kanade.presentation.components.ExtendedFloatingActionButton
+import eu.kanade.presentation.components.LazyColumn
 import eu.kanade.presentation.components.Scaffold
 import eu.kanade.presentation.components.SwipeRefreshIndicator
 import eu.kanade.presentation.components.VerticalFastScroller

+ 60 - 0
app/src/main/java/eu/kanade/presentation/util/Scrollable.kt

@@ -0,0 +1,60 @@
+package eu.kanade.presentation.util
+
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.animateDecay
+import androidx.compose.animation.rememberSplineBasedDecay
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.MotionDurationScale
+import kotlinx.coroutines.withContext
+import kotlin.math.abs
+
+/**
+ * FlingBehavior that always uses the default motion scale.
+ *
+ * This makes the scrolling animation works like View's lists
+ * when "Remove animation" settings is on.
+ */
+@Composable
+fun flingBehaviorIgnoringMotionScale(): FlingBehavior {
+    val flingSpec = rememberSplineBasedDecay<Float>()
+    return remember(flingSpec) {
+        DefaultFlingBehavior(flingSpec)
+    }
+}
+
+private val DefaultMotionDurationScale = object : MotionDurationScale {
+    // Use default motion scale factor
+    override val scaleFactor: Float = 1f
+}
+
+private class DefaultFlingBehavior(
+    private val flingDecay: DecayAnimationSpec<Float>,
+) : 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 if (abs(initialVelocity) > 1f) {
+            var velocityLeft = initialVelocity
+            var lastValue = 0f
+            withContext(DefaultMotionDurationScale) {
+                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) this.cancelAnimation()
+                }
+            }
+            velocityLeft
+        } else {
+            initialVelocity
+        }
+    }
+}