Image for post
Image for post

My FAB brings all the boys to the yard, damn right

(hello there, BulletTooth [rest of name redacted for privacy reasons. I just left this here to prove a point)

The first step is creating a shared element transition between the two activities.

The shared element is, obviously, the FAB. To create the transition, we have to set up some things. Firstly, we have to make the motion circular, otherwise, it the FAB will move in a straight line, which won’t look as snazzy. Which isn’t acceptable, as snazziness was a requirement here. We also have to make sure that the two activities have been set up to support the transitions, otherwise nothing awesome will happen.

public void setupActivityAnimations() {

if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

TransitionSet set = new TransitionSet();
TransitionInflater inflater = TransitionInflater.from(this);
Transition transition = inflater
.inflateTransition(R.transition.arc_trans);

set.addTransition(new ChangeImageTransform());
set.addTransition(transition);
set.setOrdering(TransitionSet.ORDERING_TOGETHER);
set.setDuration(600);
set.setInterpolator(new AccelerateDecelerateInterpolator());

Transition slide = new Slide();
slide.excludeTarget(android.R.id.statusBarBackground, true);
slide.excludeTarget(android.R.id.navigationBarBackground, true);
getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
getWindow().setEnterTransition(slide);
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
getWindow().setAllowEnterTransitionOverlap(true);
getWindow().setSharedElementEnterTransition(set);
getWindow().setSharedElementReturnTransition(set);
getWindow().setSharedElementReenterTransition(set);
getWindow().setSharedElementExitTransition(set);
getWindow().setReenterTransition(slide);
getWindow().setExitTransition(slide);
Window w = getWindow();
w.setFlags(
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
}
}
<?xml version="1.0" encoding="utf-8"?>
<changeBounds xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="100">

<arcMotion
android:maximumAngle="90"
android:minimumHorizontalAngle="35"
android:minimumVerticalAngle="0" />

</changeBounds>
getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);

The second step is making sure that it is the FAB that is the shared element between the two activities

The way we do this is by going into the .xml layout file of both activities and, on both FAB views, set the attribute transitionName to be exactly the same

<android.support.design.widget.FloatingActionButton
android:transitionName="fab_transition"
android:id="@+id/fab"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_margin="16dp"

/>
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, CreateBookActivity.class);

ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this
, fab, "fab_transition");

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

Part 4 is making the animation as snazzy as can be!

Now, you have to reveal the card. The duration for the curved transition is 600ms (in the setupActivityAnimations() method above, you will notice that I set the duration of the animation to 600ms), so that card has to start getting revealed just as the FAB settles into position in the second activity. For that reason, in the second activity, I delay the reveal animation by 600ms. To do that, I do the following

fab.postDelayed(new Runnable() {
@Override
public void run() {

revealView(infoBookLayout);
}
}, 600);
public void revealView(View view) {

int centerY = view.getMeasuredHeight() / 2;
int centerX = view.getMeasuredWidth() / 2;

Animator animator = ViewAnimationUtils.createCircularReveal(view,
centerX, centerY, 0, view.getWidth());

animator.setDuration(400);
view.setVisibility(View.VISIBLE);
animator.setStartDelay(0);
animator.start();

}
public void hideView(final View view) {

int centerY = view.getMeasuredHeight() / 2;
int centerX = view.getMeasuredWidth() / 2;

Animator animator = ViewAnimationUtils.createCircularReveal(view,
centerX, centerY, view.getHeight(), Utilities.dp2px(32, this));

animator.setDuration(800);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
view.setVisibility(View.INVISIBLE);
}
});
animator.start();

}
  • the center in the X-axis (centerX)
  • the center in the Y-axis (centerY),
  • the final revealed view’s largest dimension (the height, in this case, since the view is in portrait) and,
  • as its last argument, the remaining radius of the view after the animation is finished. This is very important!
public static int dp2px(float dp, Context context) {

if (context != null) {
return (int) (dp
* context.getResources().getDisplayMetrics().density + 0.5f);
} else {

return (int) dp;
}
}
victim.animate().setStartDelay(200).setDuration(100).scaleX(0.0f).scaleY(0.0f).start();

Conclusion

Making animation appear effortless is probably more work than you think. Those two seconds or so of animation took me about 4 hours to get exactly as I wanted them. Is it worth it? Or should you spend your time adding, say, more features to your app, watching Shark Tank or chillin’ with some piña coladas?

Founder @ Looxie (http://looxie.co) Android developer for http://codehousefive.com, author of Android Development for Gifted Primates https://amzn.to/2ApMFwe

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