76

As per https://developer.android.com/training/material/animations.html

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:

// previously invisible view
View myView = findViewById(R.id.my_view);

// get the center for the clipping circle
int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;

// get the final radius for the clipping circle
int finalRadius = Math.max(myView.getWidth(), myView.getHeight());

// create the animator for this view (the start radius is zero)
Animator anim =
    ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);

// make the view visible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();

This is meant to reveal a view. How can I use this to circularly reveal an entire activity, without any shared elements?

Specifically, I'd like my searchActivity to circularly reveal from the search action button in the toolbar.

Ishaan Garg
  • 3,014
  • 3
  • 25
  • 28

5 Answers5

93

After looking for a solution for half a day without a result, I came up with an own implementation. I'm using a transparent activity with a matching root layout. The root layout is a view which can then be revealed with createCircularReveal().

My code looks like this:

Theme Definition in styles.xml

<style name="Theme.Transparent" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
    <item name="android:windowBackground">@android:color/transparent</item>
</style>

Activity Definition in AndroidManifest.xml

<activity
        android:name=".ui.CircularRevealActivity"
        android:theme="@style/Theme.Transparent"
        android:launchMode="singleTask"
        />

then I declared a layout for my activity (I've chosen DrawerLayout, so that I can have a NavDrawer. Every layout should work here.)

<android.support.v4.widget.DrawerLayout
    android:id="@+id/drawer_layout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <FrameLayout
        android:id="@+id/root_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/honey_melon"
        >

        <!-- Insert your actual layout here -->

    </FrameLayout>

</android.support.v4.widget.DrawerLayout>

Important is the FrameLayout with the id root_layout. This view will be revealed in the activity.

Finally I implemented CircularRevealActivity and overwrote onCreate():

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    overridePendingTransition(R.anim.do_not_move, R.anim.do_not_move);

    setContentView(R.layout.activity_reveal_circular);

    if (savedInstanceState == null) {
        rootLayout.setVisibility(View.INVISIBLE);

        ViewTreeObserver viewTreeObserver = rootLayout.getViewTreeObserver();
        if (viewTreeObserver.isAlive()) {
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    circularRevealActivity();
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                        rootLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    } else {
                        rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    } 
                }
            });
        }
    }
}

It was important to put circularRevealActivity() into a OnGlobalLayoutListener, because the view needs to be drawn for the animation.

circularRevealActivity() looks like Ishaan's proposal:

private void circularRevealActivity() {

    int cx = rootLayout.getWidth() / 2;
    int cy = rootLayout.getHeight() / 2;

    float finalRadius = Math.max(rootLayout.getWidth(), rootLayout.getHeight());

    // create the animator for this view (the start radius is zero)
    Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootLayout, cx, cy, 0, finalRadius);
    circularReveal.setDuration(1000);

    // make the view visible and start the animation
    rootLayout.setVisibility(View.VISIBLE);
    circularReveal.start();
}

Edit 1

The definition for R.anim.do_not_move was added. However, it should work without that line too, if your design does not specify default transitions for activities. Let me know

R.anim.do_not_move:

<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
    android:fromYDelta="0"
    android:toYDelta="0"
    android:duration="@android:integer/config_mediumAnimTime"
    />
</set>
Saeed Masoumi
  • 8,746
  • 7
  • 57
  • 76
