For #13030 - Use material design animation values for swipe to switch tabs.
This commit is contained in:
parent
2743c37b40
commit
c119070e21
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}"
|
||||
|
|
Loading…
Reference in New Issue
Block a user