An Android animation framework that gracefully handles interruptions. Currently in beta, as some details of the API may change in future releases.
- Smoothly blend animations started at different times together
- Easily read and describe animations with a Kotlin DSL
- Use it anywhere where an
Animator
is accepted (e.g.Transition
s) - Animate any property on any object, not just
View
s - Test animations with provided immediate and mock implementations
def blendVersion = "0.2.5"
implementation "com.wealthfront:blend:${blendVersion}"
testImplementation "com.wealthfront:blend-test:${blendVersion}"
In order to make quality animations, we need them to be two things:
- Correct: Animations should be predictable and always end in the correct state, regardless of interruptions or competing animations.
- Smooth: Animations should be continuous (i.e. never visibly jump in value) and retain velocity (i.e. never visibly jump in speed), regardless of interruptions or competing animations.
Most frameworks in android are missing one or the other, especially since only physics-based animations handle interruption by new animations properly (and even then only if you call animateToFinalPosition()
instead of start()
). The problem with the physics-based animation framework is that they don't extend Animator
, so you can't use them in many places in the android framework (e.g. Transition
s).
Why is handling interruptions important? The main reason is that we, as developers, don't control when the user interacts with the app. This means that the user could reverse that animation or click something else and move it somewhere else before it's done.
Apple added additive animations by default in iOS 8, and has a great developer talk about it (starting around 17:50). If you don't want to spend the time to watch it, here's a gif showing the difference:
First, see above for download instructions. Then, create a Blend
instance (called blend
in the following examples) to get started.
blend {
target(view).animations {
fadeIn()
translationX(200f)
}
}.start()
Blend includes a wide library of standard properties to animate in AdditiveViewProperties
, including elevation and margin/padding.
If you want all targets to do the same thing:
blend {
target(view1, view2).animations {
fadeIn()
translationX(200f)
}
}.start()
Or if you want them to do different things at the same time:
blend {
target(view1).animations {
fadeIn()
}
target(view2).animations {
translationX(200f)
}
}.start()
blend {
accelerate()
duration(200, MILLISECONDS)
target(view1).animations {
fadeIn()
}
}.start()
Blend includes some convenience methods to cover most cases with the Material-Design-approved easing functions.
defaultEase()
(default) = FastOutSlowInaccelerate()
= FastOutLinearIndecelerate()
= LinearOutSlowInovershoot()
= Overshoot
Animations chained in time will blend in the same way as normal animations. That is, whichever set of animations .start()
ed last will define the end state (when all running animations have settled). This means that during the animation, some properties may go outside of their expected bounds, but will always end up in a predictable state.
blend {
target(view1).animations {
fadeIn()
}
}.then {
target(view2).animations {
translationX(200f)
}
}.start()
blend {
target(view1).animations {
fadeIn()
}
}.with {
startDelay(200, MILLISECONDS)
target(view2).animations {
translationX(200f)
}
}.start()
Calling .prepare()
instead of .start()
will return a standard Android Animator
(or rather, a subclass of it called BlendableAnimator
) that includes the entire animation specified. Use this to interface with the android framework.
val myAnimator = blend {
target(view).animations {
fadeIn()
translationX(200f)
}
}.prepare()
To animate properties not included in the standard DSL (see AdditiveViewProperties
) or on a non-view object, simply create a new AdditiveProperty
and either pass it into AnimationBuilder.genericProperty
or create an extension function on AnimationBuilder
.
Using makeExternalAdditiveViewProperty
is recommended for non-view objects that reside in the UI.
For unit-testing your views, use MockBlend
. It allows for assertions on a given subject like:
mockBlend.assertThat(view).isFadedIn()
mockBlend.assertThat(view).isCollapsed()
mockBlend.assertThat(view).hasPropertySetTo(MY_CUSTOM_PROPERTY, 100f)
mockBlend.assertThat(otherView).isNeverAnimated()
For integration testing, or for unit testing where you want the animations to be applied like a set
call rather than queued for verification, use ImmediateBlend
. Pass an instance in and all animations and listeners will run immediately, without ever starting a ValueAnimator.
To change the default values of animations, simply subclass Blend
and override createBlendableAnimator
with your default values (duration, ease/interpolator, etc.) set.
Copyright 2019 Wealthfront, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.