Stefan Medack
  • 2,731
  • 26
  • 32
  • Thanks for such an elaborate answer. Is it working for you? I will give it a go this week. – Ishaan Garg Aug 25 '15 at 10:42
  • Working like a charm for me. I just had the feeling that this circular reveal should be possible without merging two activities into one... If there are problems let me know. – Stefan Medack Aug 25 '15 at 11:05
  • 1
    works great. I changed the cx and cy value to change the point of origin – ch3tanz Sep 18 '15 at 07:12
  • What is the definition of R.anim.do_not_move ? – Jaka Dirnbek Oct 13 '15 at 09:21
  • @JakaDirnbek R.anim.do_not_move is added to override default animations. However, when you have not defined default activity animations, it should work without that line too. I added the code for R.anim.do_not_move to the listing if needed. – Stefan Medack Oct 14 '15 at 07:41
  • @TheOddAbhi it does not for me. Maybe you're calling `createCircularReveal()` twice or you have some default animation/ overriding animation messing it up for you? Or maybe a weird phone (e.g. Samsung :D ), which messes up something? Without code I can not help you precisely. You could open a new thread and link it here. – Stefan Medack Nov 04 '15 at 10:04
  • @StefanMedack I am calling it in globallayoutlistener only. And I have Nexus 5. I think it is because I am doing this in a ViewPager activity where I am adding two fragments to the ViewPager. – Abhishek Balani Nov 05 '15 at 10:32
  • @TheOddAbhi did you find solution for twice revealing ? –  Feb 21 '16 at 20:08
  • 1
    @StefanMedack You shoud add `getViewTreeObserver().removeGlobalOnLayoutListener(this);` in `onGlobalLayout()`. This avoids calling more than once which caused the double revealing. Works here. – WindRider Feb 26 '16 at 17:53
  • Thanks for the advice @WindRider, I added it to the snippets. It actually was already in our sources, to prevent memory leakage, but I forgot to add it here. Hope it also helps TheOddAbhi – Stefan Medack Feb 28 '16 at 18:37
  • Now it works pretty fine but with some additions to support fullscreen and drawing under the statusbar. Good work! – WindRider Feb 29 '16 at 08:44
  • @StefanMedack How can you do this to hide an activity? – DropArt Mar 20 '16 at 14:46
  • @DropArt So far, I haven't needed the other way around, but it should be possible by overwriting `onBackPressed` in your Activity. But if you need some more help. maybe you should ask a new question here on StackOverflow. – Stefan Medack Apr 06 '16 at 10:12
  • @StefanMedack can we launch an app with circular reveal animation. – rupesh Mar 30 '17 at 09:51
  • @Saeed Masoumi can we launch an app with circular reveal animation. – rupesh Mar 30 '17 at 09:51
  • @rup35h yes of course, also there are some utility libraries for circular reveal on github you can use them – Saeed Masoumi Mar 30 '17 at 09:53
  • @SaeedMasoumi thanks for swift response. What i found is that we can use open activity from another opened activity but not the launch app. Can you please give me some lead. thank you :) – rupesh Mar 30 '17 at 09:55
  • @rup35h yes, a transparent activity with a circular reveal after `setContentView`. – Saeed Masoumi Mar 30 '17 at 09:58
  • @SaeedMasoumi can you please elaborate this. – rupesh Mar 30 '17 at 09:59
  • @SaeedMasoumi What about the activity with actionbar? – Pei Aug 04 '17 at 12:57
  • I want to finish my first activity once reveal is complete. Where should I do that? – AndroDev Sep 26 '18 at 15:27
  • Thank you so much! This is the only working solution I found. Even `ActivityOptionsCompat#makeClipRevealAnimation()` does not work correctly. One thing: You should add `overridePendingTransition(0, 0)` after the `finish()` call to prevent a flicker when the activity is finished. – muetzenflo Nov 12 '18 at 16:16
  • Instead of creating a `@+id/rootLayout`, couldn't we just use `window.decorView` ? – laim2003 Jul 18 '19 at 10:23
13

If you want to reverse the circular reveal on leaving activity, use the following modification to onBackPressed().

@Override
public void onBackPressed() {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        int cx = rootLayout.getWidth();
        int cy = 0;
        float finalRadius = Math.max(rootLayout.getWidth(), rootLayout.getHeight());
        Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootLayout, cx, cy, finalRadius, 0);

        circularReveal.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                rootLayout.setVisibility(View.INVISIBLE);
                finish();
            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        circularReveal.setDuration(400);
        circularReveal.start();
    }else{
        super.onBackPressed();
    }
}
jayasoo
  • 343
  • 2
  • 9
9

I think you can use ActivityOptionsCompat.makeClipRevealAnimation .

[https://developer.android.com/reference/android/support/v4/app/ActivityOptionsCompat.html#makeClipRevealAnimation(android.view.View, int, int, int, int)](https://developer.android.com/reference/android/support/v4/app/ActivityOptionsCompat.html#makeClipRevealAnimation(android.view.View, int, int, int, int))

imknown
  • 68
  • 1
  • 11
TeeTracker
  • 7,064
  • 8
  • 40
  • 46
8

To reverse the CircularReveal animation swap the startRadius and endRadius arguments. Also you will need to setup an AnimatorListenerand in the onAnimationEnd() callback method is where you can call finishAfterTransition(). This is for when you press the up navigation or click on the back button.

Etienne Lawlor
  • 6,817
  • 18
  • 77
  • 89
2

ou have to draw the circle view, and after that you should create an animation to it.

Creating the circle view:

public class Circle extends View {

    private static final int START_ANGLE_POINT = 90;

    private final Paint paint;
    private final RectF rect;

    private float angle;

    public Circle(Context context, AttributeSet attrs) {
        super(context, attrs);

        final int strokeWidth = 40;

        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(strokeWidth);
        //Circle color
        paint.setColor(Color.RED);

        //size 200x200 example
        rect = new RectF(strokeWidth, strokeWidth, 200 + strokeWidth, 200 + strokeWidth);

        //Initial Angle (optional, it can be zero)
        angle = 120;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawArc(rect, START_ANGLE_POINT, angle, false, paint);
    }

    public float getAngle() {
        return angle;
    }

    public void setAngle(float angle) {
        this.angle = angle;
    }
}

Creating the animation class to set the new angle:

public class CircleAngleAnimation extends Animation {

    private Circle circle;

    private float oldAngle;
    private float newAngle;

    public CircleAngleAnimation(Circle circle, int newAngle) {
        this.oldAngle = circle.getAngle();
        this.newAngle = newAngle;
        this.circle = circle;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation transformation) {
        float angle = oldAngle + ((newAngle - oldAngle) * interpolatedTime);

        circle.setAngle(angle);
        circle.requestLayout();
    }
}

Put circle into your layout:

<com.package.Circle
    android:id="@+id/circle"
    android:layout_width="300dp"
    android:layout_height="300dp" />

And finally starting the animation:

Circle circle = (Circle) findViewById(R.id.circle);

CircleAngleAnimation animation = new CircleAngleAnimation(circle, 240);
animation.setDuration(1000);
circle.startAnimation(animation);
Mahesh Kumthekar
  • 63
  • 1
  • 1
  • 5