Make your app shine #3: How to reveal an activity with circular revelation

Leandro Borges Ferreira
AndroidPub
Published in
6 min readMay 22, 2017

--

Edit: Please keep in mind that, using this code keeps the last activity in memory, so remember to finish it after the animation or to use this in simple transitions. (Like a splash screen)

Micro interactions are so cool! Android makes possible to create many nice animations with little effort, so it is a good place to create very interactive software (and have fun while you do so!). This time, I will show how to reveal an activity with the circular reveal animation. This can be a really nice way to start a new activity from a circular button (a floating action button, for example)

Part 1 — The skeleton of the project

from giphy.com

I want you to focus on the animation, so I am going to create two really simple activities with solid colors, a text and a button. You can get your hands dirty and make an amazing UI after you learn the animation!

XML for activity 1:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:fitsSystemWindows="true"
android:background="@android:color/background_dark"
tools:context="com.ebanx.circularreveal.MainActivity"
>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="First activity"
android:layout_centerHorizontal="true"
/>

<Button
android:id="@+id/btn"
android:layout_width="70dp"
android:layout_height="70dp"
android:text="GO!"
android:textColor="@android:color/white"
android:layout_centerInParent="true"
android:background="@drawable/button_shape"
/>

</RelativeLayout>

I made the activity with a black background because I want you to see very clearly the animation to the second activity.

I used custom background for the button to make it a circle. Please remind that this animation needs to be used in the right place. It wouldn’t look very nice with a squared button, for example (or maybe it does!).

This is the button_shape:

<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android"
>

<corners android:radius="100dp" />
<solid android:color="@color/colorPrimary"/>
</shape>

Code for activity 1:

public class MainActivity extends AppCompatActivity {

private Button btnCreate;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

btnCreate = (Button) findViewById(R.id.btn);

btnCreate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
presentActivity(v);
}
});
}

public void presentActivity(View view) {
ActivityOptionsCompat options = ActivityOptionsCompat.
makeSceneTransitionAnimation(this, view, "transition");
int revealX = (int) (view.getX() + view.getWidth() / 2);
int revealY = (int) (view.getY() + view.getHeight() / 2);

Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra(SecondActivity.EXTRA_CIRCULAR_REVEAL_X, revealX);
intent.putExtra(SecondActivity.EXTRA_CIRCULAR_REVEAL_Y, revealY);

ActivityCompat.startActivity(this, intent, options.toBundle());
}
}

Really simple, right? All we have to do is to create an scene transition animation to use the default fading animation. I am sending the coordinates to the secound activity so it knows where is it should start de reveal animation. I picked the coordinates of the center of the button, but you can choose the way you prefer.

XML for activity 2:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="32dp"
android:background="@android:color/background_light"
android:fitsSystemWindows="true"
tools:context="com.ebanx.circularreveal.SecondActivity"
>

<TextView
android:text="It works!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
/>

</RelativeLayout>

The only important line is this one:

android:background="@android:color/background_light"

Our second activity has a transparent background, remember? So you need to set a color to the root_layout so you don’t end up with a totally transparent activity.

Code for activity 2:

Now we have the first part of our project. Let's make the transition between the two activities.

public class SecondActivity extends AppCompatActivity {

public static final String EXTRA_CIRCULAR_REVEAL_X = "EXTRA_CIRCULAR_REVEAL_X";
public static final String EXTRA_CIRCULAR_REVEAL_Y = "EXTRA_CIRCULAR_REVEAL_Y";

View rootLayout;

private int revealX;
private int revealY;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);

final Intent intent = getIntent();

rootLayout = findViewById(R.id.root_layout);

if (savedInstanceState == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
intent.hasExtra(EXTRA_CIRCULAR_REVEAL_X) &&
intent.hasExtra(EXTRA_CIRCULAR_REVEAL_Y)) {
[I will show the code for this in a while!]
} else {
rootLayout.setVisibility(View.VISIBLE);
}

