Showing posts with label behavior. Show all posts
Showing posts with label behavior. Show all posts

11 June 2016

Floating ‘text balloons’ for context relevant information in Xamarin Forms

Picking the challenge apart

An Italian proverb says that a fool can ask more questions than seven wise men can answer – a modern variant could be that a designer can think up more things than a developer can build. I don’t pretend to be the proverbial wise man, and neither do I want to call my designer colleague a fool, and when he came up with the idea of introducing text balloons with context relevant information, floating on top on the rest of the UI, cross-platform, and preferably appearing with a nice animation, I indeed had to do some head scratching.

What he meant was this, and this is exactly how I created it

The issues I had to tackle, were:

  1. How do I create a text balloon in the first place, with a kind of pointy bit pointing to the UI element it belongs to?
  2. How do I get the absolute position of a UI element - that is, the one the user taps?
  3. How do I show the text balloon in situ?

Text balloon 101

tekstballon

This text balloon consists of a translucent grid that ties the components together. It contains two grids, one of them containing the label. The first grid is the bit that points up. This actually is a 15 by 15 square, rotated 45⁰, and moved a little bit to the left using the new Margins property that has finally made it to Xamarin Forms. The second and biggest grid is the green rectangle actually containing the text you want to show. Because it’s in XAML after the square, drawing precedence rules make that it be drawn on top of the first one. You would not see it at all, if is wasn’t for the fact this grid also has a margin - of 7 on the top so about half of the rotated square. The net result, as you can see, is a triangle sticking out of the rectangle, making the optical illusion of a kind of text balloon. In XAML, this looks like this

<Grid x:Name="MessageGridContainer" xmlns="http://xamarin.com/schemas/2014/forms"
           xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
           x:Class="XamarinFormsDemos.Views.Controls.FloatingPopupControl" 
           BackgroundColor="#01000000">
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="75*"></ColumnDefinition>
    <ColumnDefinition Width="25*"></ColumnDefinition>
  </Grid.ColumnDefinitions>
  <Grid x:Name="MessageGrid" HorizontalOptions="Start" VerticalOptions="Start" >
    <Grid BackgroundColor="{StaticResource  AccentColor}" HeightRequest="15" WidthRequest="15" 
          HorizontalOptions="End" VerticalOptions="Start" Rotation="45" 
          Margin="0,0,4,0" InputTransparent="True"/>
    <Grid Padding="10,10,10,10" BackgroundColor="{StaticResource AccentColor}" Margin="0,7,0,0" 
          HorizontalOptions="FillAndExpand" InputTransparent="True">
      <Label  x:Name="InfoText" TextColor="{StaticResource ContrastColor}" 
             HorizontalOptions="Center" VerticalOptions="Center" 
             InputTransparent="True"/> 
    </Grid>
  </Grid>
</Grid>

The complete text balloon is contained in the “MessageGrid” grid; the pointy bit upward is emphasized using red and underlining. The complete control is contained within yet another grid “MessageGridContainer”, that fills the whole screen – or at least the part in which the text balloons appear. Is has three functions:

  • It provides a canvas to place the actual text balloons on
  • It is an event catcher – as the user taps ‘anywhere on the screen’ the text balloon disappears
  • It makes sure the text balloon never gets wider than 75% of the screen (this was a designer requirement) – hence the columns.

Some important details to take note of:

  • A few elements have set the property “InputTransparent”  to true. This means they will never receive any events (like tap) but those will be received by the elements lying ‘below’ them. In other words, they don’t block events. This makes the text balloon disappear even when you tap on the text balloon itself, as the events goes downwards and is processed by MessageGridContainer
  • MessageGridContainer itself is not opaque but has BackgroundColor "#01000000", that is, 1% black. For all intents and purposes it is opaque in the sense that you don’t see it, but if you leave it totally opaque is will also be opaque to events - on Windows 10 UWP. A little concession to a cross platform issue.
  • This whole contraption is called “FloatingPopupControl” – this is the control that handles showing, displaying and eventually removing the text balloon. ‘Something’ has to call it’s ShowMessageFor method to tell it what the balloon should contain, and under which control it should appear.We will come to that later

Determining absolute position of the ‘anchor element’

The anchor-element is the element under which the text balloon appear should appear when it’s tapped – in this sample, the i-symbol. It is actually pretty to simple to find the absolute position of a relatively placed element: this is achieved by going recursively upwards via the “Parent” propertie and get the sum of all X values and the sum of all Y values. You can actually find hints to this in the Xamarin developer forumsin the Xamarin developer forums, and I have put this into the following extension method:

using Xamarin.Forms;

namespace Wortell.XamarinForms.Extensions
{
  public static class ElementExtensions
  {
    public static Point GetAbsoluteLocation(this VisualElement e)
    {
      var result = new Point();
      var parent = e.Parent;
      while (parent != null)
      {
        var view = parent as VisualElement;
        if (view != null)
        {
          result.X += view.X;
          result.Y += view.Y;
        }
        parent = parent.Parent;
      }
      return result;
    }
  }
}

Positioning, showing, animating and removing text balloons

If you look at the code in the ShowMessageFor method - in the FloatingPopupControl code behind – you’ll see the code is only deferring to FloatingPopupDisplayStrategy. This is done because it’s not wise to put much code into a user control if you want to re-use part of that intelligence easily. It also makes adapting and changing animations easier. FloatingPopupDisplayStrategy has the following constructor:

public class FloatingPopupDisplayStrategy
{
  private readonly Label _infoText;
  private readonly View _overallView;
  private readonly View _messageView;

  public FloatingPopupDisplayStrategy(Label infoText, View overallView, View messageView)
  {
    _infoText = infoText;
    _overallView = overallView;
    _messageView = messageView;

    _overallView.GestureRecognizers.Add(new TapGestureRecognizer
    { Command = new Command(ResetControl) });
    _overallView.SizeChanged += (sender, args) => { ResetControl(); };
  }
}
  • infoText the text balloon name 
  • overallView is the canvas in which the text ballon is placed; it also receives the tap to remove the text balloon again
  • messageView is het containing grid of the text balloon itself

ShowMessageFor, and it’s little helper ExecuteAnimation, are implemented like this:

public virtual async Task ShowMessageFor(
  VisualElement parentElement, string text, Point? delta = null)
{
  _infoText.Text = text;
  _overallView.IsVisible = true;

  // IOS apparently needs to have some time to layout the grid first
  // Windows needs the size of the message to update first
  if (Device.OS == TargetPlatform.iOS || 
      Device.OS == TargetPlatform.Windows) await Task.Delay(25);
  _messageView.Scale = 0;

  var gridLocation = _messageView.GetAbsoluteLocation();
  var parentLocation = parentElement.GetAbsoluteLocation();

  _messageView.TranslationX = parentLocation.X - gridLocation.X -
                              _messageView.Width + parentElement.Width +
                              delta?.X ?? 0;
  _messageView.TranslationY = parentLocation.Y - gridLocation.Y +
                              parentElement.Height + delta?.Y ?? 0;

  _messageView.Opacity = 1;
  ExecuteAnimation(0, 1, 250);
}

private void ExecuteAnimation(double start, double end, uint runningTime)
{
  var animation = new Animation(
    d => _messageView.Scale = d, start, end, Easing.SpringOut);

  animation.Commit(_messageView, "Unfold", length: runningTime);
}
  • First the text that should be displayed in the text balloon is set
  • Next, the canvas in which the text balloon is placed is made visible. As stated before, it’s nearly invisible, but effectively it intercepts a tap
  • Windows and iOS now need a short timeout for some layout events. This feels a bit VB6’ey, doevents, right?
  • The text balloon is scaled to 0, effectively making it infinitely small (and invisible) 
  • Next, we calculate the text balloon’s current absolute location, as well as the anchor element’s(‘parentElement’) absolute location.
  • X and Y translation of the text balloon are calculated to position the text balloon at a location that will make the pointy bit end up just under the blue i-symbol
  • De message grid’s opacity is set to 1, so now the text balloon is visible (but still infinitely small)
  • A 250 ms bouncy animation (Easing.SpringOut) blows up the text balloon to scale 1 – it’s normal size.

Note: the delta uses in the calculation is a value intended to use as a correction value, in case the standard calculation does not yield the desired result (i.e. location). This will be explained later on.

And finally, the user must be able to dismiss the text balloon. This is done using the ResetControl methods. As we have seen in de constructor, this method gets called in case the user types at the invisible canvas, or if the canvas’ size changes.

private void ResetControl()
{
    if (_messageView.Opacity != 0)
    {
      _messageView.Opacity = 0;
      _overallView.IsVisible = false;
    }
}

This method does not need to be called explicitly at initialization, since he invisible grid changes size at the start of the app (because it gets child elements – the MessageGrid and its children), and the event wiring makes this call happen anyway. Another important reason to attach this method to the SizeChanged event is that in Windows 10 UWP apps windows sizes actually can be changed by the user. This may cause text balloons ending up in what is no longer being the right place, so they need to be removed as well. After all, as long as the text balloon is visible, the invisible background blocks any input, so as soon as the user starts working with the app, in any way, the text balloon needs to disappear and the app needs to be ready again.

Behavior intercepting tap event and relay to control

The only thing missing now is something to get the whole process going – respond to the tap on the i-symbol, providing the text balloon contents, and provide some optional positioning correcting for the text balloon. This is done by FloatingPopupBehavior:

