Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.
Closed
32 changes: 32 additions & 0 deletions samples/XCT.Sample/Pages/Animations/AnimationPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<pages:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Xamarin.CommunityToolkit.Sample.Pages"
xmlns:vm="clr-namespace:Xamarin.CommunityToolkit.Sample.ViewModels.Animations"
x:Class="Xamarin.CommunityToolkit.Sample.Pages.Animations.AnimationPage"
x:DataType="{x:Null}">
<pages:BasePage.BindingContext>
<vm:AnimationViewModel />
</pages:BasePage.BindingContext>

<Grid RowDefinitions="*,*">
<Label Text="Select an animation below and then tap start."
HorizontalTextAlignment="Center"
x:Name="Lab"/>

<StackLayout Grid.Row="1">
<Picker ItemsSource="{Binding Animations}"
SelectedItem="{Binding SelectedAnimation}"
ItemDisplayBinding="{Binding Name}"/>

<Button Text="Start"
Command="{Binding StartAnimationCommand}"
CommandParameter="{x:Reference Name=Lab}"/>

<Button Text="Stop"
Command="{Binding StopAnimationCommand}"/>
</StackLayout>


</Grid>
</pages:BasePage>
10 changes: 10 additions & 0 deletions samples/XCT.Sample/Pages/Animations/AnimationPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Xamarin.CommunityToolkit.Sample.Pages.Animations
{
public partial class AnimationPage : BasePage
{
public AnimationPage()
{
InitializeComponent();
}
}
}
11 changes: 3 additions & 8 deletions samples/XCT.Sample/Pages/Behaviors/AnimationBehaviorPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@
Spacing="10">

<Label Text="This sample demonstrates how to use ViewTappedAnimationBehaviour applying it in different UI elements."
Padding="10,10,10,50"
BackgroundColor="Red">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" />
</Label.GestureRecognizers>
</Label>
Padding="10,10,10,50"/>

<Button Text="Tada"
FontSize="40"
Expand All @@ -30,8 +25,8 @@
<xct:AnimationBehavior EventName="Clicked">
<xct:AnimationBehavior.AnimationType>
<xct:TadaAnimationType Easing="{x:Static Easing.Linear}"
Duration="1000"
RotationAngle="100"/>
IsRepeated="True"
Duration="1000"/>
</xct:AnimationBehavior.AnimationType>
</xct:AnimationBehavior>
</Button.Behaviors>
Expand Down
15 changes: 1 addition & 14 deletions samples/XCT.Sample/Pages/Behaviors/AnimationBehaviorPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
using Xamarin.CommunityToolkit.Animations;
using Xamarin.Forms;

