Android Animations
Link to project if you want to follow along. Or if you want the end code click here
I recently had a project that I needed to have some nice animating floating action buttons and I decided to tackle this without using third party libraries due to the client’s hesitation about using most third party libraries.
I have gone ahead and provided a base version of the code (prior to the addition of the animation code) if you want to follow along. So here is what we are working with.
As you can see we have a very basic Extended Floating Action Button in the corner and that is about it. What we want to do is when this button is clicked we want the sub buttons to appear above the button while the original button is shrinking to only show the icon.
The first thing we are going to do is define a class to handle custom animations for shrinking and expanding our Extended Fab Button. What this allows us to do is rotate the icon at the end of the animation so the + turns into a x as well aa call a function at the end of the OnShrunken and OnExtended actions.
class ExtendedFabOnChange(
private val animationTime: Long,
private val callbackFunction: () -> Unit
) : ExtendedFloatingActionButton.OnChangedCallback() {
override fun onShrunken(extendedFab: ExtendedFloatingActionButton?) {
super.onShrunken(extendedFab)
// Rotate the icon to become an X
extendedFab?.animate()
?.setDuration(animationTime)
?.rotation(45f)
?.withEndAction{
callbackFunction()
}
}
override fun onExtended(extendedFab: ExtendedFloatingActionButton?) {
super.onExtended(extendedFab)
callbackFunction()
}
}
We are also now going to define our callback function in the MainActivity.kt. All this function will do is set animating to false to signify that all of our animations have completed.
private fun finishAnimating(){
animating = false
}
}
And with all of those defined we are going to update our onCreate to create a local variable to hold the instance of our custom class ExtendedFabOnChange and instantiate it in the onCreate method.
private lateinit var fabOnChange: ExtendedFabOnChange
override fun onCreate(savedInstanceState: Bundle?) {
...
fabOnChange = ExtendedFabOnChange(animationTime, ::finishAnimating)
}
}
The next thing we need to do is code up our movement function. For this example we are going to simply move the buttons up or down and hide them if they are no longer needed.
private fun moveView(view: View, movement: Float, hideAtEndOfAnimation: Boolean) {
view.apply {
// Set the alpha to 0 if we are transitioning to nothing and vice versa
alpha = if (hideAtEndOfAnimation) 1f else 0f
visibility = View.VISIBLE
animate()
.alpha(if (hideAtEndOfAnimation) 0f else 1f)
.translationYBy(movement)
.setListener(null)
.setDuration(animationTime)
.withEndAction {
if (hideAtEndOfAnimation) {
visibility = View.GONE
}
}
}
}
With that out of the way we need to now set it up for the when we want the buttons to appear and disappear. For simplicity’s sake we are going to have two methods one for showing and one for hiding.
private fun displayFabs() {
if (!animating) {
animating = true
binding.extendedFab.shrink(fabOnChange);
moveView(
binding.fab1Label,
-resources.getDimensionPixelSize(R.dimen.button1_move).toFloat(),
false
)
moveView(
binding.fab2Label,
-resources.getDimensionPixelSize(R.dimen.button2_move).toFloat(),
false
)
moveView(
binding.fab1,
-resources.getDimensionPixelSize(R.dimen.button1_move).toFloat(),
false
)
moveView(
binding.fab2,
-resources.getDimensionPixelSize(R.dimen.button2_move).toFloat(),
false
)
}
}
private fun hideFabs() {
if (!animating) {
animating = true
binding.extendedFab.apply {
// Unrotate the X and then expand the button
animate()
.setDuration(animationTime)
.rotation(0f)
.withEndAction{
binding.extendedFab.extend(fabOnChange)
}
}
moveView(
binding.fab1Label,
resources.getDimensionPixelSize(R.dimen.button1_move).toFloat(),
true
)
moveView(
binding.fab2Label,
resources.getDimensionPixelSize(R.dimen.button2_move).toFloat(),
true
)
moveView(
binding.fab1,
resources.getDimensionPixelSize(R.dimen.button1_move).toFloat(),
true
)
moveView(
binding.fab2,
resources.getDimensionPixelSize(R.dimen.button2_move).toFloat(),
true
)
}
}
If you look above we are defining yet another animation to spin back our extended button prior to expanding it. If we dont do this the button will open vertically which as funny as it is does not look very professional. We are also using the animating property to ensure that we dont chain together multiple instances of the same animation and cause the buttons to move sporatically.
With all that done we just need to hook up the onClick handler and we are all good to go.
private lateinit var fabOnChange: ExtendedFabOnChange
override fun onCreate(savedInstanceState: Bundle?) {
...
binding.extendedFab.setOnClickListener{
if(binding.fab1.visibility == View.VISIBLE) {
hideFabs()
} else {
displayFabs()
}
}
}
Now that we are code complete lets look at what the end product looks like.