Circular Reveal Animation
Overview
Reveal is an animation introduced in Android 5.0 Lollipop (API level 21) that animates the view’s clipping boundaries. Reveal animations provide users visual continuity when you show or hide a group of UI elements. This animation is often used in conjunction with a floating action button.

Setting Up
The ViewAnimationUtils.createCircularReveal() method enables you to animate a clipping circle to reveal or hide a view.
To reveal a previously invisible view using this effect:
void enterReveal() {
// previously invisible view
final View myView = findViewById(R.id.my_view);
// get the center for the clipping circle
int cx = myView.getMeasuredWidth() / 2;
int cy = myView.getMeasuredHeight() / 2;
// get the final radius for the clipping circle: the distance from the
// center to a corner, so the circle covers the whole view
float finalRadius = (float) Math.hypot(cx, cy);
// create the animator for this view (the start radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius);
// make the view visible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();
}
To hide a previously visible view using this effect:
void exitReveal() {
// previously visible view
final View myView = findViewById(R.id.my_view);
// get the center for the clipping circle
int cx = myView.getMeasuredWidth() / 2;
int cy = myView.getMeasuredHeight() / 2;
// get the initial radius for the clipping circle: the distance from the
// center to a corner, so the whole view is visible when the animation starts
float initialRadius = (float) Math.hypot(cx, cy);
// create the animation (the final radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f);
// make the view invisible when the animation is done
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
myView.setVisibility(View.INVISIBLE);
}
});
// start the animation
anim.start();
}
View Transition
You want to call enterReveal() after the enter transition of the activity has been completed.
private android.transition.Transition.TransitionListener mEnterTransitionListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mEnterTransitionListener = new android.transition.Transition.TransitionListener() {
@Override
public void onTransitionStart(android.transition.Transition transition) {
}
@Override
public void onTransitionEnd(android.transition.Transition transition) {
enterReveal();
}
@Override
public void onTransitionCancel(android.transition.Transition transition) {
}
@Override
public void onTransitionPause(android.transition.Transition transition) {
}
@Override
public void onTransitionResume(android.transition.Transition transition) {
}
};
getWindow().getEnterTransition().addListener(mEnterTransitionListener);
...
}
Update the enterReveal() method to remove this listener so that it stops listening to the transition. This is done to disable reveal animation when the activity ends.
private void enterReveal() {
// previously invisible view
final View myView = findViewById(R.id.my_view);
// get the center for the clipping circle
int cx = myView.getMeasuredWidth() / 2;
int cy = myView.getMeasuredHeight() / 2;
// get the final radius for the clipping circle
float finalRadius = (float) Math.hypot(cx, cy);
Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius);
myView.setVisibility(View.VISIBLE);
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
getWindow().getEnterTransition().removeListener(mEnterTransitionListener);
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
anim.start();
}
While exiting the activity after the reveal transition, you want to finish the activity after completing the exit reveal animation.
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
myView.setVisibility(View.INVISIBLE);
// Finish the activity after the exit transition completes.
supportFinishAfterTransition();
}
});
To enable the exit reveal transition when the user presses the ActionBar Up button or goes back, call exitReveal() from onOptionsItemSelected() and from an OnBackPressedCallback registered in onCreate(). (Overriding Activity.onBackPressed() is deprecated as of API level 33.)
@Override
protected void onCreate(Bundle savedInstanceState) {
...
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
exitReveal();
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
exitReveal();
return true;
}
return super.onOptionsItemSelected(item);
}
Common Gotcha
If you do the reveal animation directly on the view, it might look weird because the stroke part of the shape gets revealed as well. To prevent this, you can embed the view inside a FrameLayout which has stroke border as a background. By doing this, the animation reveals the view while the background part remains the same, so it looks like only color part is revealed.
circular_button.xml:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="@android:color/white"/>
<stroke android:width="1dp" android:color="#AAA"/>
</shape>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/button_size"
android:layout_height="@dimen/button_size"
android:background="@drawable/circular_button">
<Button
android:id="@+id/my_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
...
</Button>
</FrameLayout>
Enhancements
The reveal effect can be made to look more natural if it starts where the user tapped. To do this, you could get the x & y coordinates from the MotionEvent and use that to start the reveal effect.
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
// get the final radius for the clipping circle: the view's diagonal is
// long enough to reach every corner from any point inside the view
float finalRadius = (float) Math.hypot(myView.getWidth(), myView.getHeight());
// create the animator for this view (the start radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, (int) motionEvent.getX(), (int) motionEvent.getY(), 0f, finalRadius);
// make the view visible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();
}
return false;
}