3 Big Things To Explore in the Xamarin.Forms 2.5.0 Pre-Release

David Ortinau

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.

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.

Discuss this post in the forums!

0 comments

Discussion is closed.

Feedback usabilla icon