1
0
Fork 0

For #13030 - Use material design animation values for swipe to switch tabs.

master
Kainalu Hagiwara 2020-08-10 16:25:07 -07:00 committed by Mugurell
parent 2743c37b40
commit c119070e21
3 changed files with 56 additions and 82 deletions

View File

@ -428,7 +428,6 @@ dependencies {
implementation Deps.androidx_lifecycle_viewmodel implementation Deps.androidx_lifecycle_viewmodel
implementation Deps.androidx_core implementation Deps.androidx_core
implementation Deps.androidx_core_ktx implementation Deps.androidx_core_ktx
implementation Deps.androidx_dynamic_animation
implementation Deps.androidx_transition implementation Deps.androidx_transition
implementation Deps.androidx_work_ktx implementation Deps.androidx_work_ktx
implementation Deps.google_material implementation Deps.google_material

View File

@ -6,19 +6,19 @@ package org.mozilla.fenix.browser
import android.animation.Animator import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.Activity import android.app.Activity
import android.graphics.PointF import android.graphics.PointF
import android.graphics.Rect import android.graphics.Rect
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewConfiguration import android.view.ViewConfiguration
import androidx.annotation.Dimension import androidx.annotation.Dimension
import androidx.annotation.Dimension.DP import androidx.annotation.Dimension.DP
import androidx.core.animation.doOnEnd
import androidx.core.graphics.contains import androidx.core.graphics.contains
import androidx.core.graphics.toPoint import androidx.core.graphics.toPoint
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
import androidx.dynamicanimation.animation.FlingAnimation
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.support.ktx.android.util.dpToPx import mozilla.components.support.ktx.android.util.dpToPx
@ -61,11 +61,6 @@ class ToolbarGestureHandler(
private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop
private val minimumFlingVelocity = ViewConfiguration.get(activity).scaledMinimumFlingVelocity 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 private var gestureDirection = GestureDirection.LEFT_TO_RIGHT
@ -143,25 +138,12 @@ class ToolbarGestureHandler(
) { ) {
val destination = getDestination() val destination = getDestination()
if (destination is Destination.Tab && isGestureComplete(velocityX)) { if (destination is Destination.Tab && isGestureComplete(velocityX)) {
animateToNextTab(velocityX, destination.session) animateToNextTab(destination.session)
} else { } else {
animateCanceledGesture(velocityX) 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 { private fun getDestination(): Destination {
val isLtr = activity.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR val isLtr = activity.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
val currentSession = sessionManager.selectedSession ?: return Destination.None val currentSession = sessionManager.selectedSession ?: return Destination.None
@ -234,73 +216,59 @@ class ToolbarGestureHandler(
abs(velocityX) >= minimumFlingVelocity) abs(velocityX) >= minimumFlingVelocity)
} }
private fun getVelocityFromFling(velocityX: Float): Float { private fun getAnimator(finalContextX: Float, duration: Long): ValueAnimator {
return max(abs(velocityX), defaultVelocity) 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) { val browserFinalXCoordinate: Float = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> -windowWidth.toFloat() - previewOffset GestureDirection.RIGHT_TO_LEFT -> -windowWidth.toFloat() - previewOffset
GestureDirection.LEFT_TO_RIGHT -> 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 // Finish animating the contentLayout off screen and tabPreview on screen
createFlingAnimation( getAnimator(browserFinalXCoordinate, FINISHED_GESTURE_ANIMATION_DURATION).apply {
view = contentLayout, doOnEnd {
minValue = min(0f, browserFinalXCoordinate), contentLayout.translationX = 0f
maxValue = max(0f, browserFinalXCoordinate), sessionManager.select(session)
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)
// Fade out the tab preview to prevent flickering // Fade out the tab preview to prevent flickering
val shortAnimationDuration = val shortAnimationDuration =
activity.resources.getInteger(android.R.integer.config_shortAnimTime) activity.resources.getInteger(android.R.integer.config_shortAnimTime)
tabPreview.animate() tabPreview.animate()
.alpha(0f) .alpha(0f)
.setDuration(shortAnimationDuration.toLong()) .setDuration(shortAnimationDuration.toLong())
.setListener(object : AnimatorListenerAdapter() { .setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) { override fun onAnimationEnd(animation: Animator?) {
tabPreview.isVisible = false tabPreview.isVisible = false
} }
}) })
}
}.start() }.start()
} }
private fun animateCanceledGesture(gestureVelocity: Float) { private fun animateCanceledGesture(velocityX: Float) {
val velocity = if (getDestination() is Destination.None) { val duration = if (abs(velocityX) >= minimumFlingVelocity) {
defaultVelocity CANCELED_FLING_ANIMATION_DURATION
} else { } else {
getVelocityFromFling(gestureVelocity) CANCELED_GESTURE_ANIMATION_DURATION
}.let { v ->
when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> v
GestureDirection.LEFT_TO_RIGHT -> -v
}
} }
createFlingAnimation( getAnimator(0f, duration).apply {
view = contentLayout, doOnEnd {
minValue = min(0f, contentLayout.translationX), tabPreview.isVisible = false
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
} }
}.addEndListener { _, _, _, _ ->
tabPreview.isVisible = false
}.start() }.start()
} }
@ -336,16 +304,25 @@ class ToolbarGestureHandler(
*/ */
private const val OVERSCROLL_HIDE_PERCENT = 0.20 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. * The size of the gap between the tab preview and content layout.
*/ */
@Dimension(unit = DP) @Dimension(unit = DP)
private const val PREVIEW_OFFSET = 48 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
} }
} }

View File

@ -27,7 +27,6 @@ object Versions {
const val androidx_paging = "2.1.0" const val androidx_paging = "2.1.0"
const val androidx_transition = "1.3.0" const val androidx_transition = "1.3.0"
const val androidx_work = "2.2.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_material = "1.1.0"
const val google_flexbox = "2.0.1" 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_recyclerview = "androidx.recyclerview:recyclerview:${Versions.androidx_recyclerview}"
const val androidx_core = "androidx.core:core:${Versions.androidx_core}" 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_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_transition = "androidx.transition:transition:${Versions.androidx_transition}"
const val androidx_work_ktx = "androidx.work:work-runtime-ktx:${Versions.androidx_work}" 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}" const val androidx_work_testing = "androidx.work:work-testing:${Versions.androidx_work}"