Ok, so we get the coordinates from the previous activity. But, before implementing the animation, we need to make some configurations in our project.

Part 2 — Some configurations

You will need to create a values-v21 folder, because the styles that you are going to use are only supported for this version and up.

Create the folder and, inside it, create a styles.xml. We are going to make the style for our first activity:

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowTranslucentStatus">true</item>
</style>

<style name="AppTheme.Transparent" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>

</style>

That is what makes our navigation bar, status bar and window translucent. Our windowBackground must be transparent or we won't be able to achieve the effect that we desire. When the new activity enters, we will be able to see the first activity because the second is transparent.

Apply the code theme in your activities

<activity android:name=".MainActivity"
android:theme="@style/AppTheme"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity"
android:theme="@style/AppTheme.Transparent"
/>

Part 3— The transition

Pay attention! (from giphy)

The animation is always the cool part. If you did everything right in the first and second part, you are going to make a nice animation is this one.

The code for the animation is in our second activity:

If the smartphone is using android api 21 and above the activity will be revealed with a nice circular animation. But if it is not the case, the app still works, but the revelation is the default one.

Just in case you got confused, this is the complete code for the second activity:

public class SecondActivity extends AppCompatActivity {

public static final String EXTRA_CIRCULAR_REVEAL_X = "EXTRA_CIRCULAR_REVEAL_X";
public static final String EXTRA_CIRCULAR_REVEAL_Y = "EXTRA_CIRCULAR_REVEAL_Y";

View rootLayout;

private int revealX;
private int revealY;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);

final Intent intent = getIntent();

rootLayout = findViewById(R.id.root_layout);

if (savedInstanceState == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
intent.hasExtra(EXTRA_CIRCULAR_REVEAL_X) &&
intent.hasExtra(EXTRA_CIRCULAR_REVEAL_Y)) {
rootLayout.setVisibility(View.INVISIBLE);

revealX = intent.getIntExtra(EXTRA_CIRCULAR_REVEAL_X, 0);
revealY = intent.getIntExtra(EXTRA_CIRCULAR_REVEAL_Y, 0);


ViewTreeObserver viewTreeObserver = rootLayout.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
revealActivity(revealX, revealY);
rootLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
} else {
rootLayout.setVisibility(View.VISIBLE);
}
}

protected void revealActivity(int x, int y) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
float finalRadius = (float) (Math.max(rootLayout.getWidth(), rootLayout.getHeight()) * 1.1);

// create the animator for this view (the start radius is zero)
Animator circularReveal = ViewAnimationUtils.createCircularReveal(rootLayout, x, y, 0, finalRadius);
circularReveal.setDuration(400);
circularReveal.setInterpolator(new AccelerateInterpolator());

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

protected void unRevealActivity() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
finish();
} else {
float finalRadius = (float) (Math.max(rootLayout.getWidth(), rootLayout.getHeight()) * 1.1);
Animator circularReveal = ViewAnimationUtils.createCircularReveal(
rootLayout, revealX, revealY, finalRadius, 0);

circularReveal.setDuration(400);
circularReveal.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
rootLayout.setVisibility(View.INVISIBLE);
finish();
}
});


circularReveal.start();
}
}
}

So, this is the project that we made:

(This is a gif and the quality was reduced… The animation will look a lot better in your smartphone)

You can use the unRevealActivity to create the reverse animation, but I won’t put the code for this in the tutorial. I would like you to figure this out by your own (It is really easy, you got this!)

Bonus

You can mix the code of this tutorial and the my first one to make something like this:

And that’s it! Remember to use this animation in the right place. Avoid using it as the default transition in your app, because you are going to polute your app code with the code of this animation. But, in some specific places, this animation can the a really nice way to show the new activity.

If you enjoyed reading this, please click the heart icon below. This will help to share the story with others. If you would like to see more content like this one, follow me.

--

--