Overlay menu
This example shows how to use Keyframe
with Scaling
and Translation
to make a set of wobbly buttons. The example was inspired by this awesome piece by Radek Skrzypczak, which is a design for a task list app.
The skull icon was created by Vincent Le Moign.
The arrow icon is from Google’s Material Icons pack.
This example has two main parts: the background task page and the animated overlay menu. The overlay menu is probably the most interesting part, so we’ll start with that.
Animating the menu
We place the “menu”-Panel
on top of the task page by placing the Panel
on top of the task Panel
as its sibling.
<DockPanel>
<Panel HitTestMode="None">
<Rectangle ux:Name="addTaskButtons" Layer="Background" Margin="0,0,0,80"/>
<DockPanel>
<StatusBarBackground Dock="Top"/>
<ux:Include File="AddTaskPage.ux" />
</DockPanel>
</Panel>
<Grid Rows="auto,auto,3*,2*,auto">
The buttons and their animation is defined in their own file and included in MainView.ux
using the <ux:Include />
syntax:
<ux:Include File="AddTaskPage.ux"/>
<Panel ux:Name="ellipse1" Alignment="Bottom" Width="60" Height="60" Opacity="0">
<Image File="Assets/skull.png" Margin="17"/>
<Circle Color="Red1">
<Stroke Color="Red3" Width="2" Offset="4" />
</Circle>
<Translation ux:Name="ellipseTrans1" />
<Scaling ux:Name="ellipseScaling1" Y="0.1" X="0.1"/>
</Panel>
<Text ux:Class="EllipseText" Alignment="Center" FontSize="26"/>
<Panel ux:Name="ellipse2" Alignment="Bottom" Width="60" Height="60" Opacity="0">
<EllipseText Value="!!!" Color="Orange1"/>
<Circle Color="White1"/>
<Translation ux:Name="ellipseTrans2" />
<Scaling ux:Name="ellipseScaling2" Y="0.1" X="0.1"/>
</Panel>
<Panel ux:Name="ellipse3" Alignment="Bottom" Width="60" Height="60" Opacity="0">
<EllipseText Value="!!" Color="Gray1"/>
<Circle Color="White1"/>
<Translation ux:Name="ellipseTrans3" />
<Scaling ux:Name="ellipseScaling3" Y="0.1" X="0.1"/>
</Panel>
<Panel ux:Name="ellipse4" Alignment="Bottom" Width="60" Height="60" Opacity="0">
<EllipseText Value="!" Color="Gray2"/>
<Circle Color="White1" />
<Translation ux:Name="ellipseTrans4" />
<Scaling ux:Name="ellipseScaling4" Y="0.1" X="0.1"/>
</Panel>
The buttons are created using a Panel
with a containing Circle
. Note that we do not actually use the Button
class in this case. They also each have their own Translation
and Scaling
, which we use to create their animation.
Their animation is triggered by toggling a WhileTrue
in response to a Clicked
event. Each button is animated individually by a Change
of their Scaling
and Transform
. They also have their Opacity
animated in the same way.
<Change Target="ellipseScaling1.Vector">
<Keyframe Time="0.1" Value="1.4,1.0"/>
<Keyframe Time="0.2" Value="0.8,1.0"/>
<Keyframe Time="0.3" Value="1.0,1.0"/>
</Change>
<Change ellipseTrans1.Y="-80" Duration="0.2" Delay="0" Easing="QuinticInOut"/>
<Change ellipse1.Opacity="1" Duration="0.3" Easing="QuinticOut"/>
<Change Target="ellipseScaling2.Vector">
<Keyframe Time="0.15" Value="1.0,1.4"/>
<Keyframe Time="0.3" Value="1.0,0.9"/>
<Keyframe Time="0.4" Value="1.0,1.0"/>
</Change>
<Change ellipseTrans2.Y="-160" Duration="0.25" Delay="0" Easing="QuinticInOut" />
<Change ellipse2.Opacity="1" Duration="0.4" Easing="QuinticOut"/>
<Change Target="ellipseScaling3.Vector">
<Keyframe Time="0.2" Value="1.0,1.6"/>
<Keyframe Time="0.3" Value="1.0,0.9"/>
<Keyframe Time="0.4" Value="1.0,1.0"/>
</Change>
<Change ellipseTrans3.Y="-240" Duration="0.3" Delay="0" Easing="QuinticInOut"/>
<Change ellipse3.Opacity="1" Duration="0.45" Easing="QuinticOut"/>
<Change Target="ellipseScaling4.Vector">
<Keyframe Time="0.25" Value="1.0,1.4"/>
<Keyframe Time="0.4" Value="1.0,0.9"/>
<Keyframe Time="0.5" Value="1.0,1.0"/>
</Change>
<Change ellipseTrans4.Y="-320" Duration="0.35" Delay="0" Easing="QuinticInOut"/>
<Change ellipse4.Opacity="1" Duration="0.5" Easing="QuinticOut"/>
The task page
The task page has no animation but is composed of a few building blocks. The main layout is a Grid
with five rows. The first row contain the StatusBarBackground
, which is used to compensate for the height of the status bar. The second row contains the top bar, with a title and a familiar hamburger icon (like them or not, they’re very common these days).
<Grid Columns="1*,1*,1*" Height="50" Margin="20,0">
<Grid Width="20" RowCount="2" Height="15" Alignment="Left">
<Rectangle Height="2" Color="White"/>
<Rectangle Height="2" Color="White"/>
</Grid>
<T Value="Today" Alignment="Center"/>
</Grid>
The next two rows contains the mock list of tasks:
<StackPanel ItemSpacing="20">
<JavaScript>
module.exports = { items: ['The first task', 'The second task', 'The third task'] };
</JavaScript>
<Each Items="{items}">
<Grid Columns="1*,auto" Margin="20,0">
<T Alignment="CenterLeft" Value="{}" />
<Rectangle Width="30" Height="30" Opacity="0.5">
<Stroke Color="Gray2" Width="2"/>
</Rectangle>
</Grid>
<Rectangle Height="1" Color="Gray2" Opacity="0.6"/>
</Each>
</StackPanel>
and the mock listing of the three task severities:
<Grid ColumnCount="3">
<Rectangle Color="Gray2" Height="2" Layer="Background" Y="70%"/>
<TaskCounter Severity="!!!" SeverityColor="#EF804F"
Count="3" Text="IMPORTANT" />
<TaskCounter Severity="!!" SeverityColor="#939393"
Count="2" Text="REALLY?" />
<TaskCounter Severity="!" SeverityColor="#DBDBDB"
Count="5" Text="MEH!" />
</Grid>
The TaskCounter
class is defined as follows, and was made just as a convenience.
<Grid ux:Class="TaskCounter" Rows="1*,3*,2*"
Margin="10,20" Padding="10" Color="White">
<string ux:Property="Severity" />
<float4 ux:Property="SeverityColor" />
<string ux:Property="Count" />
<string ux:Property="Text" />
<Panel Alignment="TopCenter" Y="-20" Background="White" Layer="Overlay">
<Text Value="{ReadProperty this.Severity}"
Color="{ReadProperty this.SeverityColor}"
FontSize="22" Margin="3,0"/>
</Panel>
<Rectangle Layer="Background">
<Stroke Width="2" Color="Gray2" />
</Rectangle>
<Text Value="{ReadProperty this.Text}" Alignment="Center" FontSize="10"/>
<Text Value="{ReadProperty this.Count}" FontSize="70" Alignment="Center"/>
<Image File="Assets/arrowForward.png" Color="Gray2" Width="20" Height="20"/>
</Grid>
And that’s it! Feel free to download the example code and play around.