using Wortell.XamarinForms.Behaviors.Base;
using Wortell.XamarinForms.Controls;
using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors
{
  public class FloatingPopupBehavior : BindableBehaviorBase<View>
  {
    private IGestureRecognizer _gestureRecognizer;

    protected override void OnAttachedTo(View bindable)
    {
      base.OnAttachedTo(bindable);
      _gestureRecognizer = new TapGestureRecognizer {Command = new Command(ShowControl)};
      AssociatedObject.GestureRecognizers.Add(_gestureRecognizer);
    }

    protected override void OnDetachingFrom(View bindable)
    {
      base.OnDetachingFrom(bindable);
      AssociatedObject.GestureRecognizers.Remove(_gestureRecognizer);
    }

    private void ShowControl()
    {
      if (AssociatedObject.IsVisible && AssociatedObject.Opacity > 0.01)
      {
        PopupControl?.ShowMessageFor(AssociatedObject, MessageText, new Point(Dx, Dy));
      }
    }

    #region PopupControl Attached Dependency Property      
    public static readonly BindableProperty PopupControlProperty =
      BindableProperty.Create(nameof(PopupControl), 
      typeof (IFloatingPopup), typeof (FloatingPopupBehavior),
        default(IFloatingPopup));


    public IFloatingPopup PopupControl
    {
      get { return (IFloatingPopup) GetValue(PopupControlProperty); }
      set { SetValue(PopupControlProperty, value); }
    }
    #endregion

    //MessageText Attached Dependency Property omitted

    //region Dx Attached Dependency Property omitted     

    //region Dy Attached Dependency Property omitted     

  }
}

This behavior is actually rather simple – as soon as the control tot which is attached is tapped, it calls the ShowMessageFor method of the control referenced in de PopupControl property. There are three additional property for determining which text is actually displayed, and two optional properties for a delta X and delta Y which, as we have seen, are included by the control when it actually places the text balloon on the right place.

Bringing it together in XAML

A simplified excerpt from FloatingPopupPage :

<ScrollView Grid.Row="1"  VerticalOptions="Fill" 
   HorizontalOptions="Fill" Margin="10,0,10,0" >
  <Grid>
    <Grid VerticalOptions="Start">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
      </Grid.RowDefinitions>
      <StackLayout Orientation="Horizontal" HorizontalOptions="Fill" >
        <ContentView  HorizontalOptions="FillAndExpand" VerticalOptions="Start">
          <Entry x:Name="NameEntry" Placeholder="Name" 
                  TextColor="{StaticResource AccentColor}" 
                  PlaceholderColor="{StaticResource SoftAccentColor}" />
        </ContentView>
        <ContentView>
          <Image Source="{extensions:ImageResource info.png}" VerticalOptions="Center"
                 HorizontalOptions="End" 
                 HeightRequest="{Binding Height, Source={x:Reference NameEntry}}">
            <Image.Behaviors>
              <behaviors:FloatingPopupBehavior MessageText="Fill in your name here"
                                               PopupControl="{x:Reference PopupControl}" 
                                               Dx="-6" Dy="4"/>
            </Image.Behaviors>
          </Image>
        </ContentView>
      </StackLayout>

    </Grid>
    <controls:FloatingPopupControl x:Name="PopupControl" VerticalOptions="Fill" 
                                   HorizontalOptions="Fill" />
  </Grid>
</ScrollView>

In red the actual i-symbol with the behavior attached to it, in green the popup control (including the label) itself. In the behavior’s properties we actually specify the text to be displayed as well the PopupControl reference, indicating this is the UI control that should actually handle the displaying of the text balloon. In addition it sports an optional extra delta x and delta y. Of course this could be hard coded into the control, but to have this extra flexibility in design time makes for an easier ‘constructable ’UI. As you can see, as soon as the parts are in place, actually using and re-using the components is pretty easy, making adding floating text balloons with contextual relevant information very easy indeed.

Also notice a neat trick to make sure that especially nice in Android, that sports a great rage of resolutions. I took an intentionally too big picture for the i-symbol, which is automatically sized to the height of the entry by using  HeightRequest="{Binding Height, Source={x:Reference NameEntry}}"

Some consequences of this approach

As stated repeatedly, a (nearly) invisible grid covers the whole screen, or at least part of the screen, while a text balloon is displayed – to give the text balloon space to be placed in, and to intercept a tap so it will be removed as soon as the user starts interacting with the app. The flip side is that the app is effectively blocked until the user taps, and this tap will not do anything but removing the text balloon. A plainly visible button will not respond while the text balloon is visible – that requires yet another tap. This may seem annoying, but I don’t think this will put off the user in any significant amount, as it;s likely he will stop using this functionality pretty soon as he/she has gotten the hang of the app. This is only an onboarding/adopting thing. You read the car’s manual only once (if you do it at all, and then never again unless in very extraordinary circustances)

Conclusion

Using only some pretty basic means, a few nifty tricks and a clear architectural approach it appears to be pretty simple to build a kind of re-usable infrastructure enabling the fast and flexible addition of context relevant information, which is displayed in a visually attractive way. It’s very easy to add text balloons this way, and it’s a useful tool to make onboarding and adoption of an app easier.

As usual, a sample project containing this (and previous code) can be found on GitHub

13 May 2016

Keeping input fields above the keyboard in UWP apps

Before you all think I am stark raving mad – it appears that it is actually possible to create XAML constructions that confuse the UWP renderer to such an extent that although it moves the user interface upwards - as it should - it does not always move it up far enough. This can be observed in the video below - as well as the fact that it is fixable.

A user observed this on my app Map Mania (it has been fixed since). I have only been able to repro this on Windows 10 mobile. Apparently it has something to do with going rampant on adaptive triggers, and another key part is the use of an bottom app bar.

The XAML is a simplified version of what I used for the post about a CompositeTrigger-enabled AdaptiveTrigger – basically, I use a simple viewmodel and VisualStateGroup with some Triggers to change what I see on the screen. The XAML is not too complicated:

<Grid >
  <Grid.RowDefinitions>
    <RowDefinition Height="Auto"></RowDefinition>
    <RowDefinition Height="*"></RowDefinition>
  </Grid.RowDefinitions>
  <controls:PageHeader Text="Fix" FontSize="30" VisualStateNarrowMinWidth="0" 
            VisualStateNormalMinWidth="700"></controls:PageHeader >
  <Grid Grid.Row="1" Margin="12" x:Name="TopGrid">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="60"></RowDefinition>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
      </Grid.RowDefinitions>

      <Grid Margin="0,6,0,6" x:Name="NarrowMenu" >
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"></RowDefinition>
          <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="Auto"></ColumnDefinition>
          <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Text="Green" Grid.Row="0"  FontSize="20"
                   Margin="0,0,6,0" Tapped="{x:Bind ViewModel.ToggleDisplay}" >
        </TextBlock>

        <TextBlock Text="Red" Grid.Row="0" Grid.Column="1"  FontSize="20"
                   Margin="6,0,0,0" Tapped="{x:Bind ViewModel.ToggleDisplay}">
        </TextBlock>
        <Grid Height="2" Background="White" Grid.Row="1" Grid.Column="0" Margin="0,0,6,0" 
        x:Name="GreenUnderline"/>
        <Grid Height="2" Background="White" Grid.Row="1" Grid.Column="1" Margin="6,0,0,0" 
        x:Name="RedUnderline"/>
      </Grid>

      <Grid Background="Green" Grid.Row="1" x:Name="GreenArea"></Grid>
      <Grid Background="Red" Grid.Row="1" x:Name="WideRedArea"></Grid>

      <StackPanel Grid.Row="2" Orientation="Vertical" HorizontalAlignment="Stretch" 
         VerticalAlignment="Bottom" >
        <TextBlock  Text="Some label" x:Uid="MapName"  Margin="0,0,0,6"/>
        <TextBox TextWrapping="NoWrap"/>
      </StackPanel>
    </Grid>
  </Grid>
</Grid>

This stuff is based on Template10, but the actual usage is very limited. So first we have some heading, then the menu, then the two areas (green and red) that are used to fill the middle of the screen – it stands in for actual content – and then all the way below, in red and bold, the stackpanel that has some problems, as displayed in the video. When you click on the menu text(“Red” and “Green”) a command in the view model is called that flips a property “TabDisplay”. This triggers the VisualStateManager, which is in fact

The VisualStateManager is actually pretty simple:

<VisualStateManager.VisualStateGroups>
  <VisualStateGroup x:Name="WindowStates" >
    <VisualState x:Name="NarrowState_Red">
      <VisualState.StateTriggers>
        <StateTrigger IsActive="{x:Bind ViewModel.TabDisplay, Mode=OneWay}"/>
      </VisualState.StateTriggers>
      <VisualState.Setters>
        <Setter Target="WideRedArea.Visibility" Value="Visible"></Setter>

        <Setter Target="GreenUnderline.Visibility" Value="Collapsed"></Setter>
      </VisualState.Setters>
    </VisualState>

    <VisualState x:Name="NarrowState_Green">
      <VisualState.StateTriggers>
        <StateTrigger 
          IsActive=
"{x:Bind ViewModel.TabDisplay, Mode=OneWay,Converter={StaticResource BoolInvertConverter}}"/> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="WideRedArea.Visibility" Value="Collapsed"></Setter> <Setter Target="RedUnderline.Visibility" Value="Collapsed"></Setter> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups

