Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update transitions from slide to fade and zoom #227

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Versions.androidXCoreVersion
import Versions.archVersion
import Versions.blendVersion
import Versions.butterKnifeVersion
import Versions.constraintLayoutVersion
import Versions.coroutinesVersion
Expand Down Expand Up @@ -46,6 +47,8 @@ object Dependencies {
const val androidXCore = "androidx.core:core-ktx:$androidXCoreVersion"

const val material = "com.google.android.material:material:$materialVersion"
const val blend = "com.wealthfront:blend-library:$blendVersion"
const val blendTest = "com.wealthfront:blend-test:$blendVersion"
const val junit = "junit:junit:$junitVersion"
const val junitTestExt = "androidx.test.ext:junit-ktx:$junitTestExtVersion"
const val truth = "com.google.truth:truth:$truthVersion"
Expand Down
3 changes: 2 additions & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
object Versions {
const val compileSdkVersion = 30
const val minSdkVersion = 18
const val minSdkVersion = 21
const val targetSdkVersion = 30

const val kotlinVersion = "1.5.20"
Expand All @@ -26,6 +26,7 @@ object Versions {
const val okhttpVersion = "4.4.0"
const val javaInjectVersion = "1"
const val materialVersion = "1.4.0"
const val blendVersion = "0.2.2"
const val coroutinesVersion = "1.4.3"

const val testCoreVersion = "1.4.0"
Expand Down
2 changes: 2 additions & 0 deletions magellan-library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies {
implementation Dependencies.inject
implementation Dependencies.coroutines
implementation Dependencies.coroutinesAndroid
api Dependencies.blend

testImplementation project(':internal-test-support')
testImplementation Dependencies.testCore
Expand All @@ -67,6 +68,7 @@ dependencies {
testImplementation Dependencies.archTesting
testImplementation Dependencies.robolectric
testImplementation Dependencies.coroutinesTest
testImplementation Dependencies.blendTest

// Bug in AGP: Follow this issue - https://issuetracker.google.com/issues/141840950
// lintPublish project(':magellan-lint')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,92 @@
package com.wealthfront.magellan.transitions

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.util.Property
import android.view.View
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import android.view.ViewGroup
import com.wealthfront.blend.Blend
import com.wealthfront.blend.dsl.AnimatorBuilder
import com.wealthfront.blend.dsl.fadeIn
import com.wealthfront.blend.dsl.fadeOut
import com.wealthfront.blend.dsl.scale
import com.wealthfront.magellan.Direction
import com.wealthfront.magellan.navigation.NavigationEvent

private const val ENTER_TRANSITION_LENGTH_MILLIS = 300L
private const val EXIT_TRANSITION_LENGTH_MILLIS = 250L
private const val SCALE_UP_FACTOR = 1.15f
private const val SCALE_DOWN_FACTOR = 0.85f

/**
* The default transition for all [NavigationEvent]s where another [MagellanTransition] isn't
* defined. Performs a right-to-left slide on entrance and a left-to-right slide on exit. Uses a
* [FastOutSlowInInterpolator] for both per
* defined. Performs a fade and zoom (similar to Android 12 settings) on entrance and exit. Uses
* [AnimatorBuilder.emphasizeEase] for both per
* [the Material Design guidelines](https://material.io/design/motion/speed.html#easing).
* The exit animation is also slightly shorter than the entrance, per the guidelines.
*/
public class DefaultTransition : MagellanTransition {
public class DefaultTransition(private val blend: Blend = Blend()) : MagellanTransition {

override fun animate(
from: View?,
to: View,
direction: Direction,
onAnimationEndCallback: () -> Unit
) {
val animator = createAnimator(from, to, direction)
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onAnimationEndCallback()
when (direction) {
Direction.FORWARD -> animateForward(from, to, onAnimationEndCallback)
Direction.BACKWARD -> animateBackward(from, to, onAnimationEndCallback)
}.let { }
}

private fun animateForward(from: View?, to: View, onAnimationEndCallback: () -> Unit) {
blend {
immediate()
target(to).animations {
fadeOut()
scale(SCALE_DOWN_FACTOR)
}
doOnStart {
// Put `to` behind `from`
val parent = to.parent as ViewGroup
parent.removeView(to)
parent.addView(to, 0)
}
}.then {
emphasizeEase()
duration(ENTER_TRANSITION_LENGTH_MILLIS)
from?.let { fromView ->
target(fromView).animations {
fadeOut()
scale(SCALE_UP_FACTOR)
}
}
target(to).animations {
fadeIn()
scale(1f)
}
})
animator.start()
doOnFinishedEvenIfInterrupted(onAnimationEndCallback)
}.start()
}

private fun createAnimator(
from: View?,
to: View,
direction: Direction
): AnimatorSet {
val sign = direction.sign()
val axis: Property<View, Float> = View.TRANSLATION_X
val toTranslation = sign * to.width
val set = AnimatorSet()
if (from != null) {
val fromTranslation = sign * -from.width
set.play(ObjectAnimator.ofFloat(from, axis, 0f, fromTranslation.toFloat()))
}
set.play(ObjectAnimator.ofFloat(to, axis, toTranslation.toFloat(), 0f))
set.interpolator = FastOutSlowInInterpolator()
return set
private fun animateBackward(from: View?, to: View, onAnimationEndCallback: () -> Unit) {
blend {
immediate()
target(to).animations {
fadeOut()
scale(SCALE_UP_FACTOR)
}
}.then {
emphasizeEase()
duration(EXIT_TRANSITION_LENGTH_MILLIS)
from?.let { fromView ->
target(fromView).animations {
fadeOut()
scale(SCALE_DOWN_FACTOR)
}
}
target(to).animations {
fadeIn()
scale(1f)
}
doOnFinishedEvenIfInterrupted(onAnimationEndCallback)
}.start()
}
}
Original file line number Diff line number Diff line change
@@ -1,52 +1,93 @@
package com.wealthfront.magellan.transitions

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.util.Property
import android.view.View
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import android.view.ViewGroup
import com.wealthfront.blend.Blend
import com.wealthfront.blend.dsl.AnimatorBuilder
import com.wealthfront.blend.dsl.fadeIn
import com.wealthfront.blend.dsl.fadeOut
import com.wealthfront.blend.dsl.scale
import com.wealthfront.blend.dsl.translationY
import com.wealthfront.magellan.Direction
import com.wealthfront.magellan.Direction.BACKWARD
import com.wealthfront.magellan.Direction.FORWARD

private const val ENTER_TRANSITION_LENGTH_MILLIS = 300L
private const val EXIT_TRANSITION_LENGTH_MILLIS = 250L
private const val SCALE_DOWN_FACTOR = 0.85f
private const val HEIGHT_OFFSET_FACTOR = 0.2f

/**
* A vertical version of [DefaultTransition]. Performs a bottom-to-top slide on entrance and a
* top-to-bottom slide on exit. Uses a [FastOutSlowInInterpolator] for both per
* A vertical version of [DefaultTransition]. Performs an upward slide and fade in on entrance and a
* downward slide and fade out on exit. Uses [AnimatorBuilder.emphasizeEase] for both per
* [the Material Design guidelines](https://material.io/design/motion/speed.html#easing).
* The exit animation is also slightly shorter than the entrance, per the guidelines.
*/
public class ShowTransition : MagellanTransition {
public class ShowTransition(private val blend: Blend = Blend()) : MagellanTransition {

override fun animate(
from: View?,
to: View,
direction: Direction,
onAnimationEndCallback: () -> Unit
) {
val animator = createAnimator(from, to, direction)
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onAnimationEndCallback()
when (direction) {
FORWARD -> animateForward(from, to, onAnimationEndCallback)
BACKWARD -> animateBackward(from, to, onAnimationEndCallback)
}.let { }
}

private fun animateForward(from: View?, to: View, onAnimationEndCallback: () -> Unit) {
blend {
immediate()
target(to).animations {
fadeOut()
translationY(to.height * HEIGHT_OFFSET_FACTOR)
}
})
animator.start()
}.then {
emphasizeEase()
duration(ENTER_TRANSITION_LENGTH_MILLIS)
from?.let { fromView ->
target(fromView).animations {
fadeOut()
scale(SCALE_DOWN_FACTOR)
}
}
target(to).animations {
fadeIn()
translationY(0f)
}
doOnFinishedEvenIfInterrupted(onAnimationEndCallback)
}.start()
}

private fun createAnimator(
from: View?,
to: View,
direction: Direction
): AnimatorSet {
val axis: Property<View, Float> = View.TRANSLATION_Y
val fromTranslation: Int = if (direction == FORWARD) 0 else from!!.height
val toTranslation: Int = if (direction == BACKWARD) 0 else to.height
val set = AnimatorSet()
if (from != null) {
set.play(ObjectAnimator.ofFloat(from, axis, 0f, fromTranslation.toFloat()))
}
set.play(ObjectAnimator.ofFloat(to, axis, toTranslation.toFloat(), 0f))
set.interpolator = FastOutSlowInInterpolator()
return set
private fun animateBackward(from: View?, to: View, onAnimationEndCallback: () -> Unit) {
blend {
immediate()
target(to).animations {
fadeOut()
scale(SCALE_DOWN_FACTOR)
}
doOnStart {
// Put `to` behind `from`
val parent = to.parent as ViewGroup
parent.removeView(to)
parent.addView(to, 0)
}
}.then {
emphasizeEase()
duration(EXIT_TRANSITION_LENGTH_MILLIS)
from?.let { fromView ->
target(fromView).animations {
fadeOut()
translationY(fromView.height * HEIGHT_OFFSET_FACTOR)
}
}
target(to).animations {
fadeIn()
scale(1f)
}
doOnFinishedEvenIfInterrupted(onAnimationEndCallback)
}.start()
}
}

This file was deleted.