Xamarin.Controls – Xamarin.Forms FloatingActionButton (including iOS!)

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:

  1. A custom Xamarin.Forms `Element`
  2. An Android Custom renderer to use the native `Android.Compat.Design.Widgets.FloatingActionButton`
  3. 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

Screenshot_1493173400

iOS

2017-04-25_10-38-38-PM

If you want to just pull down the control I built on GitHub, the steps are straight forward:

  1. Clone the repository
  2. Reference the PCL in your PCL/Shared Lib
  3. Reference the PCL and native projects in your respective native project
  4. Pull the namespace into your XAML (or C#)
  5. 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.

Advertisements

42 thoughts on “Xamarin.Controls – Xamarin.Forms FloatingActionButton (including iOS!)”

  1. 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

    Like

    1. 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.

      Like

      1. 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

        Like

      2. 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

        Like

      1. 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! :/

        Like

      2. 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.

        Like

  2. 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!

    Like

    1. 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″

      Like

      1. 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?

        Like

  3. 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!

    Like

  4. 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.

    Like

    1. 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.

      Like

  5. 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

    Like

  6. 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.

    Like

    1. 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?

      Liked by 1 person

  7. 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.

    Like

  8. 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”.

    Like

  9. 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.

    Like

  10. 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()));

    Like

  11. 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.

    Like

    1. 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.

      Like

  12. 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.

    Like

  13. 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.

    Like

    1. I have a circle action button but has rectangle border around it. Its border cannot be changed to transparent or to any color.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s