diff --git a/app/build.gradle b/app/build.gradle index 0fc4e940c..5055bc680 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -428,7 +428,6 @@ dependencies { implementation Deps.androidx_lifecycle_viewmodel implementation Deps.androidx_core implementation Deps.androidx_core_ktx - implementation Deps.androidx_dynamic_animation implementation Deps.androidx_transition implementation Deps.androidx_work_ktx implementation Deps.google_material diff --git a/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt b/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt index cd507391b..a7bfe9d4d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/ToolbarGestureHandler.kt @@ -6,19 +6,19 @@ package org.mozilla.fenix.browser import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator import android.app.Activity import android.graphics.PointF import android.graphics.Rect -import android.util.TypedValue import android.view.View import android.view.ViewConfiguration import androidx.annotation.Dimension import androidx.annotation.Dimension.DP +import androidx.core.animation.doOnEnd import androidx.core.graphics.contains import androidx.core.graphics.toPoint import androidx.core.view.isVisible -import androidx.dynamicanimation.animation.DynamicAnimation -import androidx.dynamicanimation.animation.FlingAnimation +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator import mozilla.components.browser.session.Session import mozilla.components.browser.session.SessionManager import mozilla.components.support.ktx.android.util.dpToPx @@ -61,11 +61,6 @@ class ToolbarGestureHandler( private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop private val minimumFlingVelocity = ViewConfiguration.get(activity).scaledMinimumFlingVelocity - private val defaultVelocity = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - MINIMUM_ANIMATION_VELOCITY, - activity.resources.displayMetrics - ) private var gestureDirection = GestureDirection.LEFT_TO_RIGHT @@ -143,25 +138,12 @@ class ToolbarGestureHandler( ) { val destination = getDestination() if (destination is Destination.Tab && isGestureComplete(velocityX)) { - animateToNextTab(velocityX, destination.session) + animateToNextTab(destination.session) } else { animateCanceledGesture(velocityX) } } - private fun createFlingAnimation( - view: View, - minValue: Float, - maxValue: Float, - startVelocity: Float - ): FlingAnimation = - FlingAnimation(view, DynamicAnimation.TRANSLATION_X).apply { - setMinValue(minValue) - setMaxValue(maxValue) - setStartVelocity(startVelocity) - friction = ViewConfiguration.getScrollFriction() - } - private fun getDestination(): Destination { val isLtr = activity.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR val currentSession = sessionManager.selectedSession ?: return Destination.None @@ -234,73 +216,59 @@ class ToolbarGestureHandler( abs(velocityX) >= minimumFlingVelocity) } - private fun getVelocityFromFling(velocityX: Float): Float { - return max(abs(velocityX), defaultVelocity) + private fun getAnimator(finalContextX: Float, duration: Long): ValueAnimator { + return ValueAnimator.ofFloat(contentLayout.translationX, finalContextX).apply { + this.duration = duration + this.interpolator = LinearOutSlowInInterpolator() + addUpdateListener { animator -> + val value = animator.animatedValue as Float + contentLayout.translationX = value + tabPreview.translationX = when (gestureDirection) { + GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset + GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset + } + } + } } - private fun animateToNextTab(velocityX: Float, session: Session) { + private fun animateToNextTab(session: Session) { val browserFinalXCoordinate: Float = when (gestureDirection) { GestureDirection.RIGHT_TO_LEFT -> -windowWidth.toFloat() - previewOffset GestureDirection.LEFT_TO_RIGHT -> windowWidth.toFloat() + previewOffset } - val animationVelocity = when (gestureDirection) { - GestureDirection.RIGHT_TO_LEFT -> -getVelocityFromFling(velocityX) - GestureDirection.LEFT_TO_RIGHT -> getVelocityFromFling(velocityX) - } // Finish animating the contentLayout off screen and tabPreview on screen - createFlingAnimation( - view = contentLayout, - minValue = min(0f, browserFinalXCoordinate), - maxValue = max(0f, browserFinalXCoordinate), - startVelocity = animationVelocity - ).addUpdateListener { _, value, _ -> - tabPreview.translationX = when (gestureDirection) { - GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset - GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset - } - }.addEndListener { _, _, _, _ -> - contentLayout.translationX = 0f - sessionManager.select(session) + getAnimator(browserFinalXCoordinate, FINISHED_GESTURE_ANIMATION_DURATION).apply { + doOnEnd { + contentLayout.translationX = 0f + sessionManager.select(session) - // Fade out the tab preview to prevent flickering - val shortAnimationDuration = - activity.resources.getInteger(android.R.integer.config_shortAnimTime) - tabPreview.animate() - .alpha(0f) - .setDuration(shortAnimationDuration.toLong()) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - tabPreview.isVisible = false - } - }) + // Fade out the tab preview to prevent flickering + val shortAnimationDuration = + activity.resources.getInteger(android.R.integer.config_shortAnimTime) + tabPreview.animate() + .alpha(0f) + .setDuration(shortAnimationDuration.toLong()) + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + tabPreview.isVisible = false + } + }) + } }.start() } - private fun animateCanceledGesture(gestureVelocity: Float) { - val velocity = if (getDestination() is Destination.None) { - defaultVelocity + private fun animateCanceledGesture(velocityX: Float) { + val duration = if (abs(velocityX) >= minimumFlingVelocity) { + CANCELED_FLING_ANIMATION_DURATION } else { - getVelocityFromFling(gestureVelocity) - }.let { v -> - when (gestureDirection) { - GestureDirection.RIGHT_TO_LEFT -> v - GestureDirection.LEFT_TO_RIGHT -> -v - } + CANCELED_GESTURE_ANIMATION_DURATION } - createFlingAnimation( - view = contentLayout, - minValue = min(0f, contentLayout.translationX), - maxValue = max(0f, contentLayout.translationX), - startVelocity = velocity - ).addUpdateListener { _, value, _ -> - tabPreview.translationX = when (gestureDirection) { - GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset - GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset + getAnimator(0f, duration).apply { + doOnEnd { + tabPreview.isVisible = false } - }.addEndListener { _, _, _, _ -> - tabPreview.isVisible = false }.start() } @@ -336,16 +304,25 @@ class ToolbarGestureHandler( */ private const val OVERSCROLL_HIDE_PERCENT = 0.20 - /** - * The speed of the fling animation (in dp per second). - */ - @Dimension(unit = DP) - private const val MINIMUM_ANIMATION_VELOCITY = 1500f - /** * The size of the gap between the tab preview and content layout. */ @Dimension(unit = DP) private const val PREVIEW_OFFSET = 48 + + /** + * Animation duration when switching to another tab + */ + private const val FINISHED_GESTURE_ANIMATION_DURATION = 250L + + /** + * Animation duration gesture is canceled due to the swipe not being far enough + */ + private const val CANCELED_GESTURE_ANIMATION_DURATION = 200L + + /** + * Animation duration gesture is canceled due to a swipe in the opposite direction + */ + private const val CANCELED_FLING_ANIMATION_DURATION = 150L } } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 40933b3c0..8ba27fd3e 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -27,7 +27,6 @@ object Versions { const val androidx_paging = "2.1.0" const val androidx_transition = "1.3.0" const val androidx_work = "2.2.0" - const val androidx_dynamic_animation = "1.0.0" const val google_material = "1.1.0" const val google_flexbox = "2.0.1" @@ -172,7 +171,6 @@ object Deps { const val androidx_recyclerview = "androidx.recyclerview:recyclerview:${Versions.androidx_recyclerview}" const val androidx_core = "androidx.core:core:${Versions.androidx_core}" const val androidx_core_ktx = "androidx.core:core-ktx:${Versions.androidx_core}" - const val androidx_dynamic_animation = "androidx.dynamicanimation:dynamicanimation:${Versions.androidx_dynamic_animation}" const val androidx_transition = "androidx.transition:transition:${Versions.androidx_transition}" const val androidx_work_ktx = "androidx.work:work-runtime-ktx:${Versions.androidx_work}" const val androidx_work_testing = "androidx.work:work-testing:${Versions.androidx_work}"