Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit f6a3553

Browse files
pictosbijingtonTheCodeTravelerjfversluis
authored
Pre Built Animations (#1667)
* Add tada animation (#1468) * Base class implementation for pre-built animations * TADA! Added the first animation * Max and Min scales for Tada * Added the RubberBand animation Co-authored-by: Pedro Jesus <[email protected]> * Feature/animation samples (#1527) * Initial attempt at an Animations sample section * Move all responsibility to AnimationTypes * Code tidy up * changed the null and empty verification to be more performant * Inverted `if` to make the code more readable * An attempt at async with cancellation * Remove the need for Task.Delay * Removed the Type suffix * Code tidy up and xml docs Co-authored-by: Pedro Jesus <[email protected]> * Consolidate AnimationViewModel, Remove Unnecessary Nullables * TaskExtensions tidy up * AnimationWrapper tidy up Remove default parameters * Rename to CompoundAnimationBase * Unit tests added around animations * Reversed the >= check to keep the constant on the right * Switched to TimeSpan * Disabled/removed inconsistent tests * No longer share a ticker instance * Clear PlatformServices after each animation test * Code tidy up * Remove the inconsistent asserts * Further test ticker changes - only use AsyncTicker when really needed. - extra protection around enabling - set and clear default ticker before/after each test * Add Missing Asserts * Update Unit Test NuGet Packages * Update for Nullable Co-authored-by: Shaun Lawrence <[email protected]> Co-authored-by: Brandon Minnick <[email protected]> Co-authored-by: Gerald Versluis <[email protected]>
1 parent 6f9832c commit f6a3553

File tree

21 files changed

+762
-12
lines changed

21 files changed

+762
-12
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<pages:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:pages="clr-namespace:Xamarin.CommunityToolkit.Sample.Pages"
5+
xmlns:vm="clr-namespace:Xamarin.CommunityToolkit.Sample.ViewModels.Animations"
6+
x:Class="Xamarin.CommunityToolkit.Sample.Pages.Animations.AnimationPage"
7+
x:DataType="{x:Null}">
8+
<pages:BasePage.BindingContext>
9+
<vm:AnimationViewModel />
10+
</pages:BasePage.BindingContext>
11+
12+
<Grid RowDefinitions="*,*">
13+
<Label Text="Select an animation below and then tap start."
14+
HorizontalTextAlignment="Center"
15+
x:Name="Lab"/>
16+
17+
<StackLayout Grid.Row="1">
18+
<Picker ItemsSource="{Binding Animations}"
19+
SelectedItem="{Binding SelectedAnimation}"
20+
ItemDisplayBinding="{Binding Name}"/>
21+
22+
<Button Text="Start"
23+
Command="{Binding StartAnimationCommand}"
24+
CommandParameter="{x:Reference Name=Lab}"/>
25+
26+
<Button Text="Stop"
27+
Command="{Binding StopAnimationCommand}"/>
28+
</StackLayout>
29+
30+
31+
</Grid>
32+
</pages:BasePage>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Xamarin.CommunityToolkit.Sample.Pages.Animations
2+
{
3+
public partial class AnimationPage : BasePage
4+
{
5+
public AnimationPage()
6+
{
7+
InitializeComponent();
8+
}
9+
}
10+
}

samples/XCT.Sample/Pages/Behaviors/AnimationBehaviorPage.xaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@
1616
<Label Text="This sample demonstrates how to use ViewTappedAnimationBehaviour applying it in different UI elements."
1717
Padding="10,10,10,50"/>
1818

19+
<Button Text="Tada"
20+
FontSize="40"
21+
FontAttributes="Bold"
22+
TextColor="#2D1767"
23+
BackgroundColor="#F8E3C8">
24+
<Button.Behaviors>
25+
<xct:AnimationBehavior EventName="Clicked">
26+
<xct:AnimationBehavior.AnimationType>
27+
<xct:TadaAnimation Easing="{x:Static Easing.Linear}"
28+
IsRepeated="True"
29+
Duration="1000"/>
30+
</xct:AnimationBehavior.AnimationType>
31+
</xct:AnimationBehavior>
32+
</Button.Behaviors>
33+
</Button>
34+
1935
<Button Text="Testing Fade Animation"
2036
TextColor="HotPink">
2137
<Button.Behaviors>

samples/XCT.Sample/Pages/Behaviors/AnimationBehaviorPage.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
public partial class AnimationBehaviorPage : BasePage
44
{
55
public AnimationBehaviorPage() => InitializeComponent();
6-
}
6+
}
77
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using System.Collections.ObjectModel;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Xamarin.CommunityToolkit.Behaviors;
7+
using Xamarin.CommunityToolkit.ObjectModel;
8+
using Xamarin.Forms;
9+
10+
namespace Xamarin.CommunityToolkit.Sample.ViewModels.Animations
11+
{
12+
public class AnimationViewModel : BaseViewModel
13+
{
14+
AnimationDetailModel selectedAnimation;
15+
CompoundAnimationBase? currentAnimation;
16+
CancellationTokenSource? cancellationTokenSource;
17+
18+
public AnimationViewModel()
19+
{
20+
Animations = new ObservableCollection<AnimationDetailModel>
21+
{
22+
new AnimationDetailModel("Tada", (view, onFinished) => new TadaAnimation()),
23+
new AnimationDetailModel("RubberBand", (view, onFinished) => new RubberBandAnimation())
24+
};
25+
26+
selectedAnimation = Animations.First();
27+
StartAnimationCommand = new AsyncCommand<View>(v => OnStart(v), _ => SelectedAnimation is not null);
28+
StopAnimationCommand = new Command(OnStop, () => cancellationTokenSource is not null);
29+
}
30+
31+
public ObservableCollection<AnimationDetailModel> Animations { get; }
32+
33+
public AsyncCommand<View> StartAnimationCommand { get; }
34+
35+
public Command StopAnimationCommand { get; }
36+
37+
public AnimationDetailModel SelectedAnimation
38+
{
39+
get => selectedAnimation;
40+
set => SetProperty(ref selectedAnimation, value);
41+
}
42+
43+
async Task OnStart(View? view)
44+
{
45+
if (view is null)
46+
{
47+
return;
48+
}
49+
50+
if (cancellationTokenSource != null)
51+
{
52+
cancellationTokenSource.Cancel();
53+
}
54+
55+
currentAnimation = SelectedAnimation.CreateAnimation(view, (d, b) =>
56+
{
57+
StartAnimationCommand.ChangeCanExecute();
58+
StopAnimationCommand.ChangeCanExecute();
59+
});
60+
61+
cancellationTokenSource = new CancellationTokenSource();
62+
StopAnimationCommand.ChangeCanExecute();
63+
64+
await currentAnimation.Animate(cancellationTokenSource.Token, view);
65+
66+
cancellationTokenSource = null;
67+
StopAnimationCommand.ChangeCanExecute();
68+
}
69+
70+
void OnStop()
71+
{
72+
if (cancellationTokenSource != null)
73+
{
74+
cancellationTokenSource.Cancel();
75+
}
76+
}
77+
}
78+
79+
public class AnimationDetailModel
80+
{
81+
public string Name { get; }
82+
83+
public Func<View, Action<double, bool>, CompoundAnimationBase> CreateAnimation { get; }
84+
85+
public AnimationDetailModel(string name, Func<View, Action<double, bool>, CompoundAnimationBase> createAnimation)
86+
{
87+
Name = name;
88+
CreateAnimation = createAnimation;
89+
}
90+
}
91+
}
92+

samples/XCT.Sample/ViewModels/WelcomeViewModel.cs renamed to samples/XCT.Sample/ViewModels/Views/Tabs/WelcomeViewModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Generic;
22
using Xamarin.CommunityToolkit.Sample.Models;
3+
using Xamarin.CommunityToolkit.Sample.Pages.Animations;
34
using Xamarin.CommunityToolkit.Sample.Pages.Behaviors;
45
using Xamarin.CommunityToolkit.Sample.Pages.Converters;
56
using Xamarin.CommunityToolkit.Sample.Pages.Effects;
@@ -14,6 +15,9 @@ public class WelcomeViewModel : BaseGalleryViewModel
1415
{
1516
protected override IEnumerable<SectionModel> CreateItems() => new[]
1617
{
18+
new SectionModel(typeof(AnimationPage), "Animations", Color.FromHex("#41337A"),
19+
"A set of pre-built animations to give your app a real edge."),
20+
1721
new SectionModel(typeof(BehaviorsGalleryPage), "Behaviors", Color.FromHex("#8E8CD8"),
1822
"Behaviors lets you add functionality to user interface controls without having to subclass them. Behaviors are written in code and added to controls in XAML or code"),
1923

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using NUnit.Framework;
6+
using Xamarin.CommunityToolkit.Behaviors;
7+
using Xamarin.CommunityToolkit.UnitTests.Mocks;
8+
using Xamarin.Forms;
9+
using Xamarin.Forms.Internals;
10+
11+
namespace Xamarin.CommunityToolkit.UnitTests.Behaviors.Animations.AnimationTypes
12+
{
13+
public class RubberBandAnimation_Tests
14+
{
15+
[SetUp]
16+
public void SetUp()
17+
{
18+
Device.PlatformServices = new MockPlatformServices();
19+
Ticker.SetDefault(new AsyncTicker(TimeSpan.FromMilliseconds(16)));
20+
}
21+
22+
[TearDown]
23+
public void TearDown()
24+
{
25+
Device.PlatformServices = null;
26+
Ticker.SetDefault(null);
27+
}
28+
29+
[Test]
30+
public async Task AFullAnimationShouldReturnToOriginalValues()
31+
{
32+
var animation = new RubberBandAnimation();
33+
var view = new MockView();
34+
35+
await animation.Animate(view);
36+
37+
Assert.IsTrue(view.ValuesSet.ContainsKey(nameof(View.ScaleX)));
38+
Assert.AreEqual(1, view.ValuesSet[nameof(View.ScaleX)].Last());
39+
40+
Assert.IsTrue(view.ValuesSet.ContainsKey(nameof(View.ScaleY)));
41+
Assert.AreEqual(1, view.ValuesSet[nameof(View.ScaleY)].Last());
42+
}
43+
44+
[Test]
45+
public async Task AnAbortedAnimationShouldNotReturnToOriginalValues()
46+
{
47+
var animation = new RubberBandAnimation();
48+
var view = new MockView();
49+
var cancellationTokenSource = new CancellationTokenSource(100);
50+
51+
await animation.Animate(cancellationTokenSource.Token, view);
52+
53+
Assert.IsTrue(view.ValuesSet.ContainsKey(nameof(View.ScaleX)));
54+
Assert.AreNotEqual(1, view.ValuesSet[nameof(View.ScaleX)].Last());
55+
56+
Assert.IsTrue(view.ValuesSet.ContainsKey(nameof(View.ScaleY)));
57+
Assert.AreNotEqual(1, view.ValuesSet[nameof(View.ScaleY)].Last());
58+
}
59+
}
60+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using NUnit.Framework;
6+
using Xamarin.CommunityToolkit.Behaviors;
7+
using Xamarin.CommunityToolkit.UnitTests.Mocks;
8+
using Xamarin.Forms;
9+
using Xamarin.Forms.Internals;
10+
11+
namespace Xamarin.CommunityToolkit.UnitTests.Behaviors.Animations.AnimationTypes
12+
{
13+
public class TadaAnimation_Tests
14+
{
15+
[SetUp]
16+
public void SetUp()
17+
{
18+
Device.PlatformServices = new MockPlatformServices();
19+
Ticker.SetDefault(new AsyncTicker(TimeSpan.FromMilliseconds(16)));
20+
}
21+
22+
[TearDown]
23+
public void TearDown()
24+
{
25+
Device.PlatformServices = null;
26+
Ticker.SetDefault(null);
27+
}
28+
29+
[Test]
30+
public async Task AFullAnimationShouldReturnToOriginalValues()
31+
{
32+
var animation = new TadaAnimation();
33+
var view = new MockView();
34+
35+
await animation.Animate(view);
36+
37+
Assert.IsTrue(view.ValuesSet.ContainsKey(nameof(View.Scale)));
38+
Assert.AreEqual(1, view.ValuesSet[nameof(View.Scale)].Last());
39+
40+
Assert.IsTrue(view.ValuesSet.ContainsKey(nameof(View.Rotation)));
41+
Assert.AreEqual(0, view.ValuesSet[nameof(View.Rotation)].Last());
42+
}
43+
44+
[Test]
45+
public async Task AnAbortedAnimationShouldNotReturnToOriginalValues()
46+
{
47+
var animation = new TadaAnimation();
48+
var view = new MockView();
49+
var cancellationTokenSource = new CancellationTokenSource(100);
50+
51+
await animation.Animate(cancellationTokenSource.Token, view);
52+
53+
Assert.IsTrue(view.ValuesSet.ContainsKey(nameof(View.Scale)));
54+
Assert.AreNotEqual(1, view.ValuesSet[nameof(View.Scale)].Last());
55+
56+
Assert.IsTrue(view.ValuesSet.ContainsKey(nameof(View.Rotation)));
57+
Assert.AreNotEqual(0, view.ValuesSet[nameof(View.Rotation)].Last());
58+
}
59+
}
60+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System;
2+
using NUnit.Framework;
3+
using Xamarin.CommunityToolkit.Behaviors;
4+
using Xamarin.CommunityToolkit.UnitTests.Mocks;
5+
using Xamarin.Forms;
6+
using Xamarin.Forms.Internals;
7+
8+
namespace Xamarin.CommunityToolkit.UnitTests.Behaviors.Animations
9+
{
10+
public class AnimationWrapper_Tests
11+
{
12+
[SetUp]
13+
public void SetUp()
14+
{
15+
Device.PlatformServices = new MockPlatformServices();
16+
Ticker.SetDefault(new AsyncTicker(TimeSpan.FromMilliseconds(16)));
17+
}
18+
19+
[TearDown]
20+
public void TearDown()
21+
{
22+
Device.PlatformServices = null;
23+
Ticker.SetDefault(null);
24+
}
25+
26+
[Test]
27+
public void AbortShouldStopAnimationRunning()
28+
{
29+
var animation = new Animation
30+
{
31+
{ 0, 1, new Animation(d => { }) }
32+
};
33+
34+
var animationWrapper = new AnimationWrapper(
35+
animation,
36+
Guid.NewGuid().ToString(),
37+
new BoxView(),
38+
16,
39+
100,
40+
Easing.Linear,
41+
(v, t) => { },
42+
() => false);
43+
44+
Assert.IsFalse(animationWrapper.IsRunning);
45+
46+
animationWrapper.Commit();
47+
animationWrapper.Abort();
48+
49+
Assert.IsFalse(animationWrapper.IsRunning);
50+
}
51+
52+
[Test]
53+
public void IsRunningShouldReportStatus()
54+
{
55+
var animation = new Animation
56+
{
57+
{ 0, 1, new Animation(d => { }) }
58+
};
59+
60+
var animationWrapper = new AnimationWrapper(
61+
animation,
62+
Guid.NewGuid().ToString(),
63+
new BoxView(),
64+
16,
65+
100,
66+
Easing.Linear,
67+
(v, t) => { },
68+
() => false);
69+
70+
Assert.IsFalse(animationWrapper.IsRunning);
71+
72+
animationWrapper.Commit();
73+
74+
Assert.IsTrue(animationWrapper.IsRunning);
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)