Android Onboarding Hop Animation
--
You have a fantastic feature in your application that requires your users to swipe a view up to open it. But your users do NOT use it. WHY?! Of course, because they are not aware of that awesome feature. Let’s help them discover that feature.
Hop Your Feature
It is so intuitive since users need to swipe it to use it. Hop it! Let people know that the view has more things to show.
Maybe it is because of our evolutionary actions. We react to moving things around us. 🤔 Probably that’s why the Android team designed the default android launcher with that animation.
Lazy Option: Use the new Hopper library.
Let’s Try
We will start trying to ‘hop’ a simple TextView just to see how easy the animation implementation is. It is applicable to other views as well. So it is enough to know how to ‘hop’ a TextView.
One Hop
val animation = textViewHop
.animate()
.translationYBy(-40f)
.setDuration(200)
animation.withEndAction {
textViewHop.animate().translationYBy(40f).duration = 200
}
Thanks to the lovely <withEndAction> method we are able to animate the view in the opposite direction of the first animation. If you try to do it with AnimatorListener your second animation will also trigger itself and the view you are trying to animate will animate out of the screen. It is fun to watch.
How to loop this?
Now we have the basic animation. How are we going to repeat itself? Handler FTW.
Java version;
animationHandler.post(new Runnable(){
@Override
public void run(){
//Hop animation
animationHandler.postDelayed(this, 1000);
}
});
‘this’ in the ‘postDelayed’ method refers to the Runnable object in java.
Kotlin simple version;
animationHandler.post(object : Runnable {
override fun run() {
// Hop animation
animationHandler.postDelayed(this, 1000)
}
})
Kotlin slightly more complicated version;
private val animationHandler = Handler()
private lateinit var animationRunnable: Runnable
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
animationRunnable = Runnable { animationFunction() }
animationHandler.post(animationRunnable)
}private fun animationFunction() {
val animation = textViewHop
.animate()
.translationYBy(-40f)
.setDuration(200)
animation.withEndAction {
textViewHop.animate().translationYBy(40f).duration = 200
}
animationHandler.postDelayed(animationRunnable, 1000)
}
Basically, what we did is getting the runnable object globally. You can also pass it as a parameter to the ‘animationFunction’ method. It will work as long as you give the runnable object to the handler to run with a delay in the runnable itself. (I hope I didn't make it complicated.)
Edit: All the code that you need is in the bottomsheetonboarding project. If you want a quicker answer, here it comes;
Kotlin version:
animationHandler.post(object : Runnable {
override fun run() {
val animation = linearLayoutBottomSheet
.animate()
.translationYBy(-40f)
.setDuration(200)
animation.withEndAction {
linearLayoutBottomSheet.animate().translationYBy(40f).duration = 200
}
animationHandler.postDelayed(this, 1000)
}
})
Java version:
animationHandler.post(new Runnable() {
@Override
public void run() {
ViewPropertyAnimator viewPropertyAnimator =
linearLayoutBottomSheet.animate().translationYBy(-40f).setDuration(200);
viewPropertyAnimator.withEndAction(new Runnable() {
@Override
public void run() {
linearLayoutBottomSheet.animate().translationYBy(40f).setDuration(200);
}
});
animationHandler.postDelayed(this, 1000);
}
});
The result:
Our result gif shows a bottom sheet animation. It is done with the same approach.
Check out the sample project on the GitHub. Do not forget to leave a star to the project if you were able to learn something. https://github.com/EfeBudak/bottomsheetonboarding
Note: Thanks Tulio for pointing out that it is possible to refer to the anonym object in Kotlin as ‘this’, too.
If you have any questions, suggestions, or objections then leave a comment.