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
whenProgress == ProgressStart
(in this case 0, as we don’t want the image to be translated initially) - End: the target value of
TranslationY
whenProgress == 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
andDatePicker
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
andDatePicker
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 thanThreshold
(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 anExecute
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
.