namespace Xamarin.CommunityToolkit.Sample.Pages.Behaviors
namespace Xamarin.CommunityToolkit.Sample.Pages.Behaviors
{
public partial class AnimationBehaviorPage : BasePage
{
public AnimationBehaviorPage() => InitializeComponent();

private async void TapGestureRecognizer_Tapped(object sender, System.EventArgs e)
{
var label = (View)sender;

new TadaAnimation(
rotationAngle: 30,
length: 1000,
views: label).Commit();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using Xamarin.CommunityToolkit.Behaviors;
using Xamarin.Forms;

namespace Xamarin.CommunityToolkit.Sample.ViewModels.Animations
{
public class AnimationDetailViewModel : BaseViewModel
{
public string Name { get; }
public Func<View, Action<double, bool>, AnimationWrapper> CreateAnimation { get; }

public AnimationDetailViewModel(string name, Func<View, Action<double, bool>, AnimationWrapper> createAnimation)
{
Name = name;
CreateAnimation = createAnimation;
}
}
}
64 changes: 64 additions & 0 deletions samples/XCT.Sample/ViewModels/Animations/AnimationViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.ObjectModel;
using System.Linq;
using Xamarin.CommunityToolkit.Behaviors;
using Xamarin.Forms;

namespace Xamarin.CommunityToolkit.Sample.ViewModels.Animations
{
public class AnimationViewModel : BaseViewModel
{
AnimationWrapper? currentAnimation;
AnimationDetailViewModel? selectedAnimation;

public ObservableCollection<AnimationDetailViewModel> Animations { get; }

public AnimationDetailViewModel? SelectedAnimation
{
get => selectedAnimation;
set => SetProperty(ref selectedAnimation, value);
}

public Command StartAnimationCommand { get; }
public Command StopAnimationCommand { get; }

public AnimationViewModel()
{
Animations = new ObservableCollection<AnimationDetailViewModel>
{
new AnimationDetailViewModel("Tada", (view, onFinished) => new TadaAnimationType().CreateAnimation(onFinished: onFinished, views: view)),
//new AnimationDetailViewModel("RubberBand", (view, onFinished) => new RubberBandAnimation(onFinished: onFinished, views: view))
};

SelectedAnimation = Animations.First();
StartAnimationCommand = new Command<View>(OnStart, (view) => !(SelectedAnimation is null) && currentAnimation?.IsRunning != true);
StopAnimationCommand = new Command(OnStop, () => currentAnimation?.IsRunning == true);
}

void OnStart(View view)
{
if (currentAnimation != null)
{
currentAnimation.Abort();
}

currentAnimation = SelectedAnimation!.CreateAnimation(view, (d, b) =>
{
StartAnimationCommand.ChangeCanExecute();
StopAnimationCommand.ChangeCanExecute();
});

currentAnimation.Commit();

StopAnimationCommand.ChangeCanExecute();
}

void OnStop()
{
if (currentAnimation != null)
{
currentAnimation.Abort();
}
}
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Xamarin.CommunityToolkit.Sample.Models;
using Xamarin.CommunityToolkit.Sample.Pages.Animations;
using Xamarin.CommunityToolkit.Sample.Pages.Behaviors;
using Xamarin.CommunityToolkit.Sample.Pages.Converters;
using Xamarin.CommunityToolkit.Sample.Pages.Effects;
Expand All @@ -14,6 +15,9 @@ public class WelcomeViewModel : BaseGalleryViewModel
{
protected override IEnumerable<SectionModel> CreateItems() => new[]
{
new SectionModel(typeof(AnimationPage), "Animations", Color.FromHex("#41337A"),
"A set of pre-built animations to give your app a real edge."),

new SectionModel(typeof(BehaviorsGalleryPage), "Behaviors", Color.FromHex("#8E8CD8"),
"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"),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Xamarin.CommunityToolkit.Animations
{
public class TadaAnimation : AnimationBase
{
internal const uint DefaultLength = 500;
internal const uint DefaultLength = 1000;
internal const double DefaultMaximumScale = 1.1;
internal const double DefaultMinimumScale = 0.9;
internal const double DefaultRotationAngle = 3.0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Threading.Tasks;
using System;
using System.Linq;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace Xamarin.CommunityToolkit.Behaviors
Expand Down Expand Up @@ -32,6 +34,27 @@ static object GetDefaultDurationProperty(BindableObject bindable)
protected abstract uint DefaultDuration { get; set; }

public abstract Task Animate(TView? view);

// TODO: Wrap this (no pun intended) in another base class just for the pre-built types.
public AnimationWrapper CreateAnimation(
uint rate = 16,
Action<double, bool>? onFinished = null,
Func<bool>? shouldRepeat = null,
params View[] views) =>
new AnimationWrapper(
CreateAnimation(views),
Guid.NewGuid().ToString(),
views.First(),
rate,
Duration,
Easing,
onFinished,
shouldRepeat);

protected virtual Animation CreateAnimation(params View[] views)
{
return new Animation();
}
}

public abstract class AnimationBase : AnimationBase<View>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Threading.Tasks;
using Xamarin.CommunityToolkit.Animations;
using Xamarin.Forms;

namespace Xamarin.CommunityToolkit.Behaviors
Expand All @@ -9,14 +8,15 @@ public class TadaAnimationType : AnimationBase
public static readonly BindableProperty IsRepeatedProperty =
BindableProperty.Create(nameof(IsRepeated), typeof(bool), typeof(TadaAnimationType), default, BindingMode.TwoWay);

// TODO: RepeatingAnimationBase...
public bool IsRepeated
{
get => (bool)GetValue(IsRepeatedProperty);
set => SetValue(IsRepeatedProperty, value);
}

public static readonly BindableProperty MaximumScaleProperty =
BindableProperty.Create(nameof(MaximumScale), typeof(double), typeof(TadaAnimationType), TadaAnimation.DefaultMaximumScale, BindingMode.TwoWay);
BindableProperty.Create(nameof(MaximumScale), typeof(double), typeof(TadaAnimationType), 1.1, BindingMode.TwoWay);

public double MaximumScale
{
Expand All @@ -25,7 +25,7 @@ public double MaximumScale
}

public static readonly BindableProperty MinimumScaleProperty =
BindableProperty.Create(nameof(MinimumScale), typeof(double), typeof(TadaAnimationType), TadaAnimation.DefaultMinimumScale, BindingMode.TwoWay);
BindableProperty.Create(nameof(MinimumScale), typeof(double), typeof(TadaAnimationType), 0.9, BindingMode.TwoWay);

public double MinimumScale
{
Expand All @@ -34,33 +34,66 @@ public double MinimumScale
}

public static readonly BindableProperty RotationAngleProperty =
BindableProperty.Create(nameof(RotationAngle), typeof(double), typeof(TadaAnimationType), TadaAnimation.DefaultRotationAngle, BindingMode.TwoWay);
BindableProperty.Create(nameof(RotationAngle), typeof(double), typeof(TadaAnimationType), 3.0, BindingMode.TwoWay);

public double RotationAngle
{
get => (double)GetValue(RotationAngleProperty);
set => SetValue(RotationAngleProperty, value);
}

protected override uint DefaultDuration { get; set; } = TadaAnimation.DefaultLength;
protected override uint DefaultDuration { get; set; } = 1000;

public override Task Animate(View? view)
{
if (view != null)
{
var taskCompletionSource = new TaskCompletionSource<bool>();

new TadaAnimation(
rotationAngle: RotationAngle,
length: Duration,
CreateAnimation(
16,
onFinished: (v, c) =>
{
if (IsRepeated)
{
return;
}

taskCompletionSource.SetResult(c);
},
shouldRepeat: () => IsRepeated,
onFinished: (v, c) => taskCompletionSource.SetResult(c),
views: view).Commit();
view).Commit();

return taskCompletionSource.Task;
}

return Task.FromResult(false);
}

protected override Animation CreateAnimation(params View[] views) => Create(RotationAngle, MinimumScale, MaximumScale, views);

static Animation Create(double rotationAngle, double minimumScale, double maximumScale, params View[] views)
{
var animation = new Animation();

foreach (var view in views)
{
animation.Add(0, 0.1, new Animation(v => view.Scale = v, 1, minimumScale));
animation.Add(0.2, 0.3, new Animation(v => view.Scale = v, minimumScale, maximumScale));
animation.Add(0.9, 1.0, new Animation(v => view.Scale = v, maximumScale, 1));

animation.Add(0, 0.2, new Animation(v => view.Rotation = v, 0, -rotationAngle));
animation.Add(0.2, 0.3, new Animation(v => view.Rotation = v, -rotationAngle, rotationAngle));
animation.Add(0.3, 0.4, new Animation(v => view.Rotation = v, rotationAngle, -rotationAngle));
animation.Add(0.4, 0.5, new Animation(v => view.Rotation = v, -rotationAngle, rotationAngle));
animation.Add(0.5, 0.6, new Animation(v => view.Rotation = v, rotationAngle, -rotationAngle));
animation.Add(0.6, 0.7, new Animation(v => view.Rotation = v, -rotationAngle, rotationAngle));
animation.Add(0.7, 0.8, new Animation(v => view.Rotation = v, rotationAngle, -rotationAngle));
animation.Add(0.8, 0.9, new Animation(v => view.Rotation = v, -rotationAngle, rotationAngle));
animation.Add(0.9, 1.0, new Animation(v => view.Rotation = v, rotationAngle, 0));
}

return animation;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using Xamarin.Forms;

namespace Xamarin.CommunityToolkit.Behaviors
{
public class AnimationWrapper
{
readonly Easing? easing;
readonly uint length = 250;
readonly Action<double, bool>? onFinished;
readonly Animation animation;
readonly IAnimatable owner;
readonly uint rate = 16;
readonly Func<bool>? shouldRepeat;
readonly string name;

public AnimationWrapper(
Animation animation,
string name,
IAnimatable owner,
uint rate = 16,
uint length = 250,
Easing? easing = null,
Action<double, bool>? onFinished = null,
Func<bool>? shouldRepeat = null)
{
this.name = name + Guid.NewGuid().ToString();
this.length = length;
this.easing = easing;
this.onFinished = onFinished;
this.animation = animation;
this.owner = owner;
this.rate = rate;
this.shouldRepeat = shouldRepeat;
}

/// <summary>
/// Stops the animation.
/// </summary>
/// <returns>True if successful, false otherwise.</returns>
public bool Abort() => owner.AbortAnimation(name);

/// <summary>
/// Runs the animation.
/// </summary>
public void Commit() => animation.Commit(owner, name, rate, length, easing, onFinished, shouldRepeat);

/// <summary>
/// Gets a value indicating whether the animation is running.
/// </summary>
public bool IsRunning => owner.AnimationIsRunning(name);
}
}