Improvements to the pop animations

On Android 14's the pop interpolation now uses the speed at which the back gesture happens

Same "magic" applies to the back gesture for the full cover
This commit is contained in:
Jays2Kings 2023-10-22 01:29:05 -07:00
parent 4a34efdd7d
commit 24e8eeea59
5 changed files with 139 additions and 29 deletions

View file

@ -3,10 +3,18 @@ package eu.kanade.tachiyomi.ui.base.controller
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import androidx.core.animation.doOnCancel
import androidx.core.animation.doOnEnd
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler
import eu.kanade.tachiyomi.ui.main.MainActivity
import kotlin.math.max
import kotlin.math.roundToLong
class CrossFadeChangeHandler : AnimatorChangeHandler {
constructor() : super()
@ -34,22 +42,68 @@ class CrossFadeChangeHandler : AnimatorChangeHandler {
}
if (isPush) {
if (from != null) {
animatorSet.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, -from.width.toFloat() * 0.2f))
animatorSet.play(
ObjectAnimator.ofFloat(
from,
View.TRANSLATION_X,
-from.width.toFloat() * 0.2f,
),
)
}
if (to != null) {
animatorSet.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, to.width.toFloat() * 0.2f, 0f))
animatorSet.play(
ObjectAnimator.ofFloat(
to,
View.TRANSLATION_X,
to.width.toFloat() * 0.2f,
0f,
),
)
}
} else {
if (from != null) {
animatorSet.play(ObjectAnimator.ofFloat(from, View.TRANSLATION_X, from.width.toFloat() * 0.2f))
animatorSet.play(
ObjectAnimator.ofFloat(
from,
View.TRANSLATION_X,
from.width.toFloat() * 0.2f,
),
)
}
if (to != null) {
// Allow this to have a nice transition when coming off an aborted push animation or
// from back gesture
val fromLeft = from?.translationX ?: 0F
animatorSet.play(ObjectAnimator.ofFloat(to, View.TRANSLATION_X, fromLeft - to.width * 0.2f, 0f))
val fromLeft = from?.translationX ?: 0f
animatorSet.play(
ObjectAnimator.ofFloat(
to,
View.TRANSLATION_X,
fromLeft - to.width * 0.2f,
0f,
),
)
}
}
animatorSet.duration = if (isPush) {
200
} else {
from?.let {
val startX = from.width.toFloat() * 0.2f
((startX - it.x) / startX) * 150f
}?.roundToLong() ?: 150
}
animatorSet.doOnCancel { to?.x = 0f }
animatorSet.doOnEnd { to?.x = 0f }
if (!isPush && from?.x != null && from.x != 0f &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
) {
animatorSet.interpolator = if (MainActivity.backVelocity != 0f) {
DecelerateInterpolator(max(1f, MainActivity.backVelocity))
} else {
LinearOutSlowInInterpolator()
}
MainActivity.backVelocity = 0f
}
return animatorSet
}

View file

