The world of
Android Animations
+Nelson Glauber
@nglauber
www.nglauber.com.br
Por que animações?
Animations!
• No mundo real coisas não aparecem “do nada”, elas
se movimentam
• O olho humano é sensível ao movimento e isso
pode ajudar o usuário a entender melhor seu
aplicativo
• Pense na sua aplicação como um palco onde a
informação entra e sai do palco de maneira
coordenada.
https://developer.android.com/design/get-started/principles.html
1ª geração de
animações
View Animations
<alpha
android:duration="1000"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
val alphaAnim = AnimationUtils.loadAnimation(

context, R.anim.my_animation);
imageView.startAnimation(anim);
val alphaAnim = AlphaAnimation(1f, 0f)
alphaAnim.duration = 1000
imageView.startAnimation(alphaAnim)
res/anim/my_animation.xml
val anim = RotateAnimation(
0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f)
anim.duration = 1000
imageView.startAnimation(anim)
<rotate
android:duration="1000"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%" />
val anim = ScaleAnimation(1, 3, 1, 3,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f)
anim.duration = 1000
myView.startAnimation(anim)
<scale
android:duration="1000"
android:fromXScale="1.0"
android:toXScale="3.0"
android:fromYScale="1.0"
android:toYScale="3.0"
android:pivotX="50%"
android:pivotY="50%" />
Não corte sua animação!
<ViewGroup
...
android:clipChildren="false"
android:clipToPadding="false" />
val anim = TranslateAnimation(
Animation.RELATIVE_TO_SELF, 0f,
Animation.RELATIVE_TO_SELF, 1.0f,
Animation.RELATIVE_TO_SELF, 0f,
Animation.RELATIVE_TO_SELF, 2.0f)
anim.duration = 1000
myView.startAnimation(anim)
<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="100%"
android:fromYDelta="0"
android:toYDelta="200%" />
Combinando animações
val animationSet = AnimationSet(true)
animationSet.addAnimation(rotate)
animationSet.addAnimation(alpha)
animationSet.addAnimation(scale)
animationSet.addAnimation(translate)
animationSet.setDuration(1000)
myView.startAnimation(animationSet)
<set android:duration="1000">
<rotate ... />
<alpha ... />
<scale ... />
<translate ... />
</set>
val interpolator = …
LinearInterpolator()
AccelerateDecelerateInterpolator()
BounceInterpolator()
AccelerateInterpolator(1.0f) // factor (optional)
AnticipateInterpolator(2.0f) // <- tension (optional)
// tension, extra tension (optional)
AnticipateOvershootInterpolator(2.0f, 1.5f)
CycleInterpolator(2f) // <- cycles
DecelerateInterpolator(1.0f) // <- factor (optional)
OvershootInterpolator(2.0f) // <- tension (optional)
FastOutLinearInInterpolator()

FastOutSlowInInterpolator()

LinearOutSlowInInterpolator()
anim.interpolator = interpolator
<translate ...
android:interpolator="@android:anim/overshoot_interpolator"/>
Interpolator class Resource ID
LinearInterpolator @android:anim/linear_interpolator
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator
BounceInterpolator @android:anim/bounce_interpolator
AccelerateInterpolator @android:anim/accelerate_interpolator
AnticipateInterpolator @android:anim/anticipate_interpolator
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator
DecelerateInterpolator @android:anim/decelerate_interpolator
OvershootInterpolator @android:anim/overshoot_interpolator
FastOutLinearInInterpolator	 @interpolator/fast_out_linear_in	(support)	
FastOutSlowInInterpolator	 @interpolator/fast_out_slow_in	(support)	
LinearOutSlowInInterpolator	 @interpolator/linear_out_slow_in	(suppport)
val anim = // Qualquer animação…
anim.setAnimationListener(object : AnimationListener {
override fun onAnimationStart(animation: Animation) {
}
override fun onAnimationRepeat(animation: Animation) {
}
override fun onAnimationEnd(animation: Animation) {
}
})
anim.fillBefore = false
anim.fillAfter = true
anim.repeatCount = 1
anim.repeatMode = Animation.REVERSE // INFINITE or RESTART
myView.startAnimation(anim)
Animando a mudança de Activity
Animando a mudança de Activity
val it = Intent(this, NewActivity::class.java)
startActivity(it)
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim)
override fun onBackPressed() {
super.onBackPressed()
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim)
}
Animação quadro-a-quadro
<animation-list ...
android:oneshot="false">
<item android:drawable=“@drawable/mario_0"
android:duration="100" />
<item android:drawable=“@drawable/mario_1"
android:duration="100" />
<item android:drawable=“@drawable/mario_2"
android:duration="100" />
...
</animation-list>
res/drawable/sprite.xml
Imagem: https://rawgit.com/Grafluxe/sequence-runner/master/sample/index.html
Animação quadro-a-quadro
val sprite = … // ImageView
sprite.setBackgroundResource(R.drawable.sprite)
val spriteAnimation = sprite.background as AnimationDrawable
if (spriteAnimation.isRunning){
spriteAnimation.stop()
} else {
spriteAnimation.start()
}
Imagem: https://rawgit.com/Grafluxe/sequence-runner/master/sample/index.html
Prós e contras…
• Prós
• Simples de usar
• Disponível em todas as versões do Android
• Contras
• Muito limitado
• Não altera a posição real da view 😱
2ª geração de
animações
View Property
Animator
Property Animations
• Disponível a partir do Android 3.0 (v11)
• Simples e fácil de usar
• Você pode animar várias propriedades usando
simplesmente o método animate() da view (v12).
view.animate()
.alpha(0)
.translationX(-view.getHeight())
.translationY(-view.getWidth())
.scaleX(2f)
.scaleY(2f)
.rotation(360)
.setInterpolator(LinearInterpolator())
.withStartAction(Runnable {
// animation's started
})
.withEndAction(Runnable {
// animation's finished
})
.setDuration(1000)
.setStartDelay(1000)
.start()
Property Animations
Animator
AnimatorSet ValueAnimator
ObjectAnimator
Podemos animar qualquer
propriedade utilizando as classes:
val alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0f)
alpha.repeatMode = ValueAnimator.REVERSE
alpha.repeatCount = 1
val animXml = AnimatorInflater.loadAnimator(
this, R.animator.anim_fade)
animXml.setTarget(imgBazinga)
res/animator/anim_fade.xml
<objectAnimator xmlns:android="..."
android:duration="1000"
android:propertyName="alpha"
android:repeatCount="1"
android:repeatMode="reverse"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" />
val scaleX = ObjectAnimator.ofFloat(
view, View.SCALE_X, 2f)
val scaleY = ObjectAnimator.ofFloat(
view, View.SCALE_Y, 2f)
val transl = ObjectAnimator.ofFloat(
view, View.TRANSLATION_X, -view.getWidth())
val rotate = ObjectAnimator.ofFloat(
view, View.ROTATION, 360f)
val alpha = ObjectAnimator.ofFloat(
view, View.ALPHA, 0f)
val animatorSet = AnimatorSet()
animatorSet.play(scaleX)
.with(scaleY)
.with(transl)
animatorSet.play(rotate)
.with(alpha)
.after(transl)
animatorSet.duration = 2000
animatorSet.start()
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(2000);
// Sequentially
animatorSet.playSequentially(
scaleX, scaleY, transl, rotate, alpha);
// or Together
animatorSet.playTogether(
scaleX, scaleY, transl, rotate, alpha);
animatorSet.start();
<set xmlns:android=“…”
android:ordering="together">
<objectAnimator
android:duration="1000"
android:propertyName="scaleX"
android:repeatCount="1"
android:repeatMode="reverse"
android:valueFrom="1"
android:valueTo="2"
android:valueType="floatType" />
<objectAnimator
android:duration="1000"
android:propertyName="scaleY"
android:repeatCount="1"
android:repeatMode="reverse"
android:valueFrom="1"
android:valueTo="2"
android:valueType="floatType" />
</set>
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator) {
super.onAnimationCancel(animation)
}
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
}
override fun onAnimationRepeat(animation: Animator) {
super.onAnimationRepeat(animation)
}
override fun onAnimationStart(animation: Animator) {
super.onAnimationStart(animation)
}
override fun onAnimationPause(animation: Animator) {
super.onAnimationPause(animation)
}
override fun onAnimationResume(animation: Animator) {
super.onAnimationResume(animation)
}
})
Property Animations
<<interface>>

TypeEvaluator<T>
IntEvaluator
ArgbEvaluator
FloatEvaluator
Podemos utilizar os evaluators existentes
para calcular o valores das propriedades:
ObjectAnimator.ofObject(textView,
"textColor",
ArgbEvaluator(),
textView.currentTextColor,
Color.RED).start()
Animate Layout changes
<LinearLayout
...
android:animateLayoutChanges="true" />
API Level 11
3ª geração de
animações
Scenes & Transitions
• Introduzida no Android 4.4. KitKat (API Level 19)
• Captura o estado de cada view da cena e anima
para o estado da próxima cena
val sceneMore = Scene.getSceneForLayout(
sceneRoot, R.layout.scene_more, context)
val sceneLess = Scene.getSceneForLayout(
sceneRoot, R.layout.scene_less, context)
TransitionManager.go(if (more) sceneMore else sceneLess,
AutoTransition())
// reset views and events 😢
Transitions
• From API 19
• Fade
• AutoTransition
• ChangeBounds
• TransitionSet
• From API 21
• Slide
• Explode
• ChangeClipBounds
• ChangeImageTransform
• ChangeTransform
val transitionSet = TransitionSet()
transitionSet.ordering = TransitionSet.ORDERING_SEQUENTIAL
transitionSet.addTransition(Slide(Gravity.RIGHT))
transitionSet.addTransition(ChangeBounds())
TransitionManager.beginDelayedTransition(
constraintLayout, transitionSet)
val visibility = if (fieldsVisible) View.VISIBLE else View.GONE
txtNome.visibility = visibility
edtNome.visibility = visibility
txtIdade.visibility = visibility
edtIdade.visibility = visibility
<transitionSet xmlns:android=“…”
android:transitionOrdering=“sequential">
<slide android:slideEdge="right"/>
<changeBounds />
</transitionSet>
val transition = TransitionInflater.from(this)
.inflateTransition(R.transition.transition_form)
TransitionManager.beginDelayedTransition(
constraintLayout, transition)
animation.addListener(object : Transition.TransitionListener {
override fun onTransitionEnd(transition: Transition?) {
}
override fun onTransitionResume(transition: Transition?) {
}
override fun onTransitionPause(transition: Transition?) {
}
override fun onTransitionCancel(transition: Transition?) {
}
override fun onTransitionStart(transition: Transition?) {
}
})
Custom Transition
class CircleViewProgressTransition(context: Context, attrs: AttributeSet)
: Transition(context, attrs) {
override fun captureStartValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
override fun captureEndValues(transitionValues: TransitionValues) {
captureValues(transitionValues)
}
private fun captureValues(transitionValues: TransitionValues) {
if (transitionValues.view is CircleView) {
val circleView = transitionValues.view as CircleView
transitionValues.values.put(PROPNAME_ANGLE, circleView.getAngle())
}
}
...
companion object {
private val PROPNAME_ANGLE = "myapp.ui.view:CircleView:angle"
}
}
Custom Transition
override fun createAnimator(sceneRoot: ViewGroup, startValues: TransitionValues?,
endValues: TransitionValues?): Animator? {
if (startValues != null && endValues != null && endValues.view is CircleView) {
val circleView = endValues.view as CircleView
val startAngle = startValues.values[PROPNAME_ANGLE] as Float
val endAngle = endValues.values[PROPNAME_ANGLE] as Float
if (startAngle != endAngle) {
circleView.setAngle(startAngle)
val animCircleProgress = ObjectAnimator.ofFloat(
circleView, CircleView.ANGLE, endAngle)
animCircleProgress.setInterpolator(AccelerateDecelerateInterpolator())
animCircleProgress.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
circleView.setAngle(endAngle)
}
})
return animCircleProgress
}
}
return null
}
android.support.transition
• O Google fornece uma biblioteca de suporte para
v14+
• Mas apenas um subconjunto está disponível:
• AutoTransition, ChangeBounds, Fade,
TransitionSet
• Scene e TransitionManager
• TransitionInflater NÃO está disponível.
4ª geração
Activity & Fragment
Transitions
Activity & Fragment Transitions
• Disponível a partir do Android 5 (API 21)
• Maneira melhor de coreografar animações entre
activities/fragments
• Content transitions permitem especificar como as
views saem e entram na tela
• Shared element transition são elementos presentes
em ambas as telas onde podemos fazer uma
transição mais suave
Window Transitions
<style name="MyCustomTheme" parent="AppTheme">
<item name="android:windowExitTransition">@transition/exit</item>
<item name="android:windowEnterTransition">@transition/enter</item>
<item name="android:windowReenterTransition">@transition/reenter</item>
<item name="android:windowReturnTransition">@transition/return</item>
...
</style>
window.exitTransition = transition
window.enterTransition = transition
window.reenterTransition = transition
window.returnTransition = transition
<explode xmlns:android=“…”/>
res/transition-v21/list_exit.xml
<style name="ListDiscosTheme" parent="AppTheme">
<item name="android:windowExitTransition">@transition/list_exit</item>
<item name="android:windowReenterTransition">@transition/list_exit</item>
</style>
res/values-v21/styles.xml
val it = Intent(this, DetailAlbumActivity::class.java)
it.putExtra(DetailAlbumActivity.EXTRA_DISCO, Parcels.wrap(disco))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
startActivity(it, options)
} else {
startActivity(it)
}
val it = Intent(this, DetailAlbumActivity::class.java)
it.putExtra(DetailAlbumActivity.EXTRA_DISCO, Parcels.wrap(disco))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val options = ActivityOptions.makeSceneTransitionAnimation(this).toBundle()
startActivity(it, options)
} else {
startActivity(it)
}
override fun onCreate(savedInstanceState: Bundle?) {
setupWindowAnimations()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_albums)
// ...
}
private fun setupWindowAnimations() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val transition = Explode()
window.exitTransition = transition
window.reenterTransition = transition
}
}
Shared Element Transition
<style name="ListDiscosTheme" parent="AppTheme">
<item name=“android:windowSharedElementExitTransition">
@transition/shd_exit
</item>
<item name=“android:windowSharedElementEnterTransition">
@transition/shd_enter
</item>
<item name=“android:windowSharedElementReenterTransition">
@transition/shd_reenter
</item>
<item name=“android:windowSharedElementReturnTransition">
@transition/shd_return
</item>
...
</style>
window.sharedElementExitTransition = transition
window.sharedElementEnterTransition = transition
window.sharedElementReenterTransition = transition
window.sharedElementReturnTransition = transition
Shared Element Transition
val it = Intent(this, DetailAlbumActivity::class.java)
it.putExtra(DetailAlbumActivity.EXTRA_DISCO, Parcels.wrap(disco))
val options = ActivityOptions.makeSceneTransitionAnimation(
this,
Pair(view.findViewById(R.id.imgCapa), "capa"),
Pair(view.findViewById(R.id.txtTitulo), "titulo"),
Pair(view.findViewById(R.id.txtAno), "ano")
).toBundle()
startActivity(it, options)
<style name="ListDiscosTheme" parent="AppTheme">
...
<item name="android:windowContentTransitions">true</item>
</style>
ViewCompat.setTransitionName(imgCapa, "capa")
ViewCompat.setTransitionName(txtTitulo, "titulo")
ViewCompat.setTransitionName(txtAno, "ano")
res/values-v21/styles.xml
ListAlbumsActivity
DetailAlbumActivity
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
Shared Element Transition em Fragments
ChangeBounds changeBoundsTransition = TransitionInflater.from(this)
.inflateTransition(R.transition.change_bounds);
fragmentB.setSharedElementEnterTransition(changeBoundsTransition);
getFragmentManager().beginTransaction()
.replace(R.id.content, fragmentB)
.addSharedElement(blueView, getString(R.string.blue_name))
.commit();
Reveal animation
val transition = AutoTransition()
transition.duration = resources.getInteger(
android.R.integer.config_mediumAnimTime).toLong()
transition.pathMotion = GravityArcMotion()
val constraintSet = ConstraintSet()
constraintSet.clone(constraintlayout)
TransitionManager.beginDelayedTransition(constraintlayout, transition)
constraintSet.clear(R.id.fabAdd, ConstraintSet.BOTTOM)
constraintSet.clear(R.id.fabAdd, ConstraintSet.RIGHT)
constraintSet.connect(R.id.fabAdd, ConstraintSet.TOP,
R.id.cardViewCity, ConstraintSet.TOP)
constraintSet.connect(R.id.fabAdd, ConstraintSet.LEFT,
R.id.cardViewCity, ConstraintSet.LEFT)
constraintSet.connect(R.id.fabAdd, ConstraintSet.BOTTOM,
R.id.cardViewCity, ConstraintSet.BOTTOM)
constraintSet.connect(R.id.fabAdd, ConstraintSet.RIGHT,
R.id.cardViewCity, ConstraintSet.RIGHT)
constraintSet.applyTo(constraintlayout)
https://github.com/nickbutcher/plaid/blob/master/app/src/main/java/io/plaidapp/ui/transitions/GravityArcMotion.java
val cx = (cardViewCity.left + cardViewCity.right) / 2
val cy = (cardViewCity.top + cardViewCity.bottom) / 2
val anim = ViewAnimationUtils.createCircularReveal(
cardViewCity, cx, cy, fabAdd.width.toFloat(), cardViewCity.width.toFloat())
cardViewCity.visibility = View.VISIBLE
anim.start()
val cx = (cardViewCity.left + cardViewCity.right) / 2
val cy = (cardViewCity.top + cardViewCity.bottom) / 2
val anim = ViewAnimationUtils.createCircularReveal(
cardViewCity, cx, cy, cardViewCity.width.toFloat(), fabAdd.width.toFloat())
anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
cardViewCity.visibility = View.INVISIBLE
}
})
}
anim.start()
<style name="RevealActivityMain" parent="AppTheme">
<item name="android:windowContentTransitions">true</item>
</style>
val options = ActivityOptionsCompat
.makeSceneTransitionAnimation(this, Pair(fabAdd, "circular"))
.toBundle()
startActivity(Intent(this, RevealDetailActivity::class.java), options)
<autoTransition xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_longAnimTime">
<arcMotion
android:maximumAngle="90"
android:minimumHorizontalAngle="15"
android:minimumVerticalAngle="0" />
</autoTransition>
<style name="RevealActivityDetail" parent="AppTheme">
<item name="android:windowContentTransitions">true</item>
<item name=“android:windowSharedElementEnterTransition">
@transition/transition_activity_reveal
</item>
</style>
ViewCompat.setTransitionName(viewPlaceHolder, "circular")
if (isLollipopOrLater() && savedInstanceState == null) {
viewTop.visibility = View.INVISIBLE
window.sharedElementEnterTransition.addListener(
object: TransitionListenerAdapter() {
override fun onTransitionEnd(transition: Transition?) {
super.onTransitionEnd(transition)
circularRevealActivity()
}
})
}
Animated Vector
Drawable
AnimatedVectorDrawable
• Um vector drawable é composto por uma série
padronizada de comandos chamada de path.
• É possível criar um group desses comandos.
• Um animated vector drawable nos permite animar
parte de um vector drawable.
companion object {
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
}
}
android {
...
defaultConfig {
...
vectorDrawables.useSupportLibrary true
}
Path (Comandos)
• M x,y

começa um novo subpath movendo para x,y
• L x,y

desenha uma linha para x,y
• C x1,y1 x2,y2 x,y

desenha uma curva de Bezier cúbica para x,y usando os
pontos de controle x1,y1 e x2,y2
• Z

Fecha um path desenhando uma linha reta de volta ao
ponto de origem do subpath atual
L3,10 L6,8
L6,4
M3,2 Z
M6,4
L6,8
L9,6
L6,4Z
M3,2
L3,10
L5,10
L5,2
Z M7,2
L7,10
L9,10
L9,2
Z
<string name="path_play_1">M 3,2 L 3,10 L 6,8 L 6,4 Z</string>
<string name="path_play_2">M 6,4 L 6,8 L 9,6 L 6,4 Z</string>
res/values/strings.xml
<string name="path_pause_1">M 3,2 L 3,10 L 5,10 L 5,2 Z</string>
<string name="path_pause_2">M 7,2 L 7,10 L 9,10 L 9,2 Z</string>
res/values/strings.xml
drawable
values
drawable-v21
values-v21
ic_vector_play.xml
ic_vector_pause.xml
ic_avd_pause_play.xml
ic_avd_play_pause.xml
refs.xml
refs.xml
Vector Drawables
Animated

Vector Drawables
Referência de acordo
com a versão
<vector xmlns:android="..."
android:width="48dp"
android:height="48dp"
android:viewportHeight="12"
android:viewportWidth="12">
<group
android:name="triangle"
android:pivotX="6"
android:pivotY="6">
<path
android:name="triangle_1"
android:fillColor="#FFFFFF"
android:pathData="@string/path_play_1" />
<path
android:name="triangle_2"
android:fillColor="#FFFFFF"
android:pathData="@string/path_play_2" />
</group>
</vector>
ic_vector_play.xml
<vector xmlns:android="..."
android:width="48dp"
android:height="48dp"
android:viewportHeight="12"
android:viewportWidth="12">
<path
android:name="bar_1"
android:fillColor="#FFFFFF"
android:pathData="@string/path_pause_1" />
<path
android:name="bar_2"
android:fillColor="#FFFFFF"
android:pathData="@string/path_pause_2" />
</vector>
ic_vector_pause.xml
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_vector_play">
<target android:name="triangle">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="180"
android:valueType="floatType" />
</aapt:attr>
</target>
<target android:name="triangle_1">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:propertyName="pathData"
android:valueFrom="@string/path_play_1"
android:valueTo="@string/path_pause_1"
android:valueType="pathType" />
</aapt:attr>
</target>
<target android:name="triangle_2">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:propertyName="pathData"
android:valueFrom="@string/path_play_2"
android:valueTo="@string/path_pause_2"
android:valueType="pathType" />
</aapt:attr>
</target>
</animated-vector>
ic_avd_play_pause.xml
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_vector_pause">
<target android:name="bar_1">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:propertyName="pathData"
android:valueFrom="@string/path_pause_1"
android:valueTo="@string/path_play_1"
android:valueType="pathType" />
</aapt:attr>
</target>
<target android:name="bar_2">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="300"
android:propertyName="pathData"
android:valueFrom="@string/path_pause_2"
android:valueTo="@string/path_play_2"
android:valueType="pathType" />
</aapt:attr>
</target>
</animated-vector>
ic_avd_pause_play.xml
<resources>
<item name="ic_play" type="drawable">@drawable/ic_vector_play</item>
<item name="ic_pause" type="drawable">@drawable/ic_vector_pause</item>
</resources>
<resources>
<item name="ic_play" type="drawable">@drawable/ic_avd_play_pause</item>
<item name="ic_pause" type=“drawable">@drawable/ic_avd_pause_play</item>
</resources>
<ImageView
...
app:srcCompat="@drawable/ic_play" />
values
values-v21
refs.xml
refs.xml
fun getIconDrawable(): Drawable? {
val drawableId: Int
if (isPlaying) {
drawableId = R.drawable.ic_play
} else {
drawableId = R.drawable.ic_pause
}
return ResourcesCompat.getDrawable(resources, drawableId, theme)
}
private fun togglePlayPause() {
isPlaying = !isPlaying
imageAnimatedVector.setImageDrawable(getIconDrawable())
animateButton(imageAnimatedVector.drawable)
}
private fun animateButton(icon: Drawable?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
icon is AnimatedVectorDrawable) {
icon.start()
}
}
https://romannurik.github.io/AndroidIconAnimator/
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="56"
android:viewportWidth="56">
<path
android:pathData="@string/heart_stroke_left"
android:strokeColor="@android:color/white"
android:strokeWidth="2"/>
<path
android:pathData="@string/heart_stroke_right"
android:strokeColor="@android:color/white"
android:strokeWidth="2"/>
</vector>
res/drawable/ic_vector_heart_empty.xml
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="56"
android:viewportWidth="56">
<!-- mesmos paths do fill -->
<group android:name="filled">
<clip-path
android:name="clip"
android:pathData="@string/heart_clip_shown"/>
<path
android:fillColor="@android:color/white"
android:pathData="@string/heart_full_path"/>
</group>
</vector>
res/drawable/ic_vector_heart_full.xml
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_vector_heart_full">
<target android:name="clip">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="@android:integer/config_mediumAnimTime"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
android:valueFrom="@string/heart_clip_shown"
android:valueTo="@string/heart_clip_hidden"
android:valueType="pathType"/>
</aapt:attr>
</target>
</animated-vector>
res/drawable-v21/ic_avd_heart_full_empty.xml
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:drawable="@drawable/ic_vector_heart_full">
<target android:name="clip">
<aapt:attr name="android:animation">
<objectAnimator
android:duration="@android:integer/config_mediumAnimTime"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:propertyName="pathData"
android:valueFrom="@string/heart_clip_hidden"
android:valueTo="@string/heart_clip_shown"
android:valueType="pathType"/>
</aapt:attr>
</target>
</animated-vector>
res/drawable-v21/ic_avd_heart_empty_full.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/liked"
android:drawable="@drawable/ic_vector_heart_full"
android:state_checked="true"/>
<item
android:id="@+id/not_liked"
android:drawable="@drawable/ic_vector_heart_empty"/>
</selector>
drawable/selector_heart.xml
<animated-selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/liked"
android:drawable="@drawable/ic_vector_heart_full"
android:state_checked="true" />
<item
android:id="@+id/not_liked"
android:drawable="@drawable/ic_vector_heart_empty" />
<transition
android:drawable="@drawable/ic_avd_heart_empty_full"
android:fromId="@id/not_liked"
android:toId="@id/liked" />
<transition
android:drawable="@drawable/ic_avd_heart_full_empty"
android:fromId="@id/liked"
android:toId="@id/not_liked" />
</animated-selector>
drawable-v21/selector_heart.xml
Physics-based
animation
https://developer.android.com/guide/topics/graphics/spring-animation.html
https://gist.github.com/nickbutcher/7fdce476aaa589680cdd626d78e3149d
E aquela animação, que
gira, pula, solta fogos…🤔
https://github.com/airbnb/lottie-android
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lottie_fileName="assetSubDir/spring.json"
app:lottie_loop="true"
app:lottie_autoPlay="true" />
LottieAnimationView animationView = (LottieAnimationView)
findViewById(R.id.animation_view);
animationView.setAnimation("assetSubDir/spring.json");
animationView.loop(true);
animationView.playAnimation();
dependencies {
compile 'com.airbnb.android:lottie:2.0.0-beta4'
}
Algumas libs…
• https://github.com/andkulikov/Transitions-Everywhere
• https://github.com/willowtreeapps/spruce-android
• https://github.com/glomadrian/Grav
• https://github.com/daimajia/AndroidViewAnimations
• https://github.com/2359media/
EasyAndroidAnimations
• http://facebook.github.io/rebound/
Referências
• Material Design para Desenvolvedores Android

https://br.udacity.com/course/material-design-for-android-developers--ud862/
• Andre Mion Animations

https://stories.uplabs.com/music-player-3a85864d6df7

https://blog.prototypr.io/applying-meaningful-motion-on-android-
a271a873bd78
• Plaid (Nick Butcher)

https://github.com/nickbutcher/plaid
• Animatable (Nick Butcher)

https://goo.gl/99Y9qD
Referências
• Android transitions with examples

https://github.com/lgvalle/Material-Animations
• An Introduction to Icon Animation Techniques

http://www.androiddesignpatterns.com/2016/11/introduction-to-icon-
animation-techniques.html
• Animate all the things. Transitions in Android

https://medium.com/@andkulikov/animate-all-the-things-transitions-in-
android-914af5477d50
• Animate me, If you don't do it for me do it for Chet :)

https://pt.slideshare.net/Android2EE/animate-me-if-you-dont-do-it-for-me-do-
it-for-chet
Dúvidas?
@nglauber
+NelsonGlauber
www.nglauber.com.br

The world of Android Animations