So far, so good but when you use a construction like this, and you put anything below it, your might run into issues as I described. Unless you add a little something to the stackpanel:

<StackPanel Grid.Row="2" Orientation="Vertical" HorizontalAlignment="Stretch"
   VerticalAlignment="Bottom" >
  <interactivity:Interaction.Behaviors>
    <behaviors:KeepAboveInputPaneBehavior/>
  </interactivity:Interaction.Behaviors>
  <TextBlock  Text="Some label" x:Uid="MapName"  Margin="0,0,0,6"/>
  <TextBox TextWrapping="NoWrap"/>
</StackPanel

And people who know me won’t be surprised is it actually a behavior again :)

using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Microsoft.Xaml.Interactivity;

namespace WpWinNl.Behaviors
{
  public class KeepAboveInputPaneBehavior : Behavior<FrameworkElement>
  {
    private Thickness _originalMargin;

    protected override void OnAttached()
    {
      base.OnAttached();
      AssociatedObject.Loaded += AssociatedObjectLoaded;
      _originalMargin = AssociatedObject.Margin;
    }

    private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
    {
      AssociatedObject.Loaded -= AssociatedObjectLoaded;
      InputPane.GetForCurrentView().Hiding += InputPaneHiding;
      InputPane.GetForCurrentView().Showing += InputPaneShowing;
    }

    protected override void OnDetaching()
    {
      InputPane.GetForCurrentView().Hiding -= InputPaneHiding;
      InputPane.GetForCurrentView().Showing -= InputPaneShowing;
    }

    private void InputPaneShowing(InputPane sender, InputPaneVisibilityEventArgs args)
    {
      AssociatedObject.Margin = 
        new Thickness(_originalMargin.Left, _originalMargin.Top, 
        _originalMargin.Right, _originalMargin.Bottom + args.OccludedRect.Height);
    }

    private void InputPaneHiding(InputPane sender, InputPaneVisibilityEventArgs args)
    {
      AssociatedObject.Margin = _originalMargin;
    }
  }
}

When the attached object is loaded, it’s original margins are recorded. When the input pane is showing, the height of the ‘OcculedRect’ is added to it, moving the attached object op to exactly above the input bar.

This is possibly a bug, or the SDK team just never imagined people doing odd things with the Visual State Manager – “A fool may ask more questions in an hour than a wise man can answer in seven years”, right ;). Whatever – I like I tell people: you can moan about things like this or cry foul at Microsoft, but I find it much more fun to try and fix them. QED

A sample solution, with the behavior, can be found here.

25 April 2016

Behavior for view model driven animated popups in Xamarin Forms

Preface

popupIn my previous post I showed the basics for animation behaviors in Xamarin Forms. Here’s another one I made, that in conjunction with some nifty element binding makes for a pretty fluid popup appearing from the middle. Of course, you can use the native alert box but I’d rather want to see is something as displayed to the right, especially with a nice animation. The advantage of such a custom made popup is that is looks much more consistent across platforms, which is a huge win especially for LOB apps.

You can see the behavior in action below:

... and all there is to it...

The actual code is pretty small now all the heavy lifting has been done by the base classes from the previous post:

using Wortell.XamarinForms.Behaviors.Base;
using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors
{
  public class AnimateScaleBehavior : AnimateFoldBehaviorBase
  {
    protected override void Init(bool newValue)
    {
      if (newValue)
      {
        FoldOutPosition = 1;
        FoldInPosition = 0;
        AssociatedObject.Scale = FoldInPosition;
      }
    }

    protected override void ExecuteAnimation(double start, double end, 
                                             uint runningTime)
    {
      var animation = new Animation(
        d => AssociatedObject.Scale = d, start, end, Easing.SinOut);

      animation.Commit(AssociatedObject, "Unfold", length: runningTime,
        finished: (d, b) =>
        {
          if (AssociatedObject.Scale.Equals(FoldInPosition))
          {
            AssociatedObject.IsVisible = false;
          }
        });
    }
  }
}

In stead of animating TranslationX and TranslationY, like in the previous post, now the Scale property is animated from 0 to 1 and back. Attentive readers may have seen something else though - as soon as the animation starts, a grey haze appears over the underlying screen, that only disappears when the animation is done. This is entirely done in XAML - using element binding.

The whole popup sits in MenuControl.xaml – this is a user control

<?xml version="1.0" encoding="utf-8" ?>
<Grid xmlns="http://xamarin.com/schemas/2014/forms"
      //Rest of namespace stuff omitted
      IsVisible="{Binding IsVisible, Source={x:Reference AnimatedGrid}}">
  <ContentView BackgroundColor="{StaticResource SeeThrough}"
               HorizontalOptions="Fill" VerticalOptions="Fill" >

    <ContentView Padding="20,0,20,0" x:Name="AnimatedGrid" IsVisible="False">
      <ContentView.Behaviors>
        <behaviors:AnimateScaleBehavior IsVisible="{Binding IsMenuVisible}" 
           ViewIsInitialized="{Binding ViewIsInitialized}"/>
      </ContentView.Behaviors>
      <ContentView Style="{StaticResource MenuStyle}" HorizontalOptions="Fill"
                    VerticalOptions="CenterAndExpand" Padding="0,0,0,10">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="40"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
          </Grid.RowDefinitions>

          <ContentView  Grid.Row="0" Style="{StaticResource PopupHeaderStyle}">
            <Label Style="{StaticResource PopupHeaderTextStyle}" Text="Popup header" 
                VerticalOptions="Center"/>
          </ContentView>

          <StackLayout Orientation="Vertical" Grid.Row="1">

            <StackLayout Style="{StaticResource ContentStyle}"  Orientation="Vertical">
              <Label Style="{StaticResource MenuTextStyle}" Text="Here be text" />  
              <Label Style="{StaticResource MenuTextStyle}" Text="Here be more text"
                  VerticalOptions="Center"/>
            </StackLayout>

            <ContentView Style="{StaticResource Separator}"></ContentView>

            <StackLayout Style="{StaticResource ContentStyle}"  Orientation="Vertical">
              <Label Style="{StaticResource MenuTextStyle}" Text="Here be text" />
              <Label Style="{StaticResource MenuTextStyle}" Text="Here be more text" />
              <Label Style="{StaticResource MenuTextStyle}" 
                  Text="Here be more whatever UI elements you want" />
            </StackLayout>

            <Grid HeightRequest="10"></Grid>

            <Grid Style="{StaticResource ContentStyle}">
              <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="10"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
              </Grid.ColumnDefinitions>
                 <Button Text="Ok" Command="{Binding CloseMenuCommand}"></Button>
              <Button Text="Cancel" Grid.Column="2" 
                  Command="{Binding CloseMenuCommand}"></Button>
            </Grid>
          </StackLayout>
        </Grid>
      </ContentView>
    </ContentView>

    <ContentView.GestureRecognizers>
      <TapGestureRecognizer Command="{Binding CloseMenuCommand}"></TapGestureRecognizer>
    </ContentView.GestureRecognizers>
  </ContentView>

</Grid>

I have marked the interesting parts in red and bold. On top you see that the visibility of the control itself is bound to the visibility of the grid called “AnimatedGrid”. This grid is inside a ContentView which has a background color defined by the resource “SeeTrough. This you can find in app.xaml defined as being color #B2000000, aka "70% black" in designer lingo (i.e. black that is 30% transparent). AnimatedGrid is the top level element of the actual popup – it’s visibility is controlled by the AnimateScaleBehavior. As soon as the animation starts by IsVisible flipping value, the AnimatedGrid is first made visible, and it’s parent – and the SeeThrough ContentView – popup into view, only to disappear when the animation is completely finished. This is exactly what I wanted to achieve – the 70% haze turns the focus of the user to the little task at hand now, and also blocks tapping access to whatever UI elements are still visible below it.

Note, there no values supplied for FoldInTime and FoldOutTime so the default values of 150 and 250ms are used. But you can easily change that by adding them to the behavior in XAML.

As a final thing, I have added a TapGestureRecognizer that calls the popup closing command to the 70% gray area as well. This dismisses the popup when you tap outside it, which is exactly what one expects in a mobile app. It’s effectively the same thing as hitting Cancel (and in this case, Ok, as that does nothing but closing the popup as well).

Viewmodel

Hardly worth mentioning, but to make the picture complete:

using Xamarin.Forms;

namespace XamarinFormsDemos.ViewModels
{
  public class PopupViewModel : MenuViewModelBase
  {
    public Command CloseMenuCommand { get; private set; }

    public PopupViewModel() : base()
    {
      CloseMenuCommand = new Command(() => IsMenuVisible = false);
    }
  }
}
Command makes boolean false. So menu disappears again. Doh ;)

Usage

<?xml version="1.0" encoding="utf-8" ?>
<demoViewFramework:BaseContentPage xmlns="http://xamarin.com/schemas/2014/forms"
    // stuff omitted
  <Grid>
    <!-- Page content -->
    <Grid.RowDefinitions>
      <RowDefinition Height="25*"></RowDefinition>
      <RowDefinition Height="75*"></RowDefinition>
    </Grid.RowDefinitions>
    <Grid Row="0" Style="{StaticResource HeaderOutsideStyle}">
      <ContentView Style="{StaticResource ContentStyle}">
        <Label Text="Popup menu" Style="{StaticResource HeaderStyle}"  
        VerticalOptions="CenterAndExpand"></Label>
      </ContentView>
    </Grid>

    <ContentView Grid.Row="1" Style="{StaticResource ContentStyle}" 
        HorizontalOptions="Fill">
      <Label Text="Here be page content" 
        Style="{StaticResource ContentTextStyle}"></Label>

      <ContentView  VerticalOptions="Start" Style="{StaticResource ContentStyle}" >
          <Button Text="Open popup menu" Command="{Binding ToggleMenuCommand}" 
                  HorizontalOptions="Fill" VerticalOptions="Start"></Button>
      </ContentView>
    </ContentView>

    <!-- Menu -->
    <controls:PopupControl Grid.Row="0" Grid.RowSpan="2"></controls:PopupControl>

  </Grid>
</demoViewFramework:BaseContentPage;/Grid>

Once again, the control is added last and convers the whole screen, as to appear over the existing UI.

Some final words

When you are developing cross-platform apps, UI testing is crucial. For instance, while I was making this app, I noticed that when you change this

<Grid 
      //Namespaces omitted
      IsVisible="{Binding IsVisible, Source={x:Reference AnimatedGrid}}">
  <ContentView BackgroundColor="{StaticResource SeeThrough}"
               HorizontalOptions="Fill" VerticalOptions="Fill">

in this:

       
<Grid 
      //Namespaces omitted
      >
  <ContentView IsVisible="{Binding IsVisible, Source={x:Reference AnimatedGrid}}"
               BackgroundColor="{StaticResource SeeThrough}"
               HorizontalOptions="Fill" VerticalOptions="Fill">

you get a visually a identical result on all three platforms covered in the test project. On Android and Windows 10 it even works the same. But if you try to run on iOS, you will notice the “Open popup menu” is not clickable. This is because the outer grid covers the whole screen and is always visible - and although it’s empty, it apparently blocks all user input. As usual, Apple does things different ;)

The demo project – with updated code – can be found here

This article appeared earlier – in Dutch – on the Wortell company blog

13 April 2016

Behaviors for animated scroll-down and slide-in menus in Xamarin Forms

Preface

Some time ago I wrote about a proof-of-concept for viewmodel driven animations using behaviors in Xamarin Forms. In the mean time, time has moved on, so has Xamarin Forms, and the idea I had back then made it into a kind of framework solution I now used professionally. And it’s time to show how it’s done now in detail.

This article is about building animated scroll-into-view menu’s in Xamarin Forms, and to make sure we all understand what that means, I made this little video showing the code in action on an Android emulator, a Windows 10 Universal Windows Platform app, and an iPhone simulator:

Framework

The demo app contains a simple framework (project DemoViewFramework) that supports a number of things crucial to an app sporting viewmodel driven behaviors, that is:

  • facilitation of  MVVM (using MVVMLight)
  • registration of which view belongs to what viewmodel
  • navigation from viewmode to viewmodel (in stead of from page to page)
  • exposing essential events in the life cycle of the page to it’s accompanying viewmodel.

I won’t go into very much detail about the framework – it’s a pretty naïve implementation anyway – but there are two key things to take away. The first one is the class BaseContentPage

using Xamarin.Forms;

namespace DemoViewFramework
{
  public class BaseContentPage : ContentPage
  {
    protected override void OnAppearing()
    {
      base.OnAppearing();
      Context?.OnViewAppearing();
    }

    protected override void OnSizeAllocated(double width, double height)
    {
      base.OnSizeAllocated(width, height);
      Context?.OnViewInitialized(true);
    }

    protected override void OnDisappearing()
    {
      base.OnDisappearing();
      Context?.OnViewInitialized(false);
      Context?.OnViewDisappearing();
    }

    private IPageViewModelBase Context => (IPageViewModelBase)BindingContext;
  }
}
This class is intended to be a base class for your pages, and it's sole purpose is to channel the OnAppearing, OnSizeAllocated and OnViewDisappearing events to the object in the Page's BindingContext - that is, the view model. To this intent, every view model should implement the interface IPageViewModelBase so the corresponding methods can be called:
namespace DemoViewFramework
{
  public interface IPageViewModelBase
  {
    void OnViewInitialized(bool value);

    void OnViewAppearing(object state = null );

    void OnViewDisappearing();

    INavigationService NavigationService { get; set; }
  }
}
If you are using your own Navigation framework, you can forget about the NavigationService property. To make implementation easier, there's a base class for view models – which is the second key takeaway. In the demo project is a child class from MVVMLight's ViewModelBase, but of course you are very free to implement your own INotifyPropertyChanged base class as well.
using GalaSoft.MvvmLight;

namespace DemoViewFramework
{
  public class PageViewModelBase : ViewModelBase, IPageViewModelBase
  {
    public virtual void OnViewInitialized(bool value)
    {
      ViewIsInitialized = value;
    }

    private bool _viewIsInitialized;
    public bool ViewIsInitialized
    {
      get { return _viewIsInitialized; }
      set { Set(() => ViewIsInitialized, ref _viewIsInitialized, value); }
    }

    public virtual void OnViewAppearing(object state = null)
    {
      ViewHasAppeared = true;
    }

    public virtual void OnViewDisappearing()
    {
      ViewHasAppeared = false;
    }

    private bool _viewHasAppeared;
    public bool ViewHasAppeared
    {
      get { return _viewHasAppeared; }
      set { Set(() => ViewHasAppeared, ref _viewHasAppeared, value); }
    }

    public INavigationService NavigationService { get; set; }
  }
}
Key thing here is that there are two properties - ViewHasAppeared and ViewIsInitialized - available in every view model and they are set automatically by the framework. Behaviors handling animations need to know when they actually can start doing stuff and they can bind to one of these properties. As I explained in a previous blog post, from Xamarin 2.1 apparently you can only start after OnSizeAllocated, which translates to ViewIsInitialized in the view model, so the initialization code needs to be fired when that property is set to true. The actual activation of the behavior (that is, the animation), needs to come from another property. That may sound complicated, but I assure you it's not that bad. Just read on.

The (base) view model for driving animations

Basically, we need something to kick off the animations. To this extent, we are using this very simple view model

using DemoViewFramework;
using Xamarin.Forms;

namespace XamarinFormsDemos.ViewModels
{
  public class MenuViewModelBase : PageViewModelBase
  {
    private bool _isMenuVisible;

    public MenuViewModelBase()
    {
      ToggleMenuCommand = new Command(() => IsMenuVisible = !IsMenuVisible);
    }

    public bool IsMenuVisible
    {
      get { return _isMenuVisible; }
      set { Set(() => IsMenuVisible, ref _isMenuVisible, value); }
    }

    public Command ToggleMenuCommand { get; private set; }
  }
}

A command that toggles a simple boolean property. I mean, how hard can it be, right?

Leveling the playing field

If you are coming from Windows behaviors, like I do, you are in for a surprise. Xamarin behaviors are a prime example of something that quacks like a duck and walks like a duck, can be a goose after all. Some things are a bit different and – like a goose – can bite you pretty badly. First of all, there is no standard binding context, and there is no AssociatedObject property to easily refer to. This can make things a bit complex when 'translating' behaviors from Windows to Xamarin and back, hence this base class to make sure we have the same - or at least a more similar - base

using System;
using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors.Base
{
  public abstract class BindableBehaviorBase<T> : Behavior<T> 
    where T : VisualElement
  {
    protected T AssociatedObject { get; private set; }

    protected override void OnAttachedTo(T bindable)
    {
      AssociatedObject = bindable;
      bindable.BindingContextChanged += Bindable_BindingContextChanged;
      base.OnAttachedTo(bindable);
    }

    protected override void OnDetachingFrom(T bindable)
    {
      bindable.BindingContextChanged -= Bindable_BindingContextChanged;
      base.OnDetachingFrom(bindable);
      AssociatedObject = null;
    }

    private void Bindable_BindingContextChanged(object sender, EventArgs e)
    {
      if (AssociatedObject != null)
      {
        BindingContext = AssociatedObject.BindingContext;
      }
    }
  }
}
It's a bit of an oddball class, but it's purpose is simple - after this has run, you can bind to any of the behavior's dependency properties, and in your code you can refer to a typed AssociatedObject, just like you would in Windows XAML behaviors.

Getting a bit animated

Now earlier in this blog post I mentioned the fact that behaviors doing animations probably need to know when a view is ready initializing, so they can initialize themselves. To get to this point, there is this base class on top of  BindableBehaviorBase, with a similar ‘original name:

using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors.Base
{
  public abstract class ViewInitializedBehaviorBase<T> : BindableBehaviorBase<T> 
    where T : VisualElement
  {
    #region ViewIsInitialized Attached Dependency Property      
    public static readonly BindableProperty ViewIsInitializedProperty =
       BindableProperty.Create(nameof(ViewIsInitialized), typeof(bool), 
       typeof(ViewInitializedBehaviorBase<T>),
       default(bool), BindingMode.TwoWay,
       propertyChanged: OnViewIsInitializedChanged);

    public bool ViewIsInitialized
    {
      get { return (bool)GetValue(ViewIsInitializedProperty); }
      set { SetValue(ViewIsInitializedProperty, value); }
    }

    private static void OnViewIsInitializedChanged(BindableObject bindable, 
            object oldValue, object newValue)
    {
      var thisObj = bindable as ViewInitializedBehaviorBase<T>;
      thisObj?.Init((bool)newValue);
    }

    #endregion

    protected abstract void Init(bool viewIsInitialized);
  }
}