@ -16,12 +16,14 @@ import android.graphics.Rect
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.SystemClock
import android.provider.Settings
import android.view.GestureDetector
import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewGroup
import android.view.Window
@ -65,6 +67,7 @@ import com.getkeepsafe.taptargetview.TapTargetView
import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import com.google.common.primitives.Floats.max
import com.google.common.primitives.Ints.max
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.Migrations
@ -172,6 +175,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
var hingeGapSize = 0
private set
val velocityTracker: VelocityTracker by lazy { VelocityTracker.obtain() }
private val actionButtonSize: Pair<Int, Int> by lazy {
val attrs = intArrayOf(android.R.attr.minWidth, android.R.attr.minHeight)
val ta = obtainStyledAttributes(androidx.appcompat.R.style.Widget_AppCompat_ActionButton, attrs)
@ -254,8 +258,30 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
super.onCreate(savedInstanceState)
backPressedCallback = object : OnBackPressedCallback(enabled = true) {
var startTime: Long = 0
var lastX: Float = 0f
var lastY: Float = 0f
var controllerHandlesBackPress = false
override fun handleOnBackPressed() {
if (controllerHandlesBackPress &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
lastX != 0f && lastY != 0f
) {
val motionEvent = MotionEvent.obtain(
startTime,
SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP,
lastX,
lastY,
0,
)
velocityTracker.addMovement(motionEvent)
motionEvent.recycle()
velocityTracker.computeCurrentVelocity(2, 5f)
backVelocity = max(1f, velocityTracker.getAxisVelocity(MotionEvent.AXIS_X))
}
lastX = 0f
lastY = 0f
backCallback()
}
@ -276,12 +302,22 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
controllerHandlesBackPress = true
}
if (controllerHandlesBackPress) {
startTime = SystemClock.uptimeMillis()
velocityTracker.clear()
val motionEvent = MotionEvent.obtain(startTime, startTime, MotionEvent.ACTION_DOWN, backEvent.touchX, backEvent.touchY, 0)
velocityTracker.addMovement(motionEvent)
motionEvent.recycle()
(controller as? BackHandlerControllerInterface)?.handleOnBackStarted(backEvent)
}
}
override fun handleOnBackProgressed(backEvent: BackEventCompat) {
if (controllerHandlesBackPress) {
val motionEvent = MotionEvent.obtain(startTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, backEvent.touchX, backEvent.touchY, 0)
lastX = backEvent.touchX
lastY = backEvent.touchY
velocityTracker.addMovement(motionEvent)
motionEvent.recycle()
val controller = router.backstack.lastOrNull()?.controller as? BackHandlerControllerInterface
controller?.handleOnBackProgressed(backEvent)
}
@ -1517,6 +1553,7 @@ open class MainActivity : BaseActivity<MainActivityBinding>() {
const val INTENT_SEARCH_FILTER = "filter"
var chapterIdToExitTo = 0L
var backVelocity = 0f
}
}

View file

