Circular Reveal Effect like WhatsApp in Android
While sending a photo through WhatsApp, I noticed the ‘Attach’ button performs a neat Circular Reveal effect. My phone being JellyBean, I was surprised to see a Lollipop only transition. Thanks to a neat library, we can mimic the exact same thing for pre Lollipop too.
The Reveal Effect
Touch feedback in material design provides an instantaneous visual confirmation at the point of contact when users interact with UI elements.
The Reveal Effect uses the RippleDrawable class to achieve this. So visually, you can tell that it is similar to the Ripple effect. The only difference being that a Ripple is a touch feedback denoted by a color spreading from a point of contact, while the Reveal effect does the same animation, but for displaying a view.
WhatsApp’s Version
Here’s how it looks on WhatsApp, after their Material Design upgrade.
This is exactly what we’re going to create. If you still don’t get what I’m talking about then scroll down to the end of the post.
Setup
The issue is, this animation as said before is intended for Lollipop and above ONLY. There is no support library to help us out here. So no version below Lollipop can perform this.
However, there is a neat library that’s managed to backport this for us. We will be using the same, for our purpose. The good thing about the library is that, the methods are exactly the same, which is convenient. Though one must be careful about the imports.
Circular Reveal Library
We’ll start by including the library in our build.gradle file. Add the repository first, and then the dependency.
repositories { maven { url "https://jitpack.io" } } dependencies { compile 'com.github.ozodrukh:CircularReveal:1.1.0' }
Layout
Let’s create a layout similar to that of WhatsApp’s Attach option.
Here’s the layout skeleton if you’re interested. Honestly this layout could be any layout that you want to reveal using this effect, even entire screen transitions could be done.
<io.codetail.widget.RevealFrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <LinearLayout android:id="@+id/reveal_items" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:gravity="center" android:orientation="vertical"> <ImageButton android:layout_width="70dp" android:layout_height="70dp" android:background="@drawable/icon_camera" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="Gallery" /> </LinearLayout> <!-- Other 2 icons here--> </LinearLayout> </io.codetail.widget.RevealFrameLayout>
Note the RevealFrameLayout widget. This comes from the library we imported. This is nothing but a FrameLayout. To perform the animation, we will reference the immediate child layout of this frame. While that child (here being the LinearLayout) with id reveal_items will animate, it’s shape will be clipped by the outer RevealFrameLayout.
Performing the Reveal Effect
Head over to your Activity.java and lets get that animation working. The animation will be toggled when we tap the Attach (clip) button. The contents of the RevealFrameLayout will be displayed, and hidden alternatively.
Remember I said we need to reference the immediate child of the RevealFrameLayout.
LinearLayout mRevealView = (LinearLayout) findViewById(R.id.reveal_items); mRevealView.setVisibility(View.INVISIBLE);
The view will be inivisible as by default it remains hidden, only presenting itself when we tap Attach.
Our Attach button will be in our Toolbar, included using a menu resource.
... <item android:id="@+id/action_clip" android:icon="@drawable/ic_action_editor_attach_file" android:title="Media" app:showAsAction="ifRoom" /> ...
The meat of the action takes place in the onOptionsItemSelected() method. Here is where the Toolbar (ActionBar) menu item’s click actions are defined.
public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.action_clip: // handle animation here return true; } return super.onOptionsItemSelected(item); }
Animation Prerequisites
To perform the animation, two parameters are needed:
- X, Y coordinates to start the animation from
- Radius of the circular reveal
WhatsApp performs the Reveal effect from the top right.
int cx = (mRevealView.getLeft() + mRevealView.getRight()); int cy = mRevealView.getTop();
If you need it from the center, then just change cy to this instead: int cy = (mRevealView.getTop() + mRevealView.getBottom())/2;
The radius can be obtained using this method:
int radius = Math.max(mRevealView.getWidth(), mRevealView.getHeight());
ViewAnimationUtils
Now we can finally initiate the reveal effect.
SupportAnimator animator = ViewAnimationUtils.createCircularReveal(mRevealView, cx, cy, 0, radius); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.setDuration(400); SupportAnimator animator_reverse = animator.reverse();
NOTE: Be very careful of the imports here. ViewAnimationUtils is imported from from the external library, and NOT the default android.view.ViewAnimationUtils import.
animator will be used for revealing the view on tap, while animator_reverse in essense reverses the animation for hiding back the view when tapped again.
Animation Toggle
We will use a simple boolean hidden to handle the toggling.
if (hidden) { mRevealView.setVisibility(View.VISIBLE); animator.start(); hidden = false; } else { animator_reverse.addListener(new SupportAnimator.AnimatorListener() { @Override public void onAnimationEnd() { mRevealView.setVisibility(View.INVISIBLE); hidden = true; } ... }); animator_reverse.start(); }
Note that we need to make the view visible before we being the reveal effect. While this simply works, its not the same for the reverse effect. The library’s SupportAnimator provides a method reverse() to reverse the animation. We cannot just set the view invisible before starting the animation. The animation performed will never be visible.
For this we will use the AnimatorListener. Once the reverse animation ends, the convenient onAnimationEnd() method will allow us to hide the view.
Be sure to update the boolean hidden accordingly in the if and else cases.
Lollipop Fix
Strangely, this doesn’t seem to work on Lollipop 5.0.1 and above. As in, the transition itself doesn’t work. A simple workaround for this would be to use Android’s native ViewAnimationUtils for devices Lollipop and above.
Simply wrap the code you just wrote like this.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // Your previously written code } else { // Same code here but import ViewAnimationUtils from android.view.ViewAnimationUtils Animator anim = android.view.ViewAnimationUtils.createCircularReveal(mRevealView, cx, cy, 0, radius); ... }
Hope the comments there were self explanatory. There’s no need for the support animator as well. You can look at my code here, just in case.
That’s it. While this post, for the most part was just my explaining, the actual implementation itself is pretty simply and straightforward once you do it yourself. The Circular Reveal library by ozodrukh is brilliant and works nicely.
Here’s the final output and it looks amazing.
As always, here’s the link to my Git for code reference.
You can get creative with which views your using, be it a simple view or an entire screen transition itself! I simply used WhatsApp’s Attach button as an example.
Show me what you can come up with and drop em in the comments below.
Product Designer who occasionally writes code.
Hii thanks for this awesome post. Little help. How can I close the view when clicking outside the revealed layout.?
Hi Biswajit,
You can use OnTouchListener to do this. If the view is reveal and a touch was detected in the parent layout, undo the reveal.
You know what would be nice? XML.
please share the link of complete source code
If you read through the entire post, you’d find the link towards the end.
how can i make below layout nonclickable and on outside click it shoud close same as whatsapp
Add a new layout beneath the ripple layout. That should prevent touch events from passing through.
How to get the revealed layout on the top of my other views? Just like whatsapp, here my revealed layout will push the other views down and make place for its self. But i want the layout to reveal over the top of current view. Needs your help here plzz..
Could you post your layout on StackOverflow so I could take a look?
Thanks alot for your reply!
Here is my question at stackoverflow http://stackoverflow.com/questions/32642778/circular-reveal-over-top-of-other-views-gives-error-cannot-start-this-animator
Its just that I put your Reveal layout code in a parent LinearLayout and created a EditText and a Button below it I just need the RevealFrameLayout to reveal over the top of this button and edittext and hide them below it.
Thanks and hopefully I made the problem clear and didnt mix the things to confuse you..
http://stackoverflow.com/questions/32642778/circular-reveal-over-top-of-other-views-gives-error-cannot-start-this-animator
Hey here you are hiding the layout and making it visible when required. So the contents below this layout will be pushed down. But to achieve like whatsapp this layout should overlay on existing screen – sort of like pop-up. Any idea how to do it?
yeah that is what i am looking for too. As for I think of that, For that i think we will be creating a container Layout (e.g. LinearLayout) in main.java and inflate our reveal_layout.xml over there and add to that container as child. @Suleiman19 can you tell how to achieve that?
You’re half way there Tariq! Yes you add the reveal layout via the tag. Make sure its in a FrameLayout with layout_gravity=”top”. This must reside directly within a parent FrameLayout.
To make it overlap, let there be 2 FrameLayouts.
1st is the parent which holds the main layout, and 2nd which will hold the reveal view, with a layout_gravity to “top”
Thanks a ton! This is exactly what I was looking for 🙂 Reveal animation is buttery smooth but when i integrated Google AdMob banner ads to my app, reveal animation became buggy. I had read somewhere that AdMob causes FPS drops in android games, but i have just used reveal animation to a small fragment in my app. Is there a way to overcome this problem. I mean, using Reveal animation and AdMob together without any lags in the animation.
Frankly speaking, I haven’t tried using a reveal effect on an ad unit. If the performance takes a significant hit, try to make do without. Your ad could simply be placed at the bottom or attached in a list. Why need a fancy animation for it?
I haven’t applied reveal effect to the ad unit. The ad unit is not even placed in the activity where this reveal effect is integrated. See, I have two activities in my app (activity A and activity B). Reveal effect is applied to a fragment in activity B and the Google AdMob ad is placed at the bottom in activity A and still animation becomes buggy? Can you find a fix for this? This reveal effect would be of no use if we can’t integrate Ads in our apps 🙁
Sorry Suhaib, I’m afraid I can’t be of help here since I didn’t make the library. However I suggest you post this as an issue in GitHub. Hopefully that library’s developer could solve the issue for you?
Another amazing post Suleiman. I was looking for this exact thing. But I’m not able to understand to use it has a transition. Since i would be starting from a FAB in first activity and then revealing the LinearLayout of next activity, I cant understand how to connect them
Glad you liked the post Subhrajyoti.
The trick is not to launch a new activity using the transition, but to include BOTH layouts in the same activity. You can toggle each using the FAB.
// your 1st screen layout containing FAB
// 2nd screen. Toggle this.
You are a life saver. Thank you so much for replying. I definitely have to add you to my app’s credit list
Happy that it helped you. Haha, you’re too kind. By the way, what’s your app about? Link on play store if any?
Amazing Post !! Would implement this soon !!
Glad you liked it. Share what you’ve come up with once done.