When you bind the behavior’s ViewIsInitialized property to the viewmodel’s ViewIsInitialized property, the behavior knows ‘when the view is ready (or not anymore). Whatever, if the property in the view model changes, the behavior calls its (now abstract) method Init.

So what happens is

BaseContentPage –> PageViewModelBase –> (property binding) ViewInitializedBehaviorBase.Init –>
  Concrete Implementation.Init

And finally - menu animations

One more base class to go. I noticed pretty soon that animations handling the folding or scrolling of things almost always consider one property to be animated. So I created a yet another base class

using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors.Base
{
  public abstract class AnimateFoldBehaviorBase : 
    ViewInitializedBehaviorBase<View>
  {
    protected double FoldInPosition;
    protected double FoldOutPosition;

    protected VisualElement GetParentView()
    {
      var parent = AssociatedObject as Element;
      VisualElement parentView = null;
      if (parent != null)
      {
        do
        {
          parent = parent.Parent;
          parentView = parent as VisualElement;
        } while (parentView?.Width <= 0 && parent.Parent != null);
      }

      return parentView;
    }

    protected override void OnAttachedTo(View bindable)
    {
      base.OnAttachedTo(bindable);
      bindable.IsVisible = false;
    }

    private void ExecuteAnimation(bool show)
    {
      if (show)
      {
        AssociatedObject.IsVisible = true;
        ExecuteAnimation(FoldInPosition, FoldOutPosition, (uint)FoldOutTime);
      }
      else
      {
        ExecuteAnimation(FoldOutPosition, FoldInPosition, (uint)FoldInTime);
      }
    }

    protected abstract void ExecuteAnimation(double start, double end, 
      uint runningTime);

    public static readonly BindableProperty IsVisibleProperty =
       BindableProperty.Create(nameof(IsVisible), typeof(bool), 
       typeof(AnimateFoldBehaviorBase),
       false, BindingMode.OneWay,
       propertyChanged: OnIsVisibleChanged);

    public bool IsVisible
    {
      get { return (bool)GetValue(IsVisibleProperty); }
      set {SetValue(IsVisibleProperty, value); }
    }

    private static void OnIsVisibleChanged(BindableObject bindable, 
       object oldValue, object newValue)
    {
      var thisObj = bindable as AnimateFoldBehaviorBase;
      thisObj?.ExecuteAnimation((bool)newValue);
    }   

    // FoldInTime Attached Dependency Property      
 
    // FoldOutTime Attached Dependency Property      
  }
}

This class handles a few important things. First of all, it gets the parent view – in a menu’s case the page. In fact, it finds the first object that has a width, thus a size. This is the behavior’s ‘playing field’. Then there is the ExecuteAnimation method, that actually starts the animation – or reverses it. Note also that the behavior actually hides the element that it’s animating – this whole setup assumes you will move something into view, while it’s not in view initially. You might notice that the Init method which is abstract in it's parent class is still not implemented. That happens only in the concrete class - that is actually pretty small, now all the ground work has been laid:

using Wortell.XamarinForms.Behaviors.Base;
using Xamarin.Forms;

namespace Wortell.XamarinForms.Behaviors
{
  public class AnimateSlideDownBehavior : AnimateFoldBehaviorBase
  {
    protected override void Init(bool newValue)
    {
      if (newValue)
      {
        var parentView = GetParentView();
        if (parentView != null)
        {
          FoldInPosition = -parentView.Height;
          AssociatedObject.TranslationY = FoldInPosition;
        }
      }
    }

    protected override void ExecuteAnimation(double start, 
       double end, uint runningTime)
    {
      var animation = new Animation(
        d => AssociatedObject.TranslationY = d, start, end, Easing.SinOut);

      animation.Commit(AssociatedObject, "Unfold", length: runningTime,
        finished: (d, b) =>
        {
          if (AssociatedObject.TranslationY.Equals(FoldInPosition))
          {
            AssociatedObject.IsVisible = false;
          }
        });
    }
  }
}

So on Init, the menu is moved up exactly it’s own height and - assuming it’s on top of the page - it will appear just outside the view. The actual animation code itself is laughably simple – it just animates over TranslationY, and when it find it’s ending at the FoldInPosition, it will make the animated object invisible

All that’s left now is defining the menu in XAML and then adding the behavior to it. It’s important to add the menu after the actual page content so it will be drawn over it. Otherwise it will just slide behind it, and that’s not very useful.

<!-- Menu -->
<ContentView Grid.Row="0" Grid.RowSpan="2">
  <ContentView.Behaviors>
    <behaviors:AnimateSlideDownBehavior
      IsVisible="{Binding IsMenuVisible}"
      ViewIsInitialized="{Binding ViewIsInitialized}"
      FoldOutTime="400" FoldInTime="300"/>
  </ContentView.Behaviors>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="75*"></RowDefinition>
      <RowDefinition Height="25*"></RowDefinition>
    </Grid.RowDefinitions>
    <ContentView Style="{StaticResource MenuStyle}">
      <StackLayout Style="{StaticResource ContentStyle}" 
        Orientation="Vertical" VerticalOptions="Start">
        <Image Source="chevronup.png" HeightRequest="40" 
          VerticalOptions="Start" HorizontalOptions="Start">
          <Image.GestureRecognizers>
            <TapGestureRecognizer Command="{Binding ToggleMenuCommand}"/>
          </Image.GestureRecognizers>
        </Image>
        <Label Text="Menu" Style="{StaticResource HeaderStyle}"
          VerticalOptions="Start"/>
        <Label Text="Here be menu content" 
         Style="{StaticResource MenuTextStyle}"></Label>
      </StackLayout>
    </ContentView>

    <!-- Leftover space -->
    <ContentView Grid.Row="1"  HorizontalOptions="Fill" VerticalOptions="Fill">
      <ContentView.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding ToggleMenuCommand}"/>
      </ContentView.GestureRecognizers>
    </ContentView>
  </Grid>
</ContentView>

Note the binding of ViewIsInitialized to ViewIsInitialized for the initialization, and of IsVisible to IsMenuVisible for the actual execution of the animation. Also notice the FoldOutTime and FoldInTime – the menu will fold out in 400, and fold in in 300ms. These are optional values – if you don’t specify them, the behavior will use default values. Finally, note the fact that the menu actually covers the whole screen, but the lower part is empty. Yet, if you tap that part, the menu will move away as well, as you would expect in an app.

The AnimateSlideInBehavior is nearly the same but animates TranslateX – just have a look at the sample code, it’s yours for the taking

Some concluding remarks

Of course you can also code these animations into code behind or in a control’s code behind, but that way you will need to copy and paste code into your views time and time again, and potentially you will need to fix the same bugs on multiple places. What I usually do is indeed make a control, but use the behavior in that and then re-use the control over multiple pages. Using a behavior decouples the code from a specific control or page, and you can re-use the dynamic behavior everywhere.

One final bit – the Xamarin iOS linker is still sometimes ‘optimizing’ away code, just like .NET Native, something that already bit me last year. So in the Wortell.XamarinForms there is this extremely odd class Initializer:

namespace Wortell.XamarinForms
{
  public static class Initializer
  {
    public static void Init()
    {
    }
  }
}
that indeed does completely nothing, except for being called from the iOS project's AppDelegate
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    global::Xamarin.Forms.Forms.Init();
    global::Wortell.XamarinForms.Initializer.Init();
    LoadApplication(new App());

    return base.FinishedLaunching(app, options);
}
Which still does nothing, but  tricks the compiler into thinking the code is actually used (which is true) so the assembly actually gets packaged with the app.

I hope this blog will inspire you to try your hand at animations with Xamarin Forms as well. It’s actually pretty easy when you get the hang of it. And there is more to come on this blog.

Parts of this text appeared earlier on the Wortell company blog, in Dutch

30 March 2016

Xamarin Forms 2.1 upgrade–some surprises (and how to get around it)

Back in June I wrote about a proof-of-concept for view model driven animations using behaviors in Xamarin Forms. This concept made it into my employer’s standard Xamarin library (more about that soon) but after an upgrade I noticed the animation behaviors did not work anymore – at least on Android. I have no idea what happened along the way, but fact is that the the behavior no longer worked after upgrading to Xamarin Forms 2.1. A prolonged debugging session learned me the following:

  1. At the ViewAppearing stage user interface elements don’t have a size allocated yet – whereas they used to have that.
  2. A parent object - especially something a grid – does not does not necessarily has to have it’s size set yet (width and height are still –1)

I will write about this soon in more detail, but what needs to be done to get this working again is:

  1. We need to initialize the behavior on the page’s OnSizeAllocated, not the OnViewAppearing method
  2. We need to go up recursively until we find an element that does not have a size of –1 in stead of blindly taking the parent.

So, in StartPage.xaml.cs:

protected override void OnAppearing()
{
  Context.OnViewAppearing();
  base.OnAppearing();
}

protected override void OnSizeAllocated(double width, double height)
{
  Context.OnViewAppearing();
  base.OnSizeAllocated(width, height);
}

It is a bit of a kludge, but it will do for now. In FoldingPaneBehavior we will first need to add a method to recursively find a parent with a size:

protected VisualElement GetParentView()
{
  var parent = associatedObject as Element;
  VisualElement parentView = null;
  if (parent != null)
  {
    do
    {
      parent = parent.Parent;
      parentView = parent as VisualElement;
    } 
    while (parentView?.Width <= 0 && parent.Parent != null);
  }

  return parentView;
}
And the first line of the Init method neededs to be changed from
var p = associatedObject.ParentView;
to
var p = GetParentView();

And then the behavior will work again.

A second surprise when upgrading to Xamarin Forms 2.1 is that the declaration of Dependency properties using generics is deprecated. It will still work, but not for long. So in stead of

public static readonly BindableProperty IsPopupVisibleProperty =
       BindableProperty.Create<FoldingPaneBehavior, bool>(t => t.IsPopupVisible,
       default(bool), BindingMode.OneWay,
       propertyChanged: OnIsPopupVisibleChanged);
You will know have to use
public static readonly BindableProperty IsPopupVisibleProperty =
       BindableProperty.Create(nameof(IsPopupVisible), typeof(bool), 
       typeof(FoldingPaneBehavior),
       default(bool), BindingMode.OneWay,
       propertyChanged: OnIsPopupVisibleChanged);
And the callback loses its type safety because that becomes
private static void OnIsPopupVisibleChanged(BindableObject bindable, object oldValue, object newValue)
in stead of
private static void OnIsPopupVisibleChanged(BindableObject bindable, bool oldValue, object bool)

and thus you have to cast oldValue and newValue to bool. This makes the code inherently more brittle and harder to read, but I assume there is a good reason for this. I have updated the xadp snippet for creating these properties accordingly.

A typical case of moved cheese, but fortunately not too far. The updated demo solution is still on GitHub

28 February 2016

Keeping UI elements above and below the Universal Windows Platform app bars

A little history - or how I messed up

I initially created this behavior  for Windows Phone 8.1, and then ported it to Windows 8.1 store apps. I presume at one point during creating WpWinNl for Universal Windows Platform I found out that whatever I tried back then did not work anymore, and Microsoft seemed to be moving away from the app bar anyway… whatever, I can’t remember. Fact is, the KeepFromBottomBehavior fell by the wayside during the beta times of Windows 10 and I forgot about it. Time moved on, Windows SDKs finalized and the app bar stayed after all… and last week I got an email from Craig Trought who apparently had used this behavior in previous apps and had found out that it was not in the prerelease WpWinNl UWP package, which kind of was a bit unfortunate as he was now trying to make his app run on UWP. Can’t have people being stuck on UWP development because of me dropping a ball, so I took on the challenge to get the behavior to work again. And in the process I discovered that indeed my approach to this solution did not work at all anymore. Apparently the way Microsoft implemented the app bars had changed considerably.I also found out Craig is quite a persistent fellow, who found I had missed a few points in my first two attempts to port it to UWP.

Purpose of the behavior(s)

The idea behind the KeepFromBottomBehavior (and now it’s little brother, KeepFromTopBehavior) is to prevent (parts of) the UI being covered by the app bar, whether it’s in its closed state or being opened. It should also work when the ApplicationView’s DesiredBoundsMode is set to ApplicationViewBoundsMode.UseCoreWindow and take into account that most Windows 10 mobile devices now have a ‘soft keys navigation bar’, that is – there are no separate buttons – either physical or capacitive– on the device anymore, but just a button bar drawn on the  of the screen. To make things more complicated, you can hide this button bar by swiping from bottom to top over it (making the same gesture brings it back, too) to let apps make full use of the screen real estate. Too make a little more clear what I mean, consider these three screen shots of the app in the mobile emulator:

screen1screen2screen3

To the left, you see how the app looks with both app bars closed. In the middle, you see what happens when both app bars are opened – they cover part of the UI. Ugly. The rightmost screenshot shows what happens when the behaviors are applied – key parts of the UI are moved to stay into view.

ApplicationViewBoundsMode? What ApplicationViewBoundsMode?

Usually the  ApplicationView’s DesiredBoundMode is set to  ApplicationViewBoundsMode.UseVisible. In that case, Windows 10 only provides you with the space between the top app bar and the bottom app bar, if those exist – in their closed state. In Windows 10 mobile, if there is no top bar, the area covered by status bar – the thing on top that shows your battery status and stuff like that –  is not available either.

If you, on the other hand, specify ApplicationViewBoundsMode.UseCoreWindow you get all of the screen to draw on, and stuff appears behind app bars, navigation bars and status bars. This mode is best used in moderation, but can be useful if you for instance want to draw a screen filling map (as in my sample) but then it’s your own responsibility to make sure things don’t get covered up. This is one of the use cases of the behavior. The other is to make sure that whenever the app bars get opened, they still don’t cover op part of the UI. To make things extra complicated, it must also support programmatically made changes to the appbar size (from minimal to compact and back) and support navigating back with NavigationCacheMode.Required.

To see how it looks like – more screenshots!

screen4screen5screen6screen7

To the left you see the app with closed app bars, which I intentionally made translucent so you can see what is going on. Although the map nicely covers the whole screen, the rest of the UI looks pretty horrible. The 2nd to left screenshot shows the app with both app bars opened, and that is a complete mess. The 3rd to left shows closed app bars with the behaviors applied, and the rightmost shows both app bars open, also with the behaviors applied. The parts of the UI we want to not be covered by anything, are moved up- and downwards to stay into view. You are welcome ;)

So how does this work?

image

Very differently from the 8.1 era, I can tell you that. Using Microsoft’s new Live Visual Tree explorer (thanks tools team!) I found out something about the innards of the Windows 10 command bars – inside it there’s a popup that appears over the command par when you click the ellipses button. So if the app bar opens, we  only need to move the stuff we want to keep into view a number of pixels equal to the height of the popup minus the height of the app bar’s closed height. That works fine for the ApplicationViewBoundsMode.UseVisible mode, but for the ApplicationViewBoundsMode.UseCoreWindow things are quite a bit more complicated. The base setup of KeepFromBottomBehavior is as follows:

public class KeepFromBottomBehavior : Behavior<FrameworkElement>
{
  protected override void OnAttached()
  {
    if (!Initialize())
    {
      AssociatedObject.Loaded += AssociatedObjectLoaded;
    }
    base.OnAttached();
  }

  private void AssociatedObjectLoaded(object sender, RoutedEventArgs e)
  {
    AssociatedObject.Loaded -= AssociatedObjectLoaded;
    Initialize();
  }

  private bool Initialize()
  {
    var page = AssociatedObject.GetVisualAncestors().OfType<Page>().FirstOrDefault();
    if (page != null)
    {
      AppBar = GetAppBar(page);
      if (AppBar != null)
      {
        OriginalMargin = AssociatedObject.Margin;

        AppBar.Opened += AppBarManipulated;
        AppBar.Closed += AppBarManipulated;
        AppBar.SizeChanged += AppBarSizeChanged;
        UpdateMargin();
        ApplicationView.GetForCurrentView().VisibleBoundsChanged += VisibleBoundsChanged;
        return true;
      }
    }
    return false;
  }

  protected AppBar AppBar { get; private set; }

  protected Thickness OriginalMargin { get; private set; }

  protected virtual AppBar GetAppBar(Page page)
  {
    return page.BottomAppBar;
  }
}

In the AssociatedObjectLoaded I collect the things I am interested in:

  • The current bottom margin of the object that needs to be kept in view
  • The app bar that we are going to watch – if it’s being opened, closed, or changes size we need to act
  • Whether the visible bounds of the current view have changed. This is especially interesting when a phone is being rotated or the navigation bar is being dismissed.

The fact that the app bar is being collected by and overrideable method is to make a child class for keeping stuff from the top more easy. There is more noteworthy stuff: I try to initialize from OnAttached first, then from OnLoading is that fails. This is because if you use NavigationCacheMode.Required, the first time you hit the page OnAttached is called, but the visual tree is not read, so I have to call from OnLoaded. If you move away, then navigate back, OnLoaded is not called, only OnAttached. Navigate away and back again, then both events are called. So it’s “you never know, just try both”. Note also, by the way, the use of GetVisualAncestors – that’s a WpWinNl extension method, not part of the standard UWP API. It finds the page on which this behavior is located, and from that, the app bar we are interested in. So if one of the events we subscribed to fire, we need to recalculate the bottom margin, which is exactly as you see in code:

private void AppBarSizeChanged(object sender, SizeChangedEventArgs e)
{
  UpdateMargin();
}

private void VisibleBoundsChanged(ApplicationView sender, object args)
{
  UpdateMargin();
}

void AppBarManipulated(object sender, object e)
{
  UpdateMargin();
}

private void UpdateMargin()
{
  AssociatedObject.Margin = GetNewMargin();
}

And then we come to the heart of the matter. First we see the simple method GetDeltaMargin, that indeed calculates the difference in height between an opened and closed app bar:

protected double GetDeltaMargin()
{
  var popup = AppBar.GetVisualDescendents().OfType<Popup>().First();
  return popup.ActualHeight - AppBar.ActualHeight;
}

And then comes the real number trickery.

protected virtual Thickness GetNewMargin()
{
  var currentMargin = AssociatedObject.Margin;
  var baseMargin = 0.0;
  if (ApplicationView.GetForCurrentView().DesiredBoundsMode == 
       ApplicationViewBoundsMode.UseCoreWindow)
  {
    var visibleBounds = ApplicationView.GetForCurrentView().VisibleBounds;
    baseHeight = CoreApplication.GetCurrentView().CoreWindow.Bounds.Height - 
                   visibleBounds.Height + AppBar.ActualHeight;

    if(AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Mobile")
    {
      baseMargin -= visibleBounds.Top;
    }
  }

  return new Thickness(currentMargin.Left, currentMargin.Top, currentMargin.Right,
                          OriginalMargin.Bottom + 
                            (AppBar.IsOpen ? GetDeltaMargin() + baseMargin : baseMargin));

}

Right. If the simple flow is followed ( that is, for ApplicationViewBoundsMode.UseVisible), we simply need to add the difference in height of a closed and an opened app bar to the bottom margin. On the other hand, if ApplicationViewBoundsMode.UseCoreWindow was used, we need to calculate the base margin of the element we need to keep above the app bar, whether this bar is closed or not, for Windows doesn’t take care of that for us now. You wanted the whole window, you get the whole window. So the base margin is the whole height of the AppBar itself plus the difference between the windows height and the visible height -  that is, the height of that portion of the windows that would be used if we were just letting Windows take care of things. In addition, if we are on Windows 10 mobile, we have to subtract the visible top to take care of the status bar.

Also very important – the cleaning up if we leave the page! After all, we might return to a cached version of it! So all events are detached, and the original margin of the UI element handled by this behavior is restored.

protected override void OnDetaching()
{
  AppBar.Opened -= AppBarManipulated;
  AppBar.Closed -= AppBarManipulated;
  AppBar.SizeChanged -= AppBarSizeChanged;
  ApplicationView.GetForCurrentView().VisibleBoundsChanged -= VisibleBoundsChanged;
  ResetMargin();
  base.OnDetaching();
}

private void ResetMargin()
{
  AssociatedObject.Margin = OriginalMargin;
}

KeepFromTopBehavior

Basically we only have to override GetAppBar, GetOrginalMargin and of course GetNewMargin. This is a tiny bit simpler than KeepFromBottomBehavior – as we are setting the top we can now directly relate to the top margin differences, and don’t need to worry about differences between Windows 10 and Windows 10 mobile:

public class KeepFromTopBehavior : KeepFromBottomBehavior
{
  protected override Thickness GetNewMargin()
  {
    var currentMargin = AssociatedObject.Margin;
    var baseMargin = 0.0;
    if (ApplicationView.GetForCurrentView().DesiredBoundsMode ==
       ApplicationViewBoundsMode.UseCoreWindow)
    {
      var visibleBounds = ApplicationView.GetForCurrentView().VisibleBounds;
      baseMargin = visibleBounds.Top - 
        CoreApplication.GetCurrentView().CoreWindow.Bounds.Top + AppBar.ActualHeight;
    }
    return new Thickness(currentMargin.Left,
      OriginalMargin.Top + 
       (AppBar.IsOpen ? GetDeltaMargin() + baseMargin : baseMargin), 
        currentMargin.Right, currentMargin.Bottom);
  }

  protected override AppBar GetAppBar(Page page)
  {
    return page.TopAppBar;
  }
}

Concluding remarks

This is the first time I actually had to resort to checking for device family for UI purposes; the U in UWP makes application development so universal indeed that these kinds of things usually get abstracted pretty much away. As always, you can have a look at the sample solution, which is still the same as in the Windows 8.1 article, only I have added a UWP project. If you want to observe the difference between ApplicationViewBoundsMode.UseVisible and ApplicationViewBoundsMode.UseCoreWindows yourself, go to the App.Xaml.cs and uncomment the line that sets UseCoreWindow (line 68).

Oh by the way, this all works on Windows 10 desktop of course as well but making screenshots for mobile is a bit easier. The code for this is also in WpWinNl on Github already (be sure to head for the UWP branch), but I am still dogfooding the rest of the package so that is still not released as a NuGet package.

I want to thank Craig for being such a thorough tester – although he excused himself for being “such a pain” I would love to have more behaviors being put through the grinder like this, it really improves code quality.

09 December 2015

Release of WpWinNl 3.0.2 alpha with UWP support - and map data binding

I just released my open source library WpWinNl to 3.0.2-alpha (that is, pre-release) to NuGet. WpWinNl now supports Windows Phone 8, Windows Phone 8.1, Windows 8.1 and the new Universal Windows Platform. All the basic libraries are updated to the latest version.
The library builds three NuGet packages, which can be found on NuGet as of now:
  1. WpWinNlBasic, which contains everything except the stuff that hangs on MVVMLight
  2. WpWinNl, builds on top of WpWinNlBasic and MVVMLight (and has dependencies on both)
  3. WpWinNlMaps, that builds on top of WpWinNlBasic. This is code that allows you to bind data to maps utilizing a special behavior
For Windows Phone 8, Windows Phone 8.1 and Windows 8.1 basically nothing changes. With respect to UWP, the following changes have been made:
  1. The behaviors are built on top of the new UWP Behaviors NuGet Package
  2. WpWinNlMaps has a slightly expanded API to support the new features by the new maps control, as well align more with the x:Bind scenario
There is a sample application for WpWinNlMaps in GitHub that I will explain more in detail in a follow-up post. Make sure that if you have a look at the code, take the UWP branch.
A few words of warning:
  • Apart from WpWinNlMaps, this has not yet been extensively tested, hence the pre-release designation.
  • No attempt has been made to share code between UWP and the earlier incarnations of these packages. Effectively I have ceased working on wp8/wp81/win81 code, as of now only the UWP code will move forward and at one point in time these packages will stop supporting them at all.
  • Parts of this library, especially the behaviors, may be removed entirely and end up in the Microsoft Behaviors NuGet package.
However, for the time being, I think especially the map binding is pretty important, so is was time to release it. Any feedback is welcome.

13 June 2015

View model driven animations using behaviors and MVVMLight in Xamarin Forms

Intro
For some time now I have been making a number of behaviors implementing animations driven by view models for Windows and Windows Phone. Implementing animations in behaviors results into reusable animation building block without the need to copy and paste the functionality into code behind – or the need to remember how the internals exactly worked (you know they say a good programmer is a lazy programmer ;) ). I wanted to check if it would be possible to make animations for Xamarin Forms in a similar way, and it turns out to be possible. I have created a proof-of-concept that allows you to animate a kind of popup using a behavior, a view model and a little bit of code-behind plumbing. The popup folds open in a vertical direction from the center of the screen. It looks like this:

Setting the stage
I started out with a standard Xamarin Forms Portable project, and brought in my favorite MVVM framework MVVMLight. Then I added an empty StartPage.xaml to the portable project, and modified the constructor of the default App class to start with that page, in stead of with the default code:

public App()
{
  MainPage = new StartPage();
}

This is quite a standard way to set up a Xamarin Forms application using a XAML-based start page. I did not make it up myself ;) .

Passing startup events to the view model
A behavior in Xamarin.Forms has two events you can trap – OnAttachedTo and OnDetachingFrom – by basically overriding the methods with that name. But those are fired apparently before the visual element to which the behavior is attached to is loaded and made part of a layout: the attached object has no size, and no parent. To make matters more complicated, there is also no OnLoaded event on the attached object, like in WinRT XAML. So when do you know when the layout is ready?

It appears that on page level there is such an event: OnAppearing (and it’s counterpart OnDisppearing) which once again can be trapped by overriding those methods. What we need to do is pass the fact that these methods have been called to the view model. The more or less standard way to do that seems to be something like as follows. I first defined an interface:

namespace AnimationBehaviorDemo.ViewModels
{
  public interface IPageViewModelBase
  {
    void OnViewAppearing();
    void OnViewDisappearing();
  }
}

and then a base class implementing that interface. This is not strictly necessary, but I always feel compelled to make a base class for convenience, yet I don’t want to force myself or my fellow developers into always needing to use that base class. The interface allows me an escape route out of a possible undesirable inheritance kludge. I think that’s just good architecture.

using GalaSoft.MvvmLight;

namespace AnimationBehaviorDemo.ViewModels
{
  public class PageViewModelBase : 
     ViewModelBase, IPageViewModelBase
  {
    public virtual void OnViewAppearing()
    {
      ViewHasAppeared = true;
    }

    public virtual void OnViewDisappearing()
    {
      ViewHasAppeared = false;
    }

    private bool viewHasAppeared;
    public bool ViewHasAppeared
    {
      get { return viewHasAppeared; }
      set { Set(() => ViewHasAppeared, ref viewHasAppeared, value); }
    }
  }
}

And there we see the familiar MVVMLight syntax again. So great if you can use an awesome friend from the Windows ecosystem in a cross platform setting again (thanks Laurent!).

Anyway, now I can make a view model inheriting from PageViewModelBase (or at least implementing IPageViewModelBase), use it as binding context and pass the events to the view model using the code behind of the start page like this:

public partial class StartPage : ContentPage
{
  public StartPage()
  {
    InitializeComponent();
    BindingContext = new MyViewModel();
  }

  protected override void OnAppearing()
  {
    Context.OnViewAppearing();
    base.OnAppearing();
  }

