Skip to main content

Working with Progressive Animations

Overview

Grial UI Kit comes with a XAML framework to animate anything based on a numeric value that changes over time, for example as a a the result of a user interaction.

This is where the progressive nature of these animations comes from.

The most common example of a numeric value that changes is the scroll position. We let you change the value of any visual element property as the user scrolls through your page. This technique is used in ParallaxHeaderArticlePage.xaml to implement a parallax scrolling effect, where the the header image movement is “slower” than the scroll movement creating an illusion of depth.

This framework is 100% XAML so you can immediately preview your animations with a XAML previewer such as Gorilla Player. Why is that important? Because fine-tuning animations usually takes many iterations and waiting for the compiler to try out each small tweak would make that very hard.

Basic parallax example

This example shows how to make an image move up in a slower pace than the page scroll speed.

The top element of our page will be a .NET MAUI ScrollView. So, first of all, let’s associate a Grial’s ScrollViewScrollBehavior to that ScrollView to expose the scroll value that is not available out of the box.

<ScrollView>
<VisualElement.Behaviors>
<grial:ScrollViewScrollBehavior x:Name="scroll" />
</VisualElement.Behaviors>

<!-- PAGE CONTENT -->

</ScrollView>

Now, we have an element called “scroll” that has several properties related to the scroll position. In this example we will only use AbsoluteScrollY property to refer to the absolute value of the vertical scroll.

Let’s say the content of our ScrollView has this simple structure:

<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="240" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<!-- HEADER IMAGE -->

<Image
Source="header.png"
Aspect="AspectFill"
HorizontalOptions="FillAndExpand">
</Image>

<VerticalStackLayout
Grid.Row="1"
BackgroundColor="Gray"
HeightRequest="1000">

<!-- BODY CONTENT -->

</VerticalStackLayout>
</Grid>

You can see an image as header as the topmost element and a tall content body (simulated as 1000px height gray area here).

At this moment, if you scroll the page the image will move up normally along with the rest of the content. Let’s see how we achieve a slower vertical movement by adding one more behavior, this time to the Image itself:

<Image
Source="header.png"
Aspect="AspectFill"
HorizontalOptions="Fill">
<VisualElement.Behaviors>
<grial:AnimatedTranslationY
Progress="{Binding AbsoluteScrollY, Source={x:Reference scroll}}"
ProgressStart="0"
ProgressEnd="240"
Start="0"
End="120"
/>
</VisualElement.Behaviors>
</Image>

Let’s explain what the AnimatedTranslationY behavior is all about. As the name suggests, it controls the TralationY property of the associated element. All these animated behaviors have 5 basic properties:

  • Progress: the numeric value that controls the animation (in this case the vertical scroll value in pixels)
  • ProgressStart: the Progress value where the animation starts
  • ProgressEnd: the Progress value where the animation ends (in this case it ends when the scroll matches the image row height because at that moment the image will no longer be visible)
  • Start: the target value of TranslationY when Progress == ProgressStart (in this case 0, as we don’t want the image to be translated initially)
  • End: the target value of TranslationY when Progress == ProgressEnd (in this case half of the image row height simulating half the scroll speed)

These 5 properties are how all progressive animations work so once you understand them you will be ready to animate anything.

More on animated properties behaviors

Animated properties are implemented as VisualElement behaviors. They can animate any bindable property of type double or Color. As explained above, you can control them through Progress, ProgressStart, ProgressEnd, Start and End.

Grial interpolates Progress (starting in ProgressStart and ending in ProgressEnd) into the interval defined by Start and End updating the target element property value with the result.

Start and End define the target value space so they have the same type than the target property, in the case of Color they are colors, in the case of double properties they are doubles.

Additionally, you can use the Easing property to change the interpolation function from linear to one of the available functions in .NET MAUI.

The last useful property is called IsSymmetric. In most cases, Grial considers Progress values between ProgressStart and ProgressEnd, but, if you have a special scenario where ProgressStart == 0 and need to consider negative values too, you can set IsSymmetric="true". By doing so, when Progress is in the interval (0, -ProgressEnd] Grial will apply the interpolation to make it symmetric around 0.

Color properties

There are two specific behaviors to animate specific color properties:

  • AnimatedBackgroundColor animates the generic BackgroundColor on any VisualElement
  • AnimatedTextColor animates the TextColor on common .NET MAUI elements like Label, Button, Entry, Editor, SearchBar, Picker and DatePicker

You can also animate any bindable color property using AnimatedCustomColorProperty.

As an example, this is how you animate the border color of a Border from Transparent to Black when scroll reaches 100.

<Border Margin="50">
<VisualElement.Behaviors>
<grial:AnimatedCustomColorProperty
Progress="{ Binding AbsoluteScrollY, Source={x:Reference scroll} }"
ProgressStart="0"
ProgressEnd="100"
Start="Transparent"
End="Black"
TargetProperty="{ x:Static Border.StrokeProperty }"
/>
</VisualElement.Behaviors>
</Border>

