In the previous article we constructed a simple implementation of a Floating Action Button which conformed to the material design guidelines in terms of being elevated above the background (with the appropriate shadow) and rising to meet your finger when it is touched. In this article we’ll turn our attention to animating the icon to toggle between two states.
The FAB that we created contains a plus symbol which we created using a VectorDrawable. What we’re going to do is toggle the button between two states, and animate between the ‘plus’ symbol that we currently have, and a ‘minus’ symbol.
Let’s start by creating the VectorDrawable for the minus symbol:
<?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp" android:height="24dp"> <group android:name="plus_group" android:pivotX="12" android:pivotY="12"> <path android:name="plus_path" android:strokeColor="@android:color/white" android:strokeWidth="3" android:pathData="M12,12L12,12M0,12,L24,12" /> </group> </vector>
This is identical to the plus symbol with the exception of the pathData, I have added a couple of extra commands which effectively do nothing, but match the type and number of drawing commands in the plus VectorDrawable. This will enable us to perform a path animation to transition between the two. For more information on animating path data have a read of Vector Drawables β Part 4
What I have done here is to simply draw the vertical element of the plus, but with zero height so that we can animate this element in and out when we transition.
Next we need to create a few property animators. The first will perform a clockwise rotation through 90 degrees:
<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="rotation" android:valueFrom="0" android:valueTo="90" android:valueType="floatType" android:duration="@android:integer/config_mediumAnimTime" />
The next will reverse this and perform an anti-clockwise rotation through 90 degrees:
<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="rotation" android:valueFrom="90" android:valueTo="0" android:valueType="floatType" android:duration="@android:integer/config_mediumAnimTime" />
The next will perform a pathData animation from the plus to minus:
<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="pathData" android:valueFrom="M12,0L12,24M0,12,L24,12" android:valueTo="M12,0L12,24M12,12,L12,12" android:valueType="pathType" android:duration="@android:integer/config_mediumAnimTime" />
And the finl one will reverse this and perform a pathData animation from the the minus to the plus:
<?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:propertyName="pathData" android:valueFrom="M12,0L12,24M12,12,L12,12" android:valueTo="M12,0L12,24M0,12,L24,12" android:valueType="pathType" android:duration="@android:integer/config_mediumAnimTime" />
Everything that we’ve done up to this point is creating the components that we require for two AnimatedVectorDrawables (more information on this can be found in the VectorDrawables series) which will perform the transitions between the two button states. The first will apply the clockwise rotation to the group within the plus drawable, and the plus to minus pathData animation to the path:
<?xml version="1.0" encoding="utf-8"?> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/plus"> <target android:animation="@anim/rotate_clockwise" android:name="plus_group" /> <target android:animation="@anim/plus_to_minus" android:name="plus_path" /> </animated-vector>
The second will perform the anti-clockwise animation and minus to plus animation:
<?xml version="1.0" encoding="utf-8"?> <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/minus"> <target android:animation="@anim/rotate_anticlockwise" android:name="plus_group" /> <target android:animation="@anim/minus_to_plus" android:name="plus_path" /> </animated-vector>
All that remains is to hook these up:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ImageButton fab = (ImageButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { fab.setSelected(!fab.isSelected()); fab.setImageResource(fab.isSelected() ? R.drawable.animated_plus : R.drawable.animated_minus); Drawable drawable = fab.getDrawable(); if (drawable instanceof Animatable) { ((Animatable) drawable).start(); } } }); } }
When the FAB is tapped we toggle the state, switch the drawable, and begin the animation and we get an extremely smooth transition between the two FAB states:
With the transiitions of the FAB itself in place, in the next article we’ll turn our attention to adding some mini FAB actions which will appear and disappear as we toggle the FAB state.
The source code for this article is available here.
© 2015, Mark Allison. All rights reserved.
Copyright © 2015 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
I really don’t like the fact that you are using v21 for these sample, since its no what we are going to use (We need backward compatibility, you know).
Please could you fix these post using the the AppCompact, thanks!
No.
I explained in Part 1 why I made this decision, and I further explain in Part 3 that AppCompat simply does not contain the tools to properly adhere to the material design guidelines. When it does then I’ll look at porting it, but not before then.
How can i achieve this in pre-lollipop version?
I explained in the article that it was specific to Lollipop and later and that the Compat libraries are currently insufficient to implement this well pre-Lollipop.