This guest post was contributed by Slava Chernikoff, a Principal Engineer at Binwell. Microsoft MVP and Xamarin Certified experienced mobile cross-platform and native developer. Also, a previously honored Nokia Champion and Qt Certified Developer.
Introduction
If you have been a student of a technical specialty, you will surely remember the course devoted to the Finite State Machines.
This simple, but very capacious model of finite state machine (a.k.a. FSM) is used widely, although most programmers have unfortunately forgotten about it. Today we will talk about finite state machines and their application in complex animations for Xamarin.Forms apps.
If you already use Xamarin.Forms in real projects, you probably saw a built-in animation engine. If not, then we recommend you start with the articles “Creating Animations with Xamarin.Forms” and “Compound Animations”.
Modifying Properties
Most often, you need to animate the following properties:
- Scale of the element
- Opacity transparency
- Translation additional offset x, y, relative to the position obtained in the layout
- Rotation rotation around the x, y, z-axes
There are low-level iOS/Android mechanisms used in Xamarin.Forms to modify these properties which have a great effect on performance, so there is no problem animating a lot of controls at once.
In our example we will focus on the set of properties shown before, however you can expand the mechanisms by yourself for supporting BackgroundColor property, for example.
Finite State Machines
A Finite State Machine is an object that can be in different stable states (i.e. “download” or “error”). The machine changes its states under the influence of external events. The number of states is finite. Examples of such machines are elevators and traffic lights.
How do finite state machines relate to animations and especially to Xamarin.Forms? Let’s see.
One Screen, Many States, and Animated Transitions
To learn more about page states it is suggested to start with StateContainer component by Patrick McCurley. This control simplifies the development of complex user interfaces and is suitable for most screens in business applications.
This component works well when all states exist independently of each other and there is a simple transition between them, “one disappeared — the second appeared”.
But what if you need to implement a complex and animated transition from one state to another? As an example, let’s look at the screen for entering addresses and working with the map. It is implemented in navigators or taxi services.
Imagine that we have animated transitions between the following states of ONE screen:
As shown, we have such a finite state machine that shown on Figure 4.
It is necessary to implement the following animations when changing from state to state:
- Entering FindAddress state, will hide the old content with the animation and smoothly display the new one. Also, it will animate the buttons at the time of appearance.
- Switching to ShowRoute, it is necessary to hide the old state and a route information component will appear from the bottom.
- Switching to Drive, it is necessary to hide the old state and display route information on the top of the screen.
- When going to Main (except for the first run), hide the current state and smoothly display the button with a small zoom animation.
Here you can find a demo video.
Write Your FSM
Take the simplest implementation of FSM:
- The machine has a fixed set of states that can be specified at initialization – Method Add.
- Each state is described by a set of necessary animations, or final properties values, for UI elements (buttons, labels, etc.).
- When it enters to a new state, all animations that were added at the initialization of the machine are started in parallel.
We will not store any transition history. It also doesn’t matter which user event triggered the transition from one state to another. There is only a transition to a new state – the Go method, which is accompanied by animations.
So, the simplest FSM, called Storyboard, will look like this:
public enum AnimationType {
Scale,
Opacity,
TranslationX,
TranslationY,
Rotation
}
public class Storyboard {
readonly Dictionary<string, ViewTransition[]> _stateTransitions = new Dictionary<string, ViewTransition[]>();
public void Add(object state, ViewTransition[] viewTransitions) {
var stateStr = state?.ToString().ToUpperInvariant();
_stateTransitions.Add(stateStr, viewTransitions);
}
public void Go(object newState, bool withAnimation = true) {
var newStateStr = newState?.ToString().ToUpperInvariant();
// Get all ViewTransitions
var viewTransitions = _stateTransitions[newStateStr];
// Get transition tasks
var tasks = viewTransitions.Select(viewTransition => viewTransition.GetTransition(withAnimation));
// Run all transition tasks
Task.WhenAll(tasks);
}
}
public class ViewTransition {
// Some trivial code was skipped. See complete sample in repository below
public async Task GetTransition(bool withAnimation) {
VisualElement targetElement;
if( !_targetElementReference.TryGetTarget(out targetElement) )
throw new ObjectDisposedException(nameof(targetElement), "Target VisualElement was disposed");
if( _delay > 0 )
await Task.Delay(_delay);
withAnimation &= _length > 0;
switch ( _animationType ) {
case AnimationType.Scale:
if( withAnimation )
await targetElement.ScaleTo(_endValue, _length, _easing);
else
targetElement.Scale = _endValue;
break;
// See complete sample in repository below
default:
throw new ArgumentOutOfRangeException();
}
}
}
In the example above, the input data checks are omitted. A full version of the example can be found in the repository.
As you can see, when you move to a new state, only the necessary smooth changes are started in parallel. There is also the possibility to enter a new state without animation.
Using Finite State Machine
So, we have a machine and can connect it to set the necessary states of UI elements. Example of adding a new state:
_storyboard.Add(States.Drive, new[] {
new ViewTransition(ShowRouteView, AnimationType.TranslationY, 200),
new ViewTransition(ShowRouteView, AnimationType.Opacity, 0, 0, delay: 250),
new ViewTransition(DriveView, AnimationType.TranslationY, 0, 300, delay: 250), // Active and visible
new ViewTransition(DriveView, AnimationType.Opacity, 1, 0) // Active and visible
});
For the Drive state, set an array of individual animations. ShowRouteView and DriveView are the usual Views, specified in XAML displayed on the example below.
To go to a new state it is enough to call the Go() method:
_storyboard.Go(States.Drive);
The code is relatively small and group animations are created by a simple set of numbers. Our state machine can work not only with pages, but also with individual View which expands the variants of its application. It is better to use Storyboard inside the page’s C# code behind(Page.cs) and not mix it with business logic (ViewModels).
Here is an example of XAML, which describes all the elements of the user interface.
Conclusion
This article describes the Finite State Machine model and its application for complex page state transitions. If you decide to add the ability to change the color of items with animations, we recommend the article “Building Custom Animations in Xamarin. Forms”.
The full code of the demo project can be found in this repository.
Hi Slava,
Thank you for the great tutorial. I want to ask about one part. You say "It is better to use Storyboard inside the page’s C# code behind(Page.cs) and not mix it with business logic (ViewModels)." Given that I have my view bound to commands in my ViewModel, can you recommend an approach where I could call _storyboard.Go(...) in the page when my commands in the ViewModel are actually what's being called?
Thanks!