  protected override void OnDisappearing()
  {
    Context.OnViewDisappearing();
    base.OnDisappearing();
  }

  private IPageViewModelBase Context
  {
    get { return (IPageViewModelBase)BindingContext; }
  }
}

In every page you make using this behavior then needs to have this kind of plumbing. Of course, you can define a nice base class for this as well, making the last three methods disappear again. Have a blast. I only have one page in this solution, so I leave it at this. The important thing is – we have now a way to pass the fact that the view is ready to the view model, a behavior can bind to this, and things can happen in the right order.

The view model
Using the PageViewModelBase as a starter, I add a simple command and a property for the behavior to bind to as well:

using System.Windows.Input;
using GalaSoft.MvvmLight.Command;

namespace AnimationBehaviorDemo.ViewModels
{
  public class AnimationViewModel : PageViewModelBase
  {
    public AnimationViewModel()
    {
      TogglePopup = new RelayCommand(DoTogglePopup);
    }
public ICommand TogglePopup { get; private set; } private void DoTogglePopup() { IsPopupVisible = !IsPopupVisible; } private bool isPopupVisible; public bool IsPopupVisible { get { return isPopupVisible; } set { Set(() => IsPopupVisible, ref isPopupVisible, value); } } } }

Nothing special here – standard MVVMLight view model and syntax.

The animation behavior
I will be going through this step by step, to hopefully convey what I am doing here. The basics are the two methods I already have mentioned by name: OnAttachedTo and OnDetachingFrom
using System;
using Xamarin.Forms;

namespace AnimationBehaviorDemo.Behaviors
{
  public class FoldingPaneBehavior: Behavior<View>
  {
    private View associatedObject;

    private double desiredHeight;

    protected override void OnAttachedTo(View bindable)
    {
      base.OnAttachedTo(bindable);
      associatedObject = bindable;
      bindable.BindingContextChanged += (sender, e) =>
          BindingContext = associatedObject.BindingContext;
    }

    protected override void OnDetachingFrom(View bindable)
    {
      associatedObject = null;
      base.OnDetachingFrom(bindable);
    }
  }   
}    

This basically sets up the behavior, keeping a reference to the object it’s bound to. In the OnAttachedTo there is an odd piece of code. Basically, if you don’t use this, binding to the Bindable Properties that I will show later on will not work. I have no idea why this is. It took me quite some nosing around in the Xamarin forums and eventually I found this solution here in this thread. I am not sure if this is a recommendable way. But it seems to work.

Then we get to Bindable Properties, and I think of those as the Attached Dependency Properties of WinRT XAML (in fact, they have been in XAML since WPF). In fact, we need two: one to get notified of the view having appeared, and one for the popup needing to displayed (and hidden again). For the sake of brevity, I show only the property for the popup toggle:

#region IsPopupVisible Attached Dependency Property
public static readonly BindableProperty IsPopupVisibleProperty =
   BindableProperty.Create<SlidingPaneBehavior, bool>(t => t.IsPopupVisible,
   default(bool), BindingMode.OneWay,
   propertyChanged: OnIsPopupVisibleChanged);

public bool IsPopupVisible
{
  get
  {
    return (bool)GetValue(IsPopupVisibleProperty);
  }
  set
  {
    SetValue(IsPopupVisibleProperty, value);
  }
}

private static void OnIsPopupVisibleChanged(BindableObject bindable, 
                                            bool oldValue, bool newValue)
{
  var thisObj = bindable as SlidingPaneBehavior;
  if (thisObj != null)
  {
    thisObj.AnimatePopup(newValue);
  }
}
#endregion

Like Attached Dependecy Properties in Windows this is quite some code, a lot hinges on connecting the dots in the right way, and a part of it depends on naming conventions. To make life easier, I made a Visual Studio snippet for that. The whole purpose of this construct is to make sure the AnimatePopup method gets called with the right parameter whenever the bound property changes.

But first – initialization
The first Bindable Property – that I did not even show – is called OnViewHasAppeared, is bound to the OnViewHasAppeared of the view model, and fires a method that is (very originally) called Init:

private double unfoldingHeight;
private const double HeightFraction = 0.6;
private const double WidthFraction = 0.8;

private void Init()
{
  var p = associatedObject.ParentView;
  unfoldingHeight = Math.Round(p.Height * HeightFraction, 2);
  associatedObject.WidthRequest = Math.Round(p.Width * WidthFraction , 2);
  associatedObject.HeightRequest = 0;
  associatedObject.IsVisible = false;
}

This sets the width of the popup to 0.8 times that of the parent view, it’s height to 0 and it’s visibility to false, effectively creating an invisible pane. It also calculates the height to which the panel should be unfolded, that is, when it is unfolded – 0.6 times the height of the parent. Mind you, these values are now constants, but could just as well be properties as well, with values you could set from XAML - thus creating a more customizable experience.

The actual animation is done by these two methods and some more constants:

private const int FoldOutTime = 750;
private const int FoldInTime = 500;

private void AnimatePopup(bool show)
{
  if (show)
  {
    associatedObject.IsVisible = true;
    Animate(0, unfoldingHeight, FoldOutTime);
  }
  else
  {
    Animate(unfoldingHeight, 0, FoldInTime);
  }
}

private void Animate(double start, double end, uint runningTime)
{
  var animation = new Animation(
    d => associatedObject.HeightRequest = d, start, end, Easing.SpringOut);

  animation.Commit(associatedObject, "Unfold", length: runningTime, 
    finished: (d, b) =>
  {
    if (associatedObject.Height.Equals(0))
    {
      associatedObject.IsVisible = false;
    }
  });
}

If the popup should be displayed (triggered by the change on the IsPopupDisplayed property) it first sets the popup’s visibility to true (although it’s still effectively invisible, as it has zero height) and then it launches an animation that goes from 0 to the unfoldingHeight in about 750ms. The Animate method then creates the actual animation, that animates the HeightRequest using and easing method (also a familiar friend from our good old Storyboards). By calling the Commit on it, you actually launch the animation, and you can apparently also make some kind of callback running when the animation is finished – in this case, when it determines the height is 0 (thus the popup has been folding in) – it should make it invisible again. Take note that the folding back goes a bit faster than the unfolding – 500ms in stead of 750ms. They could be the same, of course. It’s just a nice touch.

Putting it all together
In Xamarin Forms XAML, it then looks like this:

<Grid>

  <StackLayout HorizontalOptions="StartAndExpand" VerticalOptions="StartAndExpand">
    <Button Text="Open Popup" HorizontalOptions="Center" VerticalOptions="Start"
        IsEnabled="{Binding IsPopupVisible, Converter={StaticResource InverseBooleanConverter}}"
        Command="{Binding TogglePopup}"></Button>
    <Label Text="Here be some text" FontSize="25"></Label>
    <Label Text="Some more text" FontSize="20"></Label>
  </StackLayout>

  <ContentView BackgroundColor="Green" 
               HorizontalOptions="CenterAndExpand" 
               VerticalOptions="CenterAndExpand" >
    <ContentView.Behaviors >
      <behaviors:FoldingPaneBehavior 
        ViewHasAppeared ="{Binding ViewHasAppeared}" 
        IsPopupVisible="{Binding IsPopupVisible}"/>
    </ContentView.Behaviors>
    <StackLayout>
      <Label Text="Here is my popup" FontSize="20"></Label>
      <Button Text="Close popup" HorizontalOptions="Center"
     IsEnabled="{Binding IsPopupVisible}"
     Command="{Binding TogglePopup}"></Button>
    </StackLayout>
  </ContentView>
  
</Grid

imageimage

First you have the StackLayout containing the initial user interface, then the ContentView that contains the ugly green popup and the behavior. Notice the button on the initial UI gets disabled by binding to IsPopupVisible too, using a bog standard InverseBooleanConverter that I took from here. I think it’s actually the same as the WinRT value converter but I was too lazy to check ;)

Caveat emptor
Warning – you mileage may vary with this approach. I am not very versed in Xamarin Forms yet and I have no idea what amount of rules I am now violating. I find the hack in OnAttachedTo a bit worrisome, and I wonder if is has side effects. In addition, Xamarin Forms is a very rapidly evolving platform so anything I write today may be obsolete tomorrow. Also, this behavior does not yet take into account any changes in screen size (or rotation) that may occur after the behavior is initialized. I don’t doubt though this could be added.

Conclusion
With this article I hope I have given you some thought about how to write reusable x-plat animation blocks that can be wired together using behaviors. I still think it’s very important to keep such stuff from code behind as much as possible (although never loosing track of practicality – if some effect can be created using two lines of code behind, it’s not very smart to write a bazillion lines of code just to avoid that). What is sorely missed in this environment is my old friend Blend, which in a Windows environment can be used to drag behaviors on top of elements and wire some properties together in a simple UI. In Xamarin, it’s hand coding XAML all the way. Still, it’s an improvement over putting all this stuff in code behind.

As usual, a sample solution can be found on GitHub. This includes the snippet for creating Bindable Properties.

Credits and thanks
I want to thank two of my fellow developers colleagues at Wortell for being an inspiration to this blog post: Bruce Wilkins for showing me the two events that occur when a Xamarin Forms page loads and unloads, and Edwin van Manen for showing me the Xamarin Forms animation basics. They made me understand some parts that I needed to connect the dots and make this technique work.