mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
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:
parent
4a34efdd7d
commit
24e8eeea59
5 changed files with 139 additions and 29 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue