Bottom navigation is a cornerstone of modern Android app design, offering users quick access to an app’s primary destinations. While functional, a static navigation bar can be uninspired. By harnessing the power of Animated Vector Drawables (AVDs), you can create fluid, engaging, and visually appealing transitions that elevate the user experience.
This guide will walk you through the fundamentals of AVDs and demonstrate how to implement a fully animated bottom navigation bar in both traditional XML layouts and the modern Jetpack Compose UI toolkit. Since there is no single implementation for animated bottom navigation, I’ll also share some of the different approaches you can take throughout this article.
Understanding Animated Vector Drawables (AVDs)
Before we dive into Bottom navigation itself, First we need to under start Animated Vector Drawables (AVD). Animated Vector Drawables are scalable vector graphics that can be animated. Defined in XML, they are lightweight and render crisply on any screen density. An AVD is composed of three key parts:
1. Vector Drawable: The static image defined by paths and groups in an XML file.
2. Animator: An XML file that defines how a property (like rotation, path data, or color) changes over time.
3. Animated Vector Drawable: The bridge that links a specific part of the vector drawable to an animator.
Here’s a simple example:
1. `ic_star.xml` (The vector drawable)
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:name="star_path" android:fillColor="#FF000000" android:pathData="M12,2L15.09,8.26L22,9.27L17,14.14L18.18,21.02L12,17.77L5.82,21.02L7,14.14L2,9.27L8.91,8.26L12,2Z" /> </vector>
2. `rotate_animator.xml` (The animator)
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="rotation" android:duration="1000" android:valueFrom="0" android:valueTo="360" />
3. `avd_star_rotate.xml` (The animated-vector drawable)
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/ic_star"> <target android:name="star_path" android:animation="@animator/rotate_animator" /> </animated-vector>
To trigger the animation in an XML layout, you would get a reference to the drawable and call its `start()` method:
val icon = findViewById<ImageView>(R.id.star_icon)
val drawable = icon.drawable
if (drawable is Animatable) {
drawable.start()
}


AVD usage in XML and Compose
Animated Navigation in XML
Implementing AVDs in a standard `BottomNavigationView` is not straightforward. The view is not designed to manage the animation lifecycle of its icons, so simply assigning an AVD as an icon will not work. Below are two approaches: a fragile workaround to be avoided, and a more robust custom solution.
A Fragile Workaround (Not Recommended)
One common but ill-advised technique involves accessing the internal `ImageView` of a `BottomNavigationItemView` and animating it directly.
Warning: This code relies on internal implementation details of the Material Design library
com.google.android.material.R.id.navigation_bar_item_icon_view
These details are not public API and can change in any library update, breaking your code without warning. Do not use this in production.
fun animateNavigationItem(view: View?) {
view?.let {
// Find the icon ImageView within the menu item
val icon = when (it) {
is BottomNavigationItemView -> it.findViewById<ImageView>(com.google.android.material.R.id.navigation_bar_item_icon_view)
else -> null
}
// Rotate animation for the icon
icon?.animate()
?.rotation(360f)
?.setDuration(300)
?.withEndAction {
// Reset rotation for next time
icon.rotation = 0f
}
?.start()
// Scale animation for the container
it.animate()
.scaleX(1.2f)
.scaleY(1.2f)
.setDuration(150)
.withEndAction {
// Scale down animation
it.animate()
.scaleX(1.0f)
.scaleY(1.0f)
.setDuration(150)
.start()
}
.start()
}
}

This approach is brittle and bypasses the intended design patterns of the library, potentially causing unforeseen issues.
The Robust Solution: A Custom View
A more reliable and production-ready solution is to create a custom `BottomNavigationView` that properly manages the AVD lifecycle. By subclassing `BottomNavigationView`, you gain full control over item selection and icon state changes.
Here is a look at the core logic of a custom implementation:
class CustomBottomNavigationView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : BottomNavigationView(context, attrs, defStyleAttr) {
private var menuItemAnimations = mutableMapOf<Int, Int>()
init {
setOnItemSelectedListener { item ->
// Animate the selected menu item
animateMenuItem(item.itemId)
true
}
// Optional: Animate on reselection as well
setOnItemReselectedListener { item ->
animateMenuItem(item.itemId)
}
}
// Register an AVD resource for a specific menu item ID
fun registerAnimatedDrawable(menuItemId: Int, avdResId: Int) {
menuItemAnimations[menuItemId] = avdResId
}
private fun animateMenuItem(itemId: Int) {
val avdResId = menuItemAnimations[itemId] ?: return
// Find the item view and its icon
val itemView = findViewById<BottomNavigationItemView>(itemId)
val iconView = itemView.findViewById<ImageView>(
com.google.android.material.R.id.navigation_bar_item_icon_view
)
// Set the AVD and start the animation
val avd = ContextCompat.getDrawable(context, avdResId)
iconView.setImageDrawable(avd)
if (avd is Animatable) {
avd.start()
}
}
}
How to Use It:
1. Add to Layout: Replace `BottomNavigationView` with your custom view in your XML layout file.
<com.example.animatedvectordrawable.CustomBottomNavigationView android:id="@+id/custom_bottom_nav" android:layout_width="match_parent" android:layout_height="wrap_content" app:menu="@menu/bottom_nav_menu" />
2. Register AVDs: In your Activity or Fragment, register the AVDs for each menu item.
val bottomNav = findViewById<CustomBottomNavigationView>(R.id.custom_bottom_nav) bottomNav.registerAnimatedDrawable(R.id.nav_home, R.drawable.avd_home) bottomNav.registerAnimatedDrawable(R.id.nav_explore, R.drawable.avd_explore) // ... and so on for other items
This approach is encapsulated, reusable, and correctly handles the animation lifecycle.

Animated Navigation in Jetpack Compose
Jetpack Compose radically simplifies working with AVDs. Its state-driven nature makes it easy to tie animations directly to UI state changes. The `AnimatedImageVector` and `rememberAnimatedVectorPainter` APIs provide everything you need.
Here is a complete example of an animated navigation bar in Compose:
@OptIn(ExperimentalAnimationGraphicsApi::class)
@Composable
fun AnimatedNavigationBar(
modifier: Modifier = Modifier,
onDestinationSelected: (NavDestinations) -> Unit = {}
) {
var selectedTabIndex by rememberSaveable { mutableIntStateOf(0) }
NavigationBar(modifier = modifier) {
NavDestinations.destinations.forEachIndexed { index, destination ->
val image = AnimatedImageVector.animatedVectorResource(destination.icon)
// The 'atEnd' boolean drives the animation state.
// It's true if the item is selected, false otherwise.
val atEnd = selectedTabIndex == index
NavigationBarItem(
selected = atEnd,
onClick = {
selectedTabIndex = index
onDestinationSelected(destination)
},
icon = {
Icon(
painter = rememberAnimatedVectorPainter(
animatedImageVector = image,
atEnd = atEnd
),
contentDescription = destination.label
)
},
label = { Text(destination.label) }
)
}
}
}
This implementation is clean, declarative, and robust. State changes (`selectedTabIndex`) automatically trigger recomposition, and the painter seamlessly handles the animation between the start and end states of the AVD.
The final step is to create the AVDs themselves.

Job Offers
Conclusion
Animating your bottom navigation icons is a subtle but powerful way to add polish and delight to your app. While the XML approach requires a custom view for a robust implementation, Jetpack Compose offers a more elegant and straightforward solution out of the box.
By understanding the principles of AVDs and choosing the right implementation strategy, you can create beautiful, responsive, and memorable navigation experiences for your users.
Love you all.
Stay tune for upcoming blogs.
Take care.
This article was previously published on proandroiddev.com



