One of the many new design patterns introduced as part of material design is the Floating Action Button. However, at the time of writing, there is isn’t a control baked in to Android to achieve this, so in this series we’ll look at how to implement a FAB which conforms to the material guidelines.
Before we begin it is worth explaining that the code in this series will be minSdkVersion="21"
. I thought long and hard about making the code backwardly compatible. But felt that it was more appropriate to use the full set of tools available in Lollipop in order to achieve the closest implementation possible to the material guidelines, and that going for a backwardly compatible version would make that much harder.
The first thing that we need to do is to create the plus icon which will be displayed within the FAB. I will be referring frequently to the material guidelines with regard to the FAB so, for reference, the FAB guidelines can be found here. Looking at the redlines for the FAB, the bounding rectangle for the inner icon within the FAB should be 24dp, and the bounds for the entire button should be 56dp, therefore we need to make the plus symbol 24dp square. I’ll create this as a VectorDrawable because it will make things much easier for animating things later on.
<?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>
All we’re doing here is drawing two straight lines, one horizontal and one vertical which intersect at the centre.
Next we need to create the background drawable for the FAB. We’ll use a shape drawable (nested inside a ripple) for this because it will make creating the shadow much easier:
<?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>
This is a simple circle which will be coloured with the accent colour defined in the current theme.
Speaking of the theme, here’s the definition for the theme itself and also a style for the FAB itself:
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar"> <item name="android:colorPrimary">@color/sa_green</item> <item name="android:colorPrimaryDark">@color/sa_green_dark</item> <item name="android:colorAccent">@color/sa_green</item> <item name="android:colorControlHighlight">@color/sa_green_transparent</item> </style> <style name="FloatingActionButton" parent="android:Widget.Material.Button"> <item name="android:background">@drawable/fab_background</item> <item name="android:stateListAnimator">@anim/fab_state_list_animator</item> <item name="android:layout_width">56dp</item> <item name="android:layout_height">56dp</item> </style> </resources>
Here we specify the background tht we’ve just created, and also set the dimensions of the button. Normally I would avoid putting any layout_* attributes in the style because the prefix means that they are related to the parent layout and not the view itself, so therefore are part of the layout and not the style. However, in this case, the FAB should be a fixed size, and width and height are common to all parent layouts, so it is safe to put them in the style to ensure consistency.
One thing to note about is that we extend the Material.Button style, but there is a bit of a gotcha in here which is worth explaining. Initially I extended this without overriding the stateListAnimator. This provided me with almost exactly the behaviour that I was looking for. The FAB had a shadow generated automatically – this is why we used a shape drawable earlier because the framework was able to determine the outline of the shape drawable and generate the appropriate shadow automagically. When pressing the FAB, it would rise slightly to meet the touch – exactly as detailed in the material guidelines. The only thing that I couldn’t get right was the elevation. The guidelines state that a FAB should appear higher than other buttons as it is a primary action, so the elevation needs to be higher than a standard button. However, I found that changing the elevation of the FAB itself in either the style or layout made absolutely no difference.
The reason for this is that the stateListAnimator defined in the parent style (which provides the various states and animates between those states) has elevation and translateZ hard coded, so effectively override any values in the stye or layout. Therefore, in order to change the elevation we have to override the stateListAnimator:
This is identical to the state list animator from the Button style, but I’ve changed the elevation and translationY values so that it sits higher than a standard button.
Now that we’ve got it all defined, we can create a layout containing our FAB:
<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:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:clipChildren="false" android:clipToPadding="false" tools:context=".MainActivity" android:tag="main" android:transitionName="content"> <ImageButton android:id="@+id/fab" style="@style/FloatingActionButton" android:src="@drawable/plus" android:layout_alignParentEnd="true" android:layout_alignParentBottom="true" android:layout_marginBottom="48dp" android:contentDescription="@null" /> </RelativeLayout>
All that remains is to use this layout in our Activity:
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
So we now have a basic FAB which offers the behaviour of a FAB which will pick up the accent colour from the style, and will have the correct shadows, and will rise to the touch:
In the next article we’ll begin looking at how we can expand the FAB to a set of related actions.
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.
Thank you. Finally an example without involving a support library!