Double properties

There are seven specific behaviors to animate specific double properties:

  • AnimatedOpacity animates the Opacity on any VisualElement
  • AnimatedRotation animates the rotation transformation on any VisualElement
  • AnimatedRotationX animates the rotation transformation on the horizontal direction on any VisualElement
  • AnimatedRotationY animates the rotation transformation on the vertical direction on any VisualElement
  • AnimatedTranslationX animates the horizontal translation transformation on any VisualElement
  • AnimatedTranslationY animates the vertical translation transformation on any VisualElement
  • AnimatedScale animates the scale transformation on any VisualElement
  • AnimatedFontSize animates the FontSize on common .NET MAUI elements like Label, Button, Entry, Editor, SearchBar, Picker and DatePicker

You can also animate any bindable color property using AnimatedCustomDoubleProperty.

As an example, this is how you animate the corner radius of a BoxView from 0 to 40 when scroll reaches 100.

<BoxView Margin="50" Color="Red">
<VisualElement.Behaviors>
<grial:AnimatedCustomDoubleProperty
Progress="{ Binding AbsoluteScrollY, Source={x:Reference scroll} }"
ProgressStart="0"
ProgressEnd="100"
Start="0"
End="40"
TargetProperty="{ x:Static BoxView.CornerRadiusProperty }"
/>
</VisualElement.Behaviors>
</BoxView>

When Start or End comes from a binding or a resource you want to reuse (like a threshold) instead of an inline constant value, you may need to adjust them. For that end double animations have MultiplyValue, StartAddValue and EndAddValue properties. For instance, if you want the End to be half the actual height of an target element you can do this:

<grial:AnimatedTranslationY
Progress="{ Binding AbsoluteScrollY, Source={ x:Reference scroll } }"
ProgressStart="0"
ProgressEnd="{ Binding Height, Source={x:Reference targetElement} }"
MultiplyValue="0.5"
Start="0"
End="{ Binding Height, Source={x:Reference targetElement} }"
/>

Boolean properties and triggers

Up until now we talked about doubles and colors, types that by nature support interpolation due to the continuous nature of their value domains. We will now introduce the last two type of behaviors: booleans and triggers.

Booleans

A common scenario for manipulating a boolean property is when you need to show/hide something after a certain scroll threshold. To do that you need to affect the IsVisible boolean property,

Since boolean properties can only hold 2 values (true and false), there is no way to interpolate as before, so, instead of having ProgressStart, ProgressEnd, Start and End we have:

  • Threshold that indicates in what value of Progress the boolean value needs to change
  • ValueBeforeThreshold that defines if the result is true or false when Progress is less than Threshold (false by default)

For instance, this is how you show a button after a the scroll reaches 100:

<Button>
<VisualElement.Behaviors>
<grial:AnimatedCustomBooleanProperty
Progress="{ Binding AbsoluteScrollY, Source={x:Reference scroll} }"
Threshold="100"
TargetProperty="{ x:Static VisualElement.IsVisibleProperty }"
/>
</VisualElement.Behaviors>
</Button>

Or in a simpler way, using the specific AnimatedIsVisible class:

<Button>
<VisualElement.Behaviors>
<grial:AnimatedIsVisible
Progress="{ Binding AbsoluteScrollY, Source={x:Reference scroll} }"
Threshold="100"
/>
</VisualElement.Behaviors>
</Button>

The specific animated boolean classes are: AnimatedIsVisible, AnimatedIsEnabled and AnimatedInputTransparent. Any other boolean property can be handled through the generic AnimatedCustomBooleanProperty as shown before.

Triggers

AnimatedTrigger is a behavior that will invoke an action once the backing value reaches a specific threshold. It has the following properties:

  • Threshold that indicates in what value of Progress where the trigger should be triggered
  • TriggerBeforeThreshold that defines whether to trigger before or after reaching the Threshold (false by default)
  • Action of type IAnimatedAction, an interface that define an Execute method that can be implemented to trigger a non-progressive animation, a validation, or any other piece of code

ScrollView behavior and ExtendedCarouselView

We’ve used the ScrollViewScrollBehavior in the previous examples. It extends the ScrollView by exposing these properties that are useful when animating:

  • ViewportHeight is the viewport height
  • ViewportWidth is the viewport width
  • RelativeScrollX is the horizontal scroll value between 0 and 1
  • RelativeScrollY is the vertical scroll value between 0 and 1
  • AbsoluteScrollX is the horizontal scroll value in pixels
  • AbsoluteScrollY is the vertical scroll value in pixels

The other control used in Grial that supports progressive animations out-of-the-box is the ExtendedCarouselView which is an extension of the .NET MAUI control.

It adds these properties:

  • ScrollProgress the progress between -1 and 1
  • CurrentItem the data source of the currently selected item
  • NextItem the data source of the next item that is going to be selected as a result of the user interaction

CurrentItem and NextItem are useful to use specific properties of each carousel item data source as part of an animation. An example of this can be found in FeaturedMoviesPage.xaml.