You did actually read that title correctly – we have a FloatingActionButton
to use in Xamarin.Forms that works in both Android and iOS!
I’ve put the source code up for this here: https://github.com/SuavePirate/Xamarin.Forms.Controls.FloatingActionButton
It’s rudimentary and has room for some more fun properties, but it is fully functional! If you would like to contribute to the repository, see the TODO:
list at the bottom of the README and start forking and making pull requests!
To breakdown the steps to create your own Floating Action Button in Xamarin.Forms, you’ll need:
- A custom Xamarin.Forms `Element`
- An Android Custom renderer to use the native `Android.Compat.Design.Widgets.FloatingActionButton`
- An iOS Custom renderer to create a button that looks like a FAB.
So let’s go in that order.
In Xamarin.Forms PCL
FloatingActionButton.xaml.cs
public partial class FloatingActionButton : Button { public static BindableProperty ButtonColorProperty = BindableProperty.Create(nameof(ButtonColor), typeof(Color), typeof(FloatingActionButton), Color.Accent); public Color ButtonColor { get { return (Color)GetValue(ButtonColorProperty); } set { SetValue(ButtonColorProperty, value); } } public FloatingActionButton() { InitializeComponent(); } }
We added a new BindableProperty
for the ButtonColor
. This is done because setting the BackgroundColor
will mess up the Android renderer and apply the background behind the FAB. We want to inherit from Button
so that we can utilize some of the already useful properties that come with it – namely the Image
property that consumes a FileImageSource
. We can use this to set the icon for our FAB.
In Android
FloatingActionButtonRenderer.cs
using FAB = Android.Support.Design.Widget.FloatingActionButton; [assembly: ExportRenderer(typeof(SuaveControls.Views.FloatingActionButton), typeof(FloatingActionButtonRenderer))] namespace SuaveControls.FloatingActionButton.Droid.Renderers { public class FloatingActionButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ViewRenderer<SuaveControls.Views.FloatingActionButton, FAB> { protected override void OnElementChanged(ElementChangedEventArgs<SuaveControls.Views.FloatingActionButton> e) { base.OnElementChanged(e); if (e.NewElement == null) return; var fab = new FAB(Context); // set the bg fab.BackgroundTintList = ColorStateList.ValueOf(Element.ButtonColor.ToAndroid()); // set the icon var elementImage = Element.Image; var imageFile = elementImage?.File; if (imageFile != null) { fab.SetImageDrawable(Context.Resources.GetDrawable(imageFile)); } fab.Click += Fab_Click; SetNativeControl(fab); } protected override void OnLayout(bool changed, int l, int t, int r, int b) { base.OnLayout(changed, l, t, r, b); Control.BringToFront(); } protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { var fab = (FAB)Control; if (e.PropertyName == nameof(Element.ButtonColor)) { fab.BackgroundTintList = ColorStateList.ValueOf(Element.ButtonColor.ToAndroid()); } if (e.PropertyName == nameof(Element.Image)) { var elementImage = Element.Image; var imageFile = elementImage?.File; if (imageFile != null) { fab.SetImageDrawable(Context.Resources.GetDrawable(imageFile)); } } base.OnElementPropertyChanged(sender, e); } private void Fab_Click(object sender, EventArgs e) { // proxy the click to the element ((IButtonController)Element).SendClicked(); } } }
A few important things to point out:
- We add the additional using statement `using FAB = Android.Support.Design.Widget.FloatingActionButton;` to help us distinguish between our Xamarin.Forms element and the built in Android control.
- We are NOT using a `ButtonRenderer` as our base class, but instead using a basic `ViewRenderer`. This is because the underlying control will not be a native Android `Button`, but the native Android `FloatingActionButton`.
- Because we replace the `ButtonRenderer`, we need to make sure we still propagate click events up to the Xamarin.Forms element.
Now let’s look at iOS, which can utilize more of the built in pieces from Xamarin.Forms since it supports the BorderRadius
property on Buttons
.
In iOS
FloatingActionButtonRenderer.cs
[assembly: ExportRenderer(typeof(SuaveControls.Views.FloatingActionButton), typeof(FloatingActionButtonRenderer))] namespace SuaveControls.FloatingActionButton.iOS.Renderers { [Preserve] public class FloatingActionButtonRenderer : ButtonRenderer { public static void InitRenderer() { } public FloatingActionButtonRenderer() { } protected override void OnElementChanged(ElementChangedEventArgs<Button> e) { base.OnElementChanged(e); if (e.NewElement == null) return; // remove text from button and set the width/height/radius Element.WidthRequest = 50; Element.HeightRequest = 50; Element.BorderRadius = 25; Element.BorderWidth = 0; Element.Text = null; // set background Control.BackgroundColor = ((SuaveControls.Views.FloatingActionButton)Element).ButtonColor.ToUIColor(); } public override void Draw(CGRect rect) { base.Draw(rect); // add shadow Layer.ShadowRadius = 2.0f; Layer.ShadowColor = UIColor.Black.CGColor; Layer.ShadowOffset = new CGSize(1, 1); Layer.ShadowOpacity = 0.80f; Layer.ShadowPath = UIBezierPath.FromOval(Layer.Bounds).CGPath; Layer.MasksToBounds = false; } protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { base.OnElementPropertyChanged(sender, e); if (e.PropertyName == "ButtonColor") { Control.BackgroundColor = ((SuaveControls.Views.FloatingActionButton)Element).ButtonColor.ToUIColor(); } } } }
We set an explicit WidthRequest
, HeightRequest
, and BorderRadius
to get ourselves a circle. I’m not a big fan of doing it here, since it’s better suited as a calculation, but for now it works.
Lastly in our Draw
override, we set up the drop shadow behind out button, and make sure that our ShadowPath
is actually built from an oval so that it rounds off with the Button
.
Also note that we take the ButtonColor
property and apply it as the BackgroundColor
of the UIButton
to override the color from Xamarin.Forms. Don’t forget to set Text
to null so that we can’t add text to the button and mess it up.
As a side note, iOS might try to link our your custom renderer if you are using it in an iOS Class Library. In order to avoid this, make sure to call a static InitRenderer
method in your AppDelegate.cs
as it will prevent it from being linked out.
Using the FloatingActionButton
Now that we have our renderers registered for our new Element
, we can use it in our XAML or C# of our PCL or Shared Project:
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:SuaveControls.FabExample" xmlns:controls="clr-namespace:SuaveControls.Views;assembly=SuaveControls.FloatingActionButton" x:Class="SuaveControls.FabExample.MainPage"> <StackLayout Margin="32"> <Label Text="This is a Floating Action Button!" VerticalOptions="Center" HorizontalOptions="Center"/> <controls:FloatingActionButton x:Name="FAB" HorizontalOptions="CenterAndExpand" WidthRequest="50" HeightRequest="50" VerticalOptions="CenterAndExpand" Image="ic_add_white.png" ButtonColor="#03A9F4" Clicked="Button_Clicked"/> </StackLayout> </ContentPage>
and our code behind:
MainPage.xaml.cs
namespace SuaveControls.FabExample { public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); } private async void Button_Clicked(object sender, EventArgs e) { await DisplayAlert("FAB Clicked!", "Congrats on creating your FAB!", "Thanks!"); } } }
Then we get these results in our Android and iOS apps:
Android
iOS
If you want to just pull down the control I built on GitHub, the steps are straight forward:
- Clone the repository
- Reference the PCL in your PCL/Shared Lib
- Reference the PCL and native projects in your respective native project
- Pull the namespace into your XAML (or C#)
- Start using it!
The repository also contains an example app that references the source libraries.
If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!
Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.
Hi Alex,
I notice that the shadow in Android looks kind of wierd? The one in iOS looks nice and natural where as the android one finishes abruptly. Is there a way to fix this in android similar to how you override draw in iOS renderer?
Cheers,
Mel
LikeLike
It’s a bug in the native Android support library when there isn’t enough margin around the button and shadow. Try updating the underlying renderer to give it some natural margin.
LikeLike
Hi Alex,
I wasn’t able to find a method to set a margin on the fab, I tried setting padding but that didn’t work either.
In some forums I read that the parent view is clipping the children and there are options such as :
android:clipChildren=”false”
android:clipToPadding=”false”
But I’m not sure how to set this on the parent as my layout is in xamarin forms xaml.
I also noticed that if I use fab.UseCompatPadding = true; the shadow renderers correctly but the shape of the fab becomes smaller and oval. So I either have the right size fab with a clipped shadow or an oval fab with the correct shadow.
Appreciate any help.
Cheers,
Mel
LikeLike
Hi Alex,
I’ve noticed the button shadows are only clipped when they are inside a StackLayout, if I take them out they render correctly. However, this is not ideal because I have 2 buttons and I want to position them the same distance apart so I need a Stack or Relative layout to contain them but both these layouts cause the shadow to be clipped.
I have:
NB: I’ve also tried by placing the fabs in a grid but the same shadow being clipped issue is still there.
Appreciate some help with this.
Cheers,
Mel
LikeLike
Dear Alex! Would you be so kind to check out my repo, because i can not seem to understand what could be that i do wrong, but i cant make it work! The MainPage wont render even! Thank you! https://github.com/207713/FABtest
LikeLike
Your MainPage doesn’t have a layout, but just has the `Label` and `FAB`, so it won’t render it. That’s a good place to start.
LikeLike
Thank you for your fast answer! I have tried to put it in a stacklayout but still nothing gets rendered! However if i remove the fab its ok even without a layout!
LikeLike
Well yeah, if you remove the fab with no layout the label will render. The problem is that in order to have 2 or more controls render, they need to be in a layout. I’ll try to take a second look for other issues.
LikeLike
Hey Alex, I’ve got a really weird issue. I implemented your FAB a few months ago and it’s been working great. However, I’ve made some changes recently that have been giving me some issues. I am using the FAB to navigate to a different page using PushAsync: await Navigation.PushAsync(new MapPage(), false);
I originally had the MapPage I was navigating to set up with a navigation bar, but I have since decided to take it off, so I am setting NavigationPage.SetHasNavigationBar(this, false); in the MapPage. After I did that, and am navigating back to the list page: await Navigation.PopAsync(false); the FAB no longer shows up. I am currently using an absolute layout to get it to show up in the middle of the page at the bottom. If I get rid of the Absolute Layout options in the StackLayout below and it by default shows up at the top of the page, it will still show up after navigating back. Below is the bit of XAML from the page that has the FAB on it:
So, it’s something to do with the AbsoluteLayout and navigating back from a page without a navigation bar, but I can’t figure out what’s going on. I have a pretty major project going on and this is the final issue we are having, so if you need sample code just let me know and I can try to create a test sample app for you. Thanks!
LikeLike
Well, I guess my XAML was removed. It wasn’t anything special other than to show you that my stack layout i had wrapped around the FAB had these parameters: AbsoluteLayout.LayoutFlags=”PositionProportional” and AbsoluteLayout.LayoutBounds=”0.5,1,-1,-1″
LikeLike
This seems like a general issue with the Absolute layout and the navigation bar change. Have you tried swapping the FAB out for a regular button and seeing if the same thing happens? I’ve seen this happen before and was a tracked bug that has come back a few times in Xamarin.Forms. What versions are you using for Xamarin.Forms and Xamarin? Also what platforms are you seeing this on?
LikeLike
Couldn’t reply to your question directly – but yeah, I’m running current versions from the stable channel for everything (VS Mac v7.0.1, Xamarin.Android v7.3.1.2, Xcode v8.3.3, Xamarin.iOS v10.10.0.36) except for the XF nuget package. We are still on 2.3.4.231 because of an issue we were having with our release build with the latest.
I just tried it with a regular button, and I’m still having the same issues, so I guess it is unrelated to the FAB. Sorry, I didn’t think to try that before. Also, I’m only getting the issue on iOS; Android is fine. I’ll keep digging to see if I can figure something out, but if you have any ideas, please let me know. Thanks!
LikeLike
I want to create a button that floats on a Xamarin Forms Maps on iOS to move the map to the user location. But I don’t know how to reference the project that I downloaded on my project. Any help?
LikeLike
You can look at the example project for how to reference it in the short term. I’m also going to be releasing it as a nuget package shortly.
LikeLike
Here’s the nuget package – I’ll be making an announcement for it tomorrow: https://www.nuget.org/packages/SuaveControls.FloatingActionButton
LikeLiked by 1 person
Nice!!!! Tks… works perfectily
LikeLiked by 1 person
The Android shadow in this nugget version is ok?
LikeLike
I’m trying your button, but it is rendering as a square. Any thoughts? See Stack Layout towards bottom. I included full layout in case there was a problem else where effecting the render.
LikeLike
I think it was a change in the Xamarin Forms version that’s causing more aggressive linking which is removing the renderer. You can try to instantiate an instance of the renderer in your MainActivity and AppDelegate to ensure it doesn’t get linked out for now. I’m working on a better solution though.
LikeLike
Hi Alex thank you so much for your nice contribution. I am using your renderer but somehow in this line ((IButtonController)Element).SendClicked(); only in HTC phones app gets crashed. Please can you give me a solution for that.
Thank you
LikeLike
Wow that’s a new one. Can you open a ticket in the github repo? I’ll have to look into it. Thanks for supporting the project!
LikeLike
Hi. This is not working. The “floating” button shows under the label or, in my case, the list view. It is not floating on top of content at all. I tried your github sample project and also tried coding it in my existing solution. Same result. I wish I could post some screenshots here. The simplest test – in your /SuaveControls.FabExample/SuaveControls.FabExample/MainPage.xaml, I set a very long text for the label on line 8. This long text pushes the button out of the screen. Please clarify.
LikeLike
You can DM me images on twitter @Suave_Pirate, but I’ve never seen this issue before. I re-ran the example project on 2 machines and things seem to work fine. Is there something wrong with your layout setup?
LikeLiked by 1 person
Hi. I made it work eventually using an AbsoluteLayout and not the StackLayout like in your example above for page MainPage.xaml. So indeed there is no issue with the renderers but with my layout. Is it is also meant to work with a StackLayout? It might be more useful if you update the screenshots above and actually show how the button is displaying over content. The text we see “This is a floating action button” is way above the button. Thanks.
LikeLike
The control is usable with any sort of layout just as if it were a regular Button. You can use it in a StackLayout if you want it to be in the stack of controls
LikeLike
I am going to send you two screenshots from your github project. First one – as it is. Second one – after setting a very long text for the label in MainPage.xaml. Then I can’t see the “flying button”.
LikeLike
Apparently can’t send files on Twitter as you are not following me. Please suggest an alternative.
LikeLike
Here you go. I uploaded a screenshot to DropBox. There is no button. This is your github example just with a longer text for the label. Link to screenshot: https://www.dropbox.com/s/9w7kl49zh9w4j94/Screen%20Shot%202017-12-13%20at%209.50.38%20PM.png?dl=0
LikeLike
Yes, this is because you have a label and a button in a stack layout and the label pushes the button out of view. You use the FAB just like any other button.
LikeLike
This is what I meant too. Your example above and code in GitHub do not demonstrate the “flying button” unless layout changed to an AbsoluteLayout and controls positioned accordingly. This is not that obvious for Xamarin beginners.
LikeLike
Hi, if use this approach in Android 4.x will crash, to solve:
Change the two lines:
fab.BackgroundTintList = ColorStateList.ValueOf(this.Element.ButtonColor.ToAndroid());
To:
Android.Support.V4.View.ViewCompat.SetBackgroundTintList(fab, ColorStateList.ValueOf(this.Element.ButtonColor.ToAndroid()));
LikeLike
Good call
LikeLike
Hi, I followed your instruction but In android the image is not loading. In IOS i am not tested. I am used Android 25.4.0.2 and Xamarin forms 2.5.0.121. Please help me how to fix this issue.
LikeLike
If you’re able to use that image in a regular button control, it should work with this control. Make sure your image is in the right folder in Resources and has the right build action.
LikeLike
Hi,
In android the image size will be very small. In IOS its working fine. I have set the width/Height is 50 and Border radius is 30 but the circle will be grow not image size will be increased. Please help to fix this. Thanks.
LikeLike
This is because it is using the actual FloatingActionButton control for Android. And the FloatingActionButton spec uses the same size image regardless of the size of the button. Check out the specs here https://material.io/design/components/buttons-floating-action-button.html#anatomy
LikeLike
Hi Alex,
I have followed your instructions but set the button background color to transparent. The result was good except for the border color. It stays black and couldn’t be changed even putting border =0 or its bordercolor = transparent/any color.
LikeLike
What exactly are you trying to achieve? Not sure what a transparent borderless floating action button should look like
LikeLike
I have a circle action button but has rectangle border around it. Its border cannot be changed to transparent or to any color.
LikeLike
please see screenshot. there is a rectangle border around the circle floating action button.
https://www.sendspace.com/file/y1o786
LikeLike