Animated Button Groups in Xamarin Forms

Kenneth Stewart
5 min readMay 11, 2021

This article is first of a series of how-to-guides on creating animated buttons groups. In this one we will use simple view extension animations to create a retractable Nav-bar.

Reveal and Hide Buttons

Layout

Moving Buttons (Or any item with a gesture detector) from its original position breaks the gesture detection, so we must first position the buttons to where we want them at the end of the animation.

We will be using the Opacity property to hide buttons rather than IsVisible, as this will allow us to fade the buttons in slowly rather than having them suddenly appear. Android does not render image buttons with a transparent background by default, so we will also need to set this to avoid buttons having the grey background shown below.

<StackLayout
Orientation="Horizontal"
BackgroundColor="Wheat">
<ImageButton
x:Name="ExpandButton"
BackgroundColor="Transparent"
HorizontalOptions="FillAndExpand"
Clicked="RevealButton_Clicked"
/>
<ImageButton
x:Name="ExploreButton"
Opacity="0"
HorizontalOptions="FillAndExpand"
Scale="0.8"
BackgroundColor="Transparent"
/>
<ImageButton
x:Name="HomeButton"
HorizontalOptions="FillAndExpand"
Opacity="0"
Scale="0.8"
BackgroundColor="Transparent"
/>
<ImageButton
x:Name="SettingsButton"
Opacity="0"
BackgroundColor="Transparent"
HorizontalOptions="FillAndExpand"
Scale="0.8"
/>
<ImageButton
x:Name="HideButton"
Opacity="0"
BackgroundColor="Transparent"
HorizontalOptions="FillAndExpand"
Clicked="HideButton_Clicked"
/>
</StackLayout>

Animations

Xamarin Forms provides two options to trigger animations. Simple Animations which can be triggered as extensions methods on most views and Custom Animations, which allow for more control over the way in which animations are executed. In this example we will be using simple view extension animations.

MyButton.TranslateTo(x, y, time , Easing Function);

In the above example, setting x to 100 will move the button 100 pixels to the right, setting it to -100 would move it 100 pixels the the left.

For Y a positive number would move it up and a negative down.

Time can be set in milliseconds, so setting it to 2000 would mean the animation would take 2 seconds to complete.

Easing types can be accessed through the Easing class and define how an item is animated. Easing.SpringOut for example will overshoot the set value and then return to it, the way a stalled car jolts forward before it stops.

The first thing we will be animating is the ‘reveal arrow’, which will shoot out of view. And then the ‘hide arrow’, whcih we will fade into view. To trigger this we will be using a click event - this needs to be async as we need to await the animations. We also want to ensure the arrow button is aways moved off screen so we will need to get the device width.

double deviceWidth;
public TroopOutButtonGroupPage()
{
InitializeComponent();
deviceWidth = Application.Current.MainPage.Width;
}
async void RevealBtn_Clicked(System.Object s, System.EventArgs e)
{
await ExpandButton
.TranslateTo(deviceWidth + 10,
0,
1000,
Easing.CubicOut);
}

I have added 10 pixels to device width to ensure none of the arrow button appears on screen. Easing has been set to CubicOut, which starts slow and then speed up near the end.

Next we need to reveal the hide button. As we are awaiting animations, the hide button will not be shown until the expand button’s animation has finished.

await HideButton.FadeTo(100, 500, Easing.Linear);

Finally we need to move the expand button back to it’s original position. We are doing this to allow the reveal and hide animations to run in a loop. Before we move it, we first need to hide it, which can be done by setting its opacity to 0.

ExpandButton.Opacity = 0;
await ExpandButton.TranslateTo(0, 0, 0);

We can now set up a click event for the hide button. This click event will look very similar to the method we just wrote but with animations which mirror those. The final result should look like this:

double deviceWidth;
public TroopOutButtonGroupPage()
{
InitializeComponent();
deviceWidth = Application.Current.MainPage.Width;
}
async void RevealBtn_Clicked(System.Object s, System.EventArgs e)
{
await ExpandButton
.TranslateTo(deviceWidth + 10,
0,
1000,
Easing.CubicOut);
await HideButton.FadeTo(100, 500, Easing.Linear);
ExpandButton.Opacity = 0;
await ExpandButton.TranslateTo(0, 0, 0);
}
async void HideBtn_Clicked(System.Object s, System.EventArgs e)
{
await HideButton
.TranslateTo(-deviceWidth - 10,
0,
1000,
Easing.SpringOut);
await ExpandButton.FadeTo(100, 500, Easing.Linear);
HideButton.Opacity = 0;
await HideButton.TranslateTo(0, 0, 0);
}

Now all that’s left to deal with are the Nav-bar buttons we are actually hiding and revealing. These will all use the same fade-in animations.

await ExploreButton.FadeTo(100, 1000, Easing.CubicIn);
await HomeButton.FadeTo(100, 1000, Easing.CubicIn);
await SettingsButton.FadeTo(100, 1000, Easing.CubicIn);

Here we encounter the biggest problem with extension animations, which is that we must await one before the next one can begin. We could shorten the duration of the fade in animation but doing so will almost negate the fade-in effect entirely and even if we do shorten them, there is still a slight pause between one animation ending and another beginning, so it will still appear painfully slow for the user.

To get around this we use Task.WhenAll() which run all 3 animation simutaneously.

await Task.WhenAll(
ExploreButton.FadeTo(100, 1000, Easing.CubicIn),
HomeButton.FadeTo(100, 1000, Easing.CubicIn),
SettingsButton.FadeTo(100, 1000, Easing.CubicIn)
);

To fade out the buttons when the ‘hide arrow button’ is clicked you can use the same animation but set opacity to 0 rather than 100. I chose to await each animation and shorten the time.This looks a lot better fading out than it did fading in. Once you’ve done that you should be left with something like this:

async void RevealBtn_Clicked(System.Object s, System.EventArgs e)
{
await ExpandButton
.TranslateTo(
deviceWidth + 10,
0,
1000,
Easing.SpringOut
);
await Task.WhenAll(
ExploreButton.FadeTo(100, 1000, Easing.CubicIn),
HomeButton.FadeTo(100, 1000, Easing.CubicIn),
SettingsButton.FadeTo(100, 1000, Easing.CubicIn)
);
await HideButton.FadeTo(100, 500, Easing.Linear);
ExpandButton.Opacity = 0;
await ExpandButton.TranslateTo(0, 0, 0);
}
async void HideBtn_Clicked(System.Object sender, System.EventArgs e)
{
await SettingsButton.FadeTo(0, 200, Easing.Linear);
await HomeButton.FadeTo(0, 200, Easing.Linear);
await ExploreButton.FadeTo(0, 200, Easing.Linear);
await HideButton.TranslateTo(
-deviceWidth - 10,
0,
1000,
Easing.SpringOut
);
await ExpandButton.FadeTo(100, 500, Easing.Linear);
HideButton.Opacity = 0;
await HideButton.TranslateTo(0, 0, 0);
}

Thanks for reading! The code can all be found here on the Github Repo.

--

--