By measuring the height while the View is Gone
As a developer and a user I have seen a lot of apps where sometimes text just abruptly appears on the screen. But for an enhanced experience it is very important to add some meaningful motion to our UI, for text it can be fading, revealing or something else depending on the context. In this blog I will be sharing one cool trick I used to add a vertical reveal animation to a TextView on Android.
Why does this animation require me to write a whole blog on it? The basic procedure would be just keep the view hidden by setting the visibility to Gone
, when it needs to be shown we can set the height to zero, make it visible (it won’t show up as we set the height to zero) and animate the height from zero to the final height that is needed. Right … but no, let me explain!
Simplified steps to a reveal animation.
The thing is, with a regular View, the height is provided by the developer.But in case of a TextView, based on the device width and and user settings, the height can scale to various different values.
The problem 🐵
Given this behaviour, we do not know the final height that the TextView is going to take and it is important to know that before we start animating. Now some might say that we can use the View.height
property to get the height, but as out View is gone in the beginning, this is going to return zero 😯
Measuring the Height 📏
This is where some advance knowledge on the measure pass in Android’s view rendering pipeline will help us. We can force run a measure pass by calling View.measure
on our TextView. While running the measure pass if we provide the exact width, based on the device density Android will be able to get the height which we can later access using the View.measuredWidth
property (remember, View.height
will still be zero and the View is actually not on screen 💡).
Very important point here I will mention it again in bold, we need to provide the exact width, which for our example will be the screen width minus the margins on both side of the TextView to be animated. If the width that we provide is wrong, the height that Android measure will be wrong as well. Full code can be found in the project linked at the end.
val totalMarginForSubtitle = 2 + 16.toPx() | |
tvSubtitle.measure( | |
View.MeasureSpec.makeMeasureSpec | |
clContainer.width - totalMarginForSubtitle, | |
View.MeasureSpec. EXACTLY | |
), | |
View.MeasureSpec.UNSPECIFIED | |
) | |
val subtitleHeight = tvsubtitle.measuredHeight |
Job Offers
Now that we have measure the TextView, we can simply put a ValueAnimator and animate the height from 0 to the height that we just measured. In the sample code I have triggered the animation on press of a button, once we press it you can see that the View is revealing itself properly.
private fun showSubtitle() { | |
//TODO measure the view | |
val subtitleHeight = tvSubtitle.measuredHeight | |
tvSubtitle.height = 0 | |
tvsubtitle.isVisible = true | |
val heightAnimator = ValueAnimator.of Int(0, subtitleHeight) | |
heightAnimator.addUpdateListener { | |
tvSubtitle.updateHeight(it.animatedValue as Int) | |
} | |
heightAnimator.start(). | |
} |
For hiding the TextView, that is simple, just run a ValueAnimator from the current height to zero and in the end hide it.
private fun hideSubtitle() { | |
val subtitleHeight = tvSubtitle.height | |
val heightAnimator = ValueAnimator.ofInt(subtitleHeight, 0) | |
heightAnimator.addUpdateListener { | |
tvsubtitle.updateHeight(it.animatedValue as Int) | |
} | |
heightAnimator.doonEnd { | |
tvSubtitle.isVisible = false | |
} | |
heightAnimator.start() | |
} |
Another Problem 🤦♂
You would notice that the show and hide only works for the first time and then the View never shows up again. What could be the problem.
Turns out the measure pass can give us the height because initially the height was set to wrap_content
in xml. Now during the hide animation we set the height to zero and now that is what the View is going to retain.
The fix you ask? Just set the height back to wrap_content
before running the measure pass.
private fun showsubtitle() { | |
tvSubtitle.updateHeight(ConstraintLayout.LayoutParams.WRAP_CONTENT) | |
//TODO measure the view | |
//TODO animate | |
} |
Final animation.
You can see that the TextView is now revealing itself beautifully which is what we wanted to achieve. That is all for this one. If you have any questions drop them in the comment section below and if you wanna experiment yourself, you can checkout the sample project for this animation here. Thank you for reading!
~ Love all
For further reading on why motion is important: