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

Part 1 — The skeleton of the project

from giphy.com
<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>
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android"
>

<corners android:radius="100dp" />
<solid android:color="@color/colorPrimary"/>
</shape>
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());
}
}
<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>
android:background="@android:color/background_light"
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);
}

Part 2 — Some configurations

<!-- 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>
<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)
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();
}
}
}

Bonus

Software developer at GetStream.io — I love Android =]

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store