@ -14,19 +14,22 @@ import android.graphics.Shader
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.PowerManager
import android.os.SystemClock
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.animation.DecelerateInterpolator
import androidx.activity.BackEventCompat
import androidx.activity.ComponentDialog
import androidx.activity.OnBackPressedCallback
import androidx.activity.addCallback
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.animation.addListener
import androidx.core.content.ContextCompat
import androidx.core.view.WindowInsetsCompat.Type.systemBars
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updateLayoutParams
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import androidx.transition.ChangeBounds
import androidx.transition.ChangeImageTransform
import androidx.transition.TransitionManager
@ -40,6 +43,7 @@ import eu.kanade.tachiyomi.util.system.powerManager
import eu.kanade.tachiyomi.util.system.rootWindowInsetsCompat
import eu.kanade.tachiyomi.util.view.animateBlur
import uy.kohesive.injekt.injectLazy
import kotlin.math.max
import kotlin.math.min
class FullCoverDialog(val controller: MangaDetailsController, drawable: Drawable, private val thumbView: View) :
@ -49,6 +53,7 @@ class FullCoverDialog(val controller: MangaDetailsController, drawable: Drawable
val binding = FullCoverDialogBinding.inflate(LayoutInflater.from(context), null, false)
val preferences: PreferencesHelper by injectLazy()
val velocityTracker: VelocityTracker by lazy { VelocityTracker.obtain() }
private val ratio = 5f.dpToPx
private val fullRatio = 0f
private val shortAnimationDuration = (
@ -85,26 +90,38 @@ class FullCoverDialog(val controller: MangaDetailsController, drawable: Drawable
}
val backPressedCallback = object : OnBackPressedCallback(enabled = true) {
var startX = 0f
var startY = 0f
var startTime: Long = 0
var lastX: Float = 0f
var lastY: Float = 0f
override fun handleOnBackPressed() {
if (binding.mangaCoverFull.isClickable) {
val motionEvent = MotionEvent.obtain(startTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, lastX, lastY, 0)
velocityTracker.addMovement(motionEvent)
motionEvent.recycle()
animateBack()
}
}
override fun handleOnBackStarted(backEvent: BackEventCompat) {
startX = backEvent.touchX
startY = backEvent.touchY
super.handleOnBackStarted(backEvent)
startTime = SystemClock.uptimeMillis()
velocityTracker.clear()
val motionEvent = MotionEvent.obtain(startTime, startTime, MotionEvent.ACTION_DOWN, backEvent.touchX, backEvent.touchY, 0)
velocityTracker.addMovement(motionEvent)
motionEvent.recycle()
}
override fun handleOnBackProgressed(backEvent: BackEventCompat) {
val maxProgress = min(backEvent.progress, 0.4f)
val motionEvent = MotionEvent.obtain(startTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_MOVE, backEvent.touchX, backEvent.touchY, 0)
lastX = backEvent.touchX
lastY = backEvent.touchY
velocityTracker.addMovement(motionEvent)
motionEvent.recycle()
binding.mangaCoverFull.scaleX = 1f - maxProgress * 0.6f
binding.mangaCoverFull.translationX =
maxProgress * 100f * (if (backEvent.swipeEdge == BackEventCompat.EDGE_LEFT) 1 else -1)
binding.mangaCoverFull.translationY =
-maxProgress * 150f
binding.mangaCoverFull.translationY = -maxProgress * 150f
binding.mangaCoverFull.scaleY = 1f - maxProgress * 0.6f
}
@ -264,21 +281,24 @@ class FullCoverDialog(val controller: MangaDetailsController, drawable: Drawable
}
// Zoom out back to tc thumbnail
val transitionSet2 = TransitionSet()
val bound2 = ChangeBounds()
transitionSet2.addTransition(bound2)
val changeImageTransform2 = ChangeImageTransform()
transitionSet2.addTransition(changeImageTransform2)
transitionSet2.duration = shortAnimationDuration
TransitionManager.beginDelayedTransition(binding.root, transitionSet2)
val transitionSet = TransitionSet()
transitionSet.addTransition(ChangeBounds())
transitionSet.addTransition(ChangeImageTransform())
transitionSet.duration = shortAnimationDuration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
velocityTracker.computeCurrentVelocity(2, 40f)
transitionSet.interpolator = DecelerateInterpolator(max(1f, velocityTracker.getAxisVelocity(MotionEvent.AXIS_X)))
}
TransitionManager.beginDelayedTransition(binding.root, transitionSet)
if (Build.VERSION.SDK_INT >= 31) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
activity?.window?.decorView?.animateBlur(20f, 0.1f, 50, true)?.apply {
startDelay = shortAnimationDuration - 100
}?.start()
}
val attrs = window?.attributes
val ogDim = attrs?.dimAmount ?: 0.25f
velocityTracker.recycle()
// AnimationSet for backdrop because idk how to use TransitionSet
AnimatorSet().apply {
@ -303,13 +323,12 @@ class FullCoverDialog(val controller: MangaDetailsController, drawable: Drawable
binding.btnSave.alpha = it.animatedValue as Float
}
}
playTogether(radiusAnimator, dimAnimator, saveAnimator)
val objectAnimator = ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, expandedImageView.scaleX, 1f)
val objectAnimator1 = ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, expandedImageView.scaleY, 1f)
val objectAnimator2 = ObjectAnimator.ofFloat(expandedImageView, View.TRANSLATION_X, expandedImageView.translationX, 0f)
val objectAnimator3 = ObjectAnimator.ofFloat(expandedImageView, View.TRANSLATION_Y, expandedImageView.translationY, 0f)
playTogether(radiusAnimator, dimAnimator, saveAnimator, objectAnimator, objectAnimator1, objectAnimator2, objectAnimator3)
play(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, 1f))
play(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, 1f))
play(ObjectAnimator.ofFloat(expandedImageView, View.TRANSLATION_X, 0f))
play(ObjectAnimator.ofFloat(expandedImageView, View.TRANSLATION_Y, 0f))
addListener(
onEnd = {

View file

@ -812,14 +812,14 @@ fun Controller.withFadeTransaction(): RouterTransaction {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
FadeChangeHandler()
} else {
CrossFadeChangeHandler(duration = 200, removesFromViewOnPush = isLowRam)
CrossFadeChangeHandler(removesFromViewOnPush = isLowRam)
},
)
.popChangeHandler(
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
FadeChangeHandler()
} else {
CrossFadeChangeHandler(duration = 150, removesFromViewOnPush = isLowRam)
CrossFadeChangeHandler(removesFromViewOnPush = isLowRam)
},
)
}

View file

@ -540,7 +540,7 @@ fun View.updateGradiantBGRadius(
}
}
@RequiresApi(31)
@RequiresApi(Build.VERSION_CODES.S)
fun View.animateBlur(
@FloatRange(from = 0.1) from: Float,
@FloatRange(from = 0.1) to: Float,