Over the past year we’ve been working on a couple of “big things” that enable you to create more performant applications on more platforms. With our latest stable release, version 2.4.0, we introduced performance optimized renderers on Android, nicknamed Fast Renderers. While faster platform renderers are a clear win, you really start to see the big gains using them in combination with a new feature, debuting in 2.5.0, called Layout Compression. Not to be eclipsed by those performance focused boosts, we’re also releasing Forms Embedding in this preview. We’re excited to take these improvements off the pages of our public roadmap and put them into your hands. Read on for details on how you can start benefiting from these fantastic improvements, and much more.
Big Thing 1: Layout Compression
When optimizing a layout for performance, whether for the purpose of smoother animation or speed of rendering, you quickly learn the value of having a flat view hierarchy. Take this page for example:
<?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:ProfileLayout"
x:Class="ProfileLayout.ProfileLayoutPage"
xmlns:views="using:ProfileLayout.Views"
xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin.Abstractions"
xmlns:converters="using:ProfileLayout.Converters;"
Title="Profile"
BackgroundColor="#F3F3F3">
<ContentPage.Resources>
<ResourceDictionary>
<converters:InvertedBoolConverter x:Key="InvertedBoolConverter"/>
<!-- Global Colors -->
<Color
x:Key="primaryColor">#303030</Color>
<Color
x:Key="secondaryColor">#C9AE98</Color>
<Color
x:Key="validationColor">#FF3F56</Color>
<Color
x:Key="callToActionColor">#4E8B4F</Color>
<Color
x:Key="accentColor">White</Color>
<Color
x:Key="darkAccentColor">#7c6a5c</Color>
<!-- Global Sizes -->
<x:Double
x:Key="mediumTextSize">20</x:Double><x:Double
x:Key="smallTextSize">14</x:Double><x:Double
x:Key="standardPadding">10</x:Double>
<!-- Global Element Styles -->
<Style
TargetType="Entry">
<Setter
Property="HeightRequest"
Value="44" />
<Setter
Property="TextColor"
Value="{StaticResource darkAccentColor}" />
</Style>
<Style
TargetType="NavigationPage"><Setter
Property="BarBackgroundColor"
Value="{StaticResource primaryColor}" /></Style><Style
TargetType="Frame"><Setter
Property="BackgroundColor"
Value="{StaticResource accentColor}" /><Setter
Property="Padding"
Value="{StaticResource standardPadding}" /><Setter
Property="HasShadow"
Value="False" /><Setter
Property="OutlineColor"
Value="{StaticResource secondaryColor}" /></Style><!-- Label Styles --><Style
x:Key="switchLabel"
TargetType="Label"><Setter
Property="TextColor"
Value="#999999" /><Setter
Property="VerticalOptions"
Value="Center" /><Setter
Property="HorizontalOptions"
Value="FillAndExpand" /></Style><!-- Button Styles --><Style
x:Key="callToActionButton"
TargetType="Button"><Setter
Property="BackgroundColor"
Value="{StaticResource callToActionColor}" /><Setter
Property="TextColor"
Value="{StaticResource accentColor}" /><Setter
Property="FontSize"
Value="{StaticResource mediumTextSize}" /></Style>
<Style
x:Key="primaryButton"
TargetType="Button">
<Setter
Property="BackgroundColor"
Value="{StaticResource primaryColor}" />
<Setter
Property="TextColor"
Value="{StaticResource accentColor}" />
<Setter
Property="FontSize"
Value="{StaticResource smallTextSize}" /></Style>
<Style
x:Key="nakedButton"
TargetType="Button">
<Setter
Property="BackgroundColor"
Value="Transparent" />
<Setter
Property="TextColor"
Value="{StaticResource accentColor}" />
<Setter
Property="BorderColor"
Value="Transparent"/>
<Setter
Property="BorderWidth"
Value="0"/>
<Setter
Property="FontSize"
Value="{StaticResource mediumTextSize}" /></Style>
<Style
x:Key="whiteClearButton"
TargetType="Button">
<Setter
Property="BackgroundColor"
Value="Transparent" />
<Setter
Property="BorderColor"
Value="{StaticResource accentColor}" />
<Setter
Property="BorderWidth"
Value="1" />
<Setter
Property="TextColor"
Value="{StaticResource accentColor}" />
<Setter
Property="FontSize"
Value="{StaticResource smallTextSize}" />
</Style>
<Style
x:Key="secondaryButton"
TargetType="Button"><Setter
Property="BackgroundColor"
Value="{StaticResource secondaryColor}" /><Setter
Property="TextColor"
Value="{StaticResource accentColor}" /><Setter
Property="FontSize"
Value="{StaticResource smallTextSize}" /><Setter
Property="FontAttributes"
Value="Bold" /></Style><Style
x:Key="footerButton"
TargetType="Button"><Setter
Property="BackgroundColor"
Value="{StaticResource secondaryColor}" /><Setter
Property="TextColor"
Value="{StaticResource accentColor}" /><Setter
Property="FontSize"
Value="{StaticResource smallTextSize}" /><Setter
Property="FontAttributes"
Value="Bold" /><Setter
Property="HorizontalOptions"
Value="FillAndExpand" /></Style>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="0,0,0,0" Android="0,0,0,0" />
</ContentPage.Padding>
<ContentPage.Content>
<StackLayout
Spacing="0"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<AbsoluteLayout
BackgroundColor="#909090"
HorizontalOptions="FillAndExpand"
HeightRequest="60">
<AbsoluteLayout.Padding>
<OnPlatform x:TypeArguments="Thickness" iOS="0,20,0,0" Android="0,0,0,0" />
</AbsoluteLayout.Padding>
<Button
Margin="10,10"
FontSize="12"
Text="Cancel"
BackgroundColor="Transparent"
Command="{Binding CancelCommand}"
Style="{StaticResource nakedButton}"/>
<Image
AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.36, 0.7"
AbsoluteLayout.LayoutFlags="All"
BackgroundColor="Transparent"
HeightRequest="36"
Source="microsoft_gray.png" />
<Button
IsVisible="{Binding IsLoggedIn}"
Margin="10,10"
BackgroundColor="Transparent"
AbsoluteLayout.LayoutBounds="1, 0, AutoSize, AutoSize"
AbsoluteLayout.LayoutFlags="PositionProportional"
FontSize="12"
Text="Logout"
Command="{Binding LogoutCommand}"
Style="{StaticResource nakedButton}"/>
</AbsoluteLayout>
<Label
HorizontalOptions="FillAndExpand"
BackgroundColor="#D1D1D1"
HeightRequest="22"
TextColor="#000000"
FontSize="14"
HorizontalTextAlignment="Center"
Text="{Binding Title}"/>
<ScrollView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout
Spacing="0"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
<Button
Margin="0,10,0,0"
IsVisible="{Binding CanEdit}"
Text=" EDIT "
Style="{StaticResource primaryButton}"
BackgroundColor="#9B9B9B"
HeightRequest="35"
HorizontalOptions="Center"
Command="{Binding ToggleEditModeCommand}"
/>
<Button
Margin="0,10,0,0"
IsVisible="{Binding IsEditing}"
Text=" EDITING "
BackgroundColor="#9B9B9B"
Style="{StaticResource primaryButton}"
HeightRequest="35"
HorizontalOptions="Center"
Command="{Binding ToggleEditModeCommand}"
/>
<StackLayout
Margin="20"
Spacing="20"
HorizontalOptions="FillAndExpand"
Orientation="Horizontal">
<StackLayout
Spacing="10"
Orientation="Vertical">
<controls:CircleImage
x:Name="ProfilePic"
Source="david.jpg"
BorderThickness="3"
BorderColor="#9B9B9B"
Aspect="AspectFill">
<controls:CircleImage.WidthRequest>
<OnPlatform x:TypeArguments="x:Double"
iOS="104"
Android="104"
WinPhone="104"/>
</controls:CircleImage.WidthRequest>
<controls:CircleImage.HeightRequest>
<OnPlatform x:TypeArguments="x:Double"
iOS="104"
Android="104"
WinPhone="104"/>
</controls:CircleImage.HeightRequest>
</controls:CircleImage>
<Button
IsVisible="{Binding CanSave}"
Text="Change Photo"
Style="{StaticResource nakedButton}"
FontSize="12"
BorderColor="Transparent"
BorderWidth="0"
BackgroundColor="Transparent"
TextColor="#303030"
>
</Button>
</StackLayout>
<StackLayout
Spacing="10"
HorizontalOptions="FillAndExpand"
Orientation="Vertical">
<Entry
IsVisible="{Binding IsLoggedIn, Converter={StaticResource InvertedBoolConverter}}"
HorizontalOptions="FillAndExpand"
Placeholder="Username"
Text="{Binding Username}"
x:Name="UsernameEntry" />
<Label
IsVisible="{Binding IsLoggedIn}"
Text="{Binding Username}"
TextColor="#303030"
FontSize="12"
FontAttributes="Bold"
HeightRequest="40"
VerticalTextAlignment="Center"
/>
<Entry
IsEnabled="{Binding CanSave}"
x:Name="EmailEntry"
HorizontalOptions="FillAndExpand"
Placeholder="Email"
Text="{Binding UserEmail}" />
</StackLayout>
</StackLayout>
<Grid Margin="20, 0, 20, 0">
<Grid.RowDefinitions>
<RowDefinition Height="53" />
<RowDefinition Height="53" />
<RowDefinition Height="60" />
<RowDefinition Height="28" />
<RowDefinition Height="*" />
<RowDefinition Height="45" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Entry
Grid.Row="0" Grid.Column="0"
IsEnabled="{Binding CanSave}"
x:Name="FirstNameEntry"
HorizontalOptions="FillAndExpand"
Placeholder="First Name"
Text="{Binding FirstName}" />
<Entry
Grid.Row="0" Grid.Column="1"
IsEnabled="{Binding CanSave}"
x:Name="LastNameEntry"
HorizontalOptions="FillAndExpand"
Placeholder="Last Name"
Text="{Binding LastName}" />
<Entry
Grid.Row="1" Grid.Column="0"
IsEnabled="{Binding CanSave}"
x:Name="PasswordEntry"
IsPassword="true"
HorizontalOptions="FillAndExpand"
Placeholder="Password"
Text="{Binding UserPassword}" />
<Entry
Grid.Row="1" Grid.Column="1"
IsEnabled="{Binding CanSave}"
x:Name="ConfirmPasswordEntry"
IsPassword="true"
HorizontalOptions="FillAndExpand"
Placeholder="Confirm Password"
Text="{Binding ConfirmUserPassword, Mode=TwoWay}" />
<Label
Grid.Row="2" Grid.ColumnSpan="2"
IsEnabled="{Binding CanSave}"
Margin="10"
Text="Password must be 8 digits long and include 1 number and 1 capital letter."
TextColor="#303030"
FontSize="12"
HorizontalOptions="FillAndExpand"
/>
<views:StrengthIndicators
IsEnabled="{Binding CanSave}"
Strength="{Binding PasswordStrength}"
HorizontalOptions="Center"
Grid.Row="3" Grid.ColumnSpan="2" />
<StackLayout
Margin="0,20,0,40"
Grid.Row="4" Grid.ColumnSpan="2"
IsVisible="{Binding IsLoggedIn}">
<BoxView
Margin="30,10"
BackgroundColor="#9B9B9B"
HorizontalOptions="FillAndExpand"
HeightRequest="1"
/>
<Label
Text="Connected Accounts:"
HorizontalOptions="Center"
TextColor="#303030"
FontSize="12"/>
<views:ConnectSocialButtonView
HorizontalOptions="Center"
BindingContext="{Binding FacebookVM}" />
<views:ConnectSocialButtonView
HorizontalOptions="Center" BindingContext="{Binding TwitterVM}" />
<views:ConnectSocialButtonView
HorizontalOptions="Center" BindingContext="{Binding YouTubeVM}" />
<views:ConnectSocialButtonView
HorizontalOptions="Center" BindingContext="{Binding InstagramVM}" />
</StackLayout>
<Button
Grid.Row="5" Grid.ColumnSpan="2"
IsVisible="{Binding CanSave}"
HeightRequest="45"
x:Name="SaveButton"
Style="{StaticResource primaryButton}"
Text=" Submit "
HorizontalOptions="Center"
Command="{Binding SaveCommand}">
</Button>
</Grid>
</StackLayout>
</ScrollView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
Source: https://github.com/davidortinau/ProfileLayout
Note: this page is NOT optimized at all. In fact, you can probably point to many things that ought to be changed.
Compound this average layout with the need to create additional container renderers and wrappers for platform renderers, and it results in more views in your view tree than are necessary, approximately 130 for this example.
Consider that performing layout requires child-to-parent recursion for measurement and then layout. The deeper a UI nests (view within view within view), the more iterations are required. Layout Compression allows you to specify unnecessary nesting and allows Xamarin.Forms to opt-out of creating that layout view.
Using Xamarin Inspector we can view the UI layering of this page without Layout Compression or Fast Renderers enabled:

Now enable Fast Renderers in the MainActivity.cs:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
...
global::Xamarin.Forms.Forms.SetFlags("FastRenderers_Experimental");
global::Xamarin.Forms.Forms.Init(this, bundle);
...
}
}
And then enable Layout Compression. To enable Layout Compression, identify the layouts (StackLayout, AbsoluteLayout, Grid, RelativeLayout) to compress and add enable it with CompressedLayout.IsHeadless=”true”. For example:
<StackLayout
Spacing="0"
CompressedLayout.IsHeadless="true"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand">
...
</StackLayout>
Now take a look at the layout tree layering:

The improvement in these visuals is obvious. I’ll save you some counting to see how much we were able to flatten the UI.
- Default: 130 Renderers
- Layout Compression: 111 Renderers
- Layout Compression + Fast Renderers: 70 Renderers
The question naturally becomes, “how much faster does this make my application?” This will vary depending on the complexity of your view, the version of the operating system you’re using, and the device on which it’s running. While everyone benefits, we expect to see the biggest difference on older devices.
Because layout compression eliminates the renderer for the layout, note that anything you previously expect to be part of that renderer will no longer be present:
- Background color
- Gestures
- Transformations
- etc.
Layout Compression is available on iOS and Android.
Big Thing 2: Forms Embedding
At Build 2017 we showcased taking a Xamarin.Forms ContentPage and embedding it in native Xamarin.iOS, Xamarin.Android, and UWP applications. Even with that being a very early preview, it didn’t stop many of you from exploring the possibilities. Since then, we’ve been cleaning up the implementation, resolving issues found in our early testing (thank you early adopters!), and validating our use cases for this new capability.
What might you want to do with this?
- Take an existing Xamarin.Forms page and use it in another Xamarin iOS, Android, or UWP application that doesn’t use Xamarin.Forms.
- Start a project using Xamarin.Forms for the speed and ease of delivering a prototype, and then migrate it page by page to Xamarin native/
- Add shared pages to any existing Xamarin application which changing the entire architecture.
And that’s just the beginning. Now there’s no question whether to start with Xamarin.Forms. Use Xamarin.Forms everywhere it’s suitable in your applications!
Big Thing 3: Improved macOS Desktop Support
Xamarin.Forms has grown up in mobile, and extending to desktop paradigms is a process. In Xamarin.Forms 2.4.0, we introduced a preview of macOS support, and I’ve already seen some impressive applications from you. Your feedback has helped identify where we most needed to increase support, so in 2.5.0 you’re getting:
App Exit
From anywhere in your application, you can call Application.Current.Quit() to quit.
Menus
Desktop applications can handle menus differently than touch or mobile applications. In this preview, you can now add context menus on right click, as well as attaching menus to the top level of your application so they appear in the top bar on macOS.
For example, below we construct a menu in C# and attach it to a Label in our view. Right clicking the Label will now open the context menu.
var mainMenu = new Menu();
var locationMenu = new Menu { Text = “Location” };
var changeItem = new MenuItem
{
Text = “Change”,
Command = new Command((obj) =>
{
Navigation.PushModalAsync(new LocationEntryPage());
})
};
locationMenu.Items.Add(changeItem);
var refreshItem = new MenuItem { Text = "Refresh", Command = _vm.ReloadCommand }; MenuItem.SetAccelerator(refreshItem, Accelerator.FromString("cmd+r")); locationMenu.Items.Add(refreshItem); mainMenu.Add(locationMenu); // Context Menu SetMenu(ConditionLabel, mainMenu);

Source: https://github.com/davidortinau/weather-app/tree/macos-and-location
Accelerators
Accelerators, also known as key modifiers, allow you to add keyboard shortcuts to activate menu items. In this example, we’re adding the cmd+R accelerator to the refresh menu item.
MenuItem.SetAccelerator(refreshItem, Accelerator.FromString("cmd+r"));
Then the accelerator is added to a top level menu item, and is available to use from anywhere in the application.
Preview Today!
Xamarin.Forms 2.5.0.19271 is available now on NuGet. To update, open your NuGet package manager, enable the Pre-Release option, and update all of your project references.
What’s with the new version numbers? Our builds are now being generated from Visual Studio Team Services which simply generates a different build number.
Your participation in our pre-releases are essential. Please let us know about any issues you find by filing detailed reports in Bugzilla.