I am currently working on an Android app in which I use a FloatingActionButton. I would like to use the speed dial to have multiple actions that spin/jump out of the action button as described in this page by Google on Android design, or as could be seen in an earlier version of the Keep app (sorry, but I can only post one link). I am using the Android Design Support library specifically version 23.1.1 (com.android.support:design:23.1.1). I already searched using Google and looked at the reference for the FloatingActionButton but couldn't find anything concerning the speed dial.
I would like to know if there is a way to easily achieve this using the default FloatingActionButton, or if I have to program all transitions/animations manually?
Additionally I would like to have little labels next to the buttons, describing the action, if possible.
Thank you in advance!
This library is implementing the Speed Dial from the Material Design guidelines:
https://github.com/leinardi/FloatingActionButtonSpeedDial
I'm here to add my 2 cents because this is where I landed after Googling for that exact title.
I hope it doesn't come too late to help someone like me.
First off, the solution comes from here, so is not mine. I just tried and it works nicely. So i thought i share with you in a single post rather have you go dig the code up from there.
The solution uses com.android.support:design:25.3.1
library so be sure to add that to build.gradle and it requires API 21 onwards.
The bad news is that it is composed from several small moving parts: 5 animators, 5 drawables plus the icons and layouts and of course, the code, the good news is that it works as it should, is highly customizable and doesn't require any coding outside MainActivity.
Some notes:
This is the result:
So, the ingredients:
animated_minus.xml
<?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,0L12,24M0,12,L24,12" />
</group>
</vector>
animated_plus.xml
<?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="@animator/rotate_clockwise"
android:name="plus_group" />
<target
android:animation="@animator/plus_to_minus"
android:name="plus_path" />
</animated-vector>
fab_background.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item>
<shape android:shape="oval">
<solid android:color="?android:colorAccent" />
</shape>
</item>
</ripple>
minus.xml
<?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>
plus.xml
<?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,0L12,24M0,12,L24,12" />
</group>
</vector>
fab_state_list_animator.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_pressed="true"
android:state_enabled="true">
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="100"
android:valueTo="3dp"
android:valueType="floatType" />
<objectAnimator
android:propertyName="elevation"
android:duration="0"
android:valueTo="5dp"
android:valueType="floatType" />
</set>
</item>
<!-- base state -->
<item android:state_enabled="true">
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="100"
android:valueTo="0"
android:startDelay="100"
android:valueType="floatType" />
<objectAnimator
android:propertyName="elevation"
android:duration="0"
android:valueTo="5dp"
android:valueType="floatType" />
</set>
</item>
<item>
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="0"
android:valueTo="0"
android:valueType="floatType" />
<objectAnimator
android:propertyName="elevation"
android:duration="0"
android:valueTo="0"
android:valueType="floatType" />
</set>
</item>
</selector>
minus_to_plus.xml
<?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" />
plus_to_minus.xml
<?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" />
rotate_anticlockwise.xml
<?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" />
rotate_clockwise.xml
<?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" />
fab.xml
. All fabs are declared here. Replace android:src
with your own icon on the first 3 ImageButtons
.
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:id="@+id/fab_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:clipChildren="false" >
<!-- Please note that the @id are defined the first time they're referenced from top to bottom -->
<ImageButton
android:id="@+id/fab_action_3"
style="@style/FloatingActionButton.Mini"
android:src="@drawable/ic_volume_up_white_24dp"
android:layout_above="@+id/fab_action_2"
android:layout_alignEnd="@+id/fab"
android:contentDescription="@null"
android:backgroundTint="@color/sa_gray"
android:width="24dp"
android:height="24dp"
android:onClick="fabAction3" />
<ImageButton
android:id="@id/fab_action_2"
style="@style/FloatingActionButton.Mini"
android:src="@drawable/ic_credit_card_white_24dp"
android:layout_above="@+id/fab_action_1"
android:layout_alignEnd="@id/fab"
android:contentDescription="@null"
android:backgroundTint="@color/sa_gray"
android:width="24dp"
android:height="24dp"
android:onClick="fabAction2" />
<ImageButton
android:id="@id/fab_action_1"
style="@style/FloatingActionButton.Mini"
android:src="@drawable/ic_add_shopping_cart_white_24dp"
android:layout_above="@id/fab"
android:layout_alignEnd="@id/fab"
android:contentDescription="@null"
android:backgroundTint="@color/sa_gray"
android:width="24dp"
android:height="24dp"
android:onClick="fabAction1" />
<ImageButton
android:id="@id/fab"
style="@style/FloatingActionButton"
android:src="@mipmap/ic_add_w"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:contentDescription="@null"
android:visibility="visible"
android:layout_marginTop="8dp" />
</RelativeLayout>
</merge>
And lastly.
a) Some declarations:
private static final String TAG = "Floating Action Button";
private static final String TRANSLATION_Y = "translationY";
private ImageButton fab;
private boolean expanded = false;
private View fabAction1;
private View fabAction2;
private View fabAction3;
private float offset1;
private float offset2;
private float offset3;
b) Delete the usual fab code on MainActivity
's onCreate
:
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
and replace it with:
final ViewGroup fabContainer = (ViewGroup) findViewById(R.id.fab_container);
fab = (ImageButton) findViewById(R.id.fab);
fabAction1 = findViewById(R.id.fab_action_1);
// insert onClickListener here
fabAction2 = findViewById(R.id.fab_action_2);
// insert onClickListener here
fabAction3 = findViewById(R.id.fab_action_3);
// insert onClickListener here
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
expanded = !expanded;
if (expanded) {
expandFab();
} else {
collapseFab();
}
}
});
fabContainer.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
fabContainer.getViewTreeObserver().removeOnPreDrawListener(this);
offset1 = fab.getY() - fabAction1.getY();
fabAction1.setTranslationY(offset1);
offset2 = fab.getY() - fabAction2.getY();
fabAction2.setTranslationY(offset2);
offset3 = fab.getY() - fabAction3.getY();
fabAction3.setTranslationY(offset3);
return true;
}
});
c) Add supporting functions on MainActivity
(animation code mostly and the 3 small fab's onClick
methods):
private void collapseFab() {
fab.setImageResource(R.drawable.animated_minus);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(createCollapseAnimator(fabAction1, offset1),
createCollapseAnimator(fabAction2, offset2),
createCollapseAnimator(fabAction3, offset3));
animatorSet.start();
animateFab();
}
private void expandFab() {
fab.setImageResource(R.drawable.animated_plus);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(createExpandAnimator(fabAction1, offset1),
createExpandAnimator(fabAction2, offset2),
createExpandAnimator(fabAction3, offset3));
animatorSet.start();
animateFab();
}
private Animator createCollapseAnimator(View view, float offset) {
return ObjectAnimator.ofFloat(view, TRANSLATION_Y, 0, offset)
.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
}
private Animator createExpandAnimator(View view, float offset) {
return ObjectAnimator.ofFloat(view, TRANSLATION_Y, offset, 0)
.setDuration(getResources().getInteger(android.R.integer.config_mediumAnimTime));
}
private void animateFab() {
Drawable drawable = fab.getDrawable();
if (drawable instanceof Animatable) {
((Animatable) drawable).start();
}
}
public void fabAction1(View view) {
Log.d(TAG, "Action 1");
Toast.makeText(this, "Go shopping!", Toast.LENGTH_SHORT).show();
}
public void fabAction2(View view) {
Log.d(TAG, "Action 2");
Toast.makeText(this, "Gimme money!", Toast.LENGTH_SHORT).show();
}
public void fabAction3(View view) {
Log.d(TAG, "Action 3");
Toast.makeText(this, "Turn it up!", Toast.LENGTH_SHORT).show();
}
d) Reference the fab.xml layout from res/layout/activity_main.xml
Delete the fab declaration:
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_dialog_email" />
Replace with:
<include layout="@layout/fab" />
Last notes:
onClick
code for the fabs and replace it with
onClickListener
. Those should go right where the comment that says
// insert onClickListener here
. Just remember to delete the
onClick
attribute for each fab in fab.xml
file and get rid of
the last 3 functions in MainActivity
(fabAction1
, fabAction2
and fabAction3
).I hope this helps someone and sorry for the wall of text.
I would like to know if there is a way to easily achieve this using the default FloatingActionButton
FAB from Design Library does not have this feature. You need to look for 3rd party FABs (there's a few on android-arsenal to choose from)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With