Animations & Transitions
A well-placed animation tells the user something happened without them having to think about it — a card that fades in feels like it arrived gently rather than snapping into existence, a photo that glides from one screen to the next feels like the same object rather than two unrelated images. Android gives you several layers of animation API, from a one-line convenience call all the way up to fully custom motion. This lesson walks through each, using TrailLog's trail detail screen and photo list as the running example.
ViewPropertyAnimator: The Simplest Way to Animate a View
For animating a View's own built-in properties — alpha, position, scale, rotation —
ViewPropertyAnimator, reached through view.animate(), is the simplest
and most efficient option. It batches multiple property changes into a single underlying
animation pass. Here's TrailLog fading in a trail's detail card once its data has finished
loading:
Fading in the trail detail card
fun showTrailDetailCard() {
detailCard.alpha = 0f
detailCard.visibility = View.VISIBLE
detailCard.animate()
.alpha(1f)
.setDuration(300)
.start()
}
The card starts fully transparent, is made visible, and then animate().alpha(1f)
smoothly raises its opacity to fully opaque over 300 milliseconds. You can chain
.translationY(), .scaleX()/.scaleY(), or .rotation() onto
the same call to combine several effects into one animation.
ObjectAnimator and ValueAnimator: More Control
ViewPropertyAnimator only covers a View's own properties. When you need to animate
something else entirely — a custom property on your own class, a color, a value you'll use to
drive a manual invalidate() redraw — reach for ObjectAnimator (animates
a named property on any object with the right getter/setter) or the lower-level
ValueAnimator (just produces a stream of interpolated values you apply yourself):
Animating the difficulty badge's background color
val colorAnimator = ObjectAnimator.ofArgb(
difficultyBadge,
"badgeColor",
Color.parseColor("#9E9E9E"),
Color.parseColor("#4CAF50")
)
colorAnimator.duration = 400
colorAnimator.start()
This targets the badgeColor property on the custom TrailDifficultyBadge
View from the previous lesson, interpolating between two ARGB colors and requiring the View to
expose a matching setter that calls invalidate() to redraw on each step.
XML-Defined Animations in res/anim/
Simple, reusable animations can also live declaratively as XML resources under
res/anim/, which keeps the animation's shape out of your Kotlin code entirely:
res/anim/fade_in.xml
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration=300 />
Loading and starting it from Kotlin
val fadeIn = AnimationUtils.loadAnimation(context, R.anim.fade_in)
detailCard.startAnimation(fadeIn)
This produces the same visual result as the ViewPropertyAnimator example above —
which approach to reach for is mostly a matter of taste, though XML animations are handy when
designers hand you a spec that maps cleanly onto a resource file, or when the same animation is
reused across many screens.
Shared Element Transitions: Carrying a View Between Screens
One of Android's most delightful, commonly-seen UI patterns is the shared element
transition: tap a trail's thumbnail in a list, and its photo appears to smoothly grow
into place as the detail screen's hero image, rather than the list simply vanishing and a new
screen popping in. Both screens declare the same transitionName on the shared View,
and the navigation call tells the system to animate between them:
Marking the shared photo in both layouts
<ImageView
android:id="@+id/trailPhoto"
android:transitionName="trail_photo_transition"
android:layout_width="match_parent"
android:layout_height="200dp" />
Launching the detail screen with the shared element
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
this, trailPhotoView, "trail_photo_transition"
)
startActivity(intent, options.toBundle())
The Navigation Component wraps this same idea for Fragment-to-Fragment transitions — see Navigation Component for how shared elements plug into that setup specifically.
MotionLayout: For Complex, Multi-State Animated Layouts
ConstraintLayout's more powerful sibling, MotionLayout, is built for
layouts that need to animate between several distinct states — think a collapsing header that
also fades its title, resizes its image, and moves a floating action button, all in one
coordinated motion driven by scroll position or a tap. It's a genuinely deep topic on its own with
its own XML scene format; this lesson only asks you to know it exists and what kind of problem it
solves, not to use it yet.
A Word Against Over-Animating
Every animation on this page has a job: the fade tells the user content has finished loading, the shared element transition confirms which photo they tapped. An animation that exists purely for decoration — a long bounce on every button press, a spinning icon nobody asked for — adds latency to the very interaction it's supposed to make feel nice. Keep durations short (roughly 150–300ms for most UI feedback), give the user a way to skip or interrupt long animations, and cut anything that doesn't clarify a transition or add real polish.