summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorE.Z. Hart <hartez@users.noreply.github.com>2016-04-08 12:26:03 -0600
committerJason Smith <jason.smith@xamarin.com>2016-04-08 15:04:34 -0700
commit3b63b226711fcc0e616d06e3f486f5bf2265761b (patch)
treeb47e375f5b234a188364649d8ce64904dc1032a0
parentad1c0537f57b870cf4b47dd43cfae0402d593c55 (diff)
downloadxamarin-forms-beta-2.2.0-pre2.tar.gz
xamarin-forms-beta-2.2.0-pre2.tar.bz2
xamarin-forms-beta-2.2.0-pre2.zip
Automatically marshal all AnimationExtensions calls onto UI thread (#48)beta-2.2.0-pre2
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39821.cs118
-rw-r--r--Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems1
-rw-r--r--Xamarin.Forms.Controls/App.cs6
-rw-r--r--Xamarin.Forms.Core/AnimationExtensions.cs155
4 files changed, 217 insertions, 63 deletions
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39821.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39821.cs
new file mode 100644
index 00000000..ab998d0a
--- /dev/null
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla39821.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Xamarin.Forms.CustomAttributes;
+
+#if UITEST
+using Xamarin.UITest;
+using NUnit.Framework;
+#endif
+
+namespace Xamarin.Forms.Controls.Issues
+{
+ [Preserve(AllMembers = true)]
+ [Issue(IssueTracker.Bugzilla, 39821, "ViewExtension.TranslateTo cannot be invoked on Main thread")]
+ public class Bugzilla39821 : TestContentPage
+ {
+ protected override void Init()
+ {
+ var box = new BoxView { BackgroundColor = Color.Blue, WidthRequest = 50, HeightRequest = 50, HorizontalOptions = LayoutOptions.Center };
+
+ var instructions = new Label { Text = "Click the 'Animate' button to run animation on the box. If the animations complete without crashing, this test has passed." };
+
+ var success = new Label { Text = "Success", IsVisible = false };
+
+ var button = new Button() { Text = "Animate" };
+
+ Content = new StackLayout
+ {
+ VerticalOptions = LayoutOptions.Fill,
+ HorizontalOptions = LayoutOptions.Fill,
+ Children =
+ {
+ instructions,
+ success,
+ button,
+ new AbsoluteLayout
+ {
+ Children = { box },
+ HorizontalOptions = LayoutOptions.Fill,
+ VerticalOptions = LayoutOptions.Fill
+ }
+ }
+ };
+
+ button.Clicked += async (sender, args) => {
+ // Run a bunch of animations from the thread pool
+ await Task.WhenAll(
+ Task.Run(async () => await Translate(box)),
+ Task.Run(async () => await CheckTranslateRunning(box)),
+ Task.Run(async () => await AnimateScale(box)),
+ Task.Run(async () => await Rotate(box)),
+ Task.Run(async () => await Animate(box)),
+ Task.Run(async () => await Kinetic(box)),
+ Task.Run(async () => await Cancel(box))
+ );
+
+ success.IsVisible = true;
+ };
+ }
+
+ async Task CheckTranslateRunning(BoxView box)
+ {
+ Debug.WriteLine(box.AnimationIsRunning("TranslateTo") ? "Translate is running" : "Translate is not running");
+ }
+
+ static async Task Translate(BoxView box)
+ {
+ var currentX = box.X;
+ var currentY = box.Y;
+
+ await box.TranslateTo(currentX, currentY + 100);
+ await box.TranslateTo(currentX, currentY);
+ }
+
+ static async Task AnimateScale(BoxView box)
+ {
+ await box.ScaleTo(2);
+ await box.ScaleTo(0.5);
+ }
+
+ static async Task Rotate(BoxView box)
+ {
+ await box.RelRotateTo(360);
+ }
+
+ async Task Cancel(BoxView box)
+ {
+ box.AbortAnimation("animate");
+ box.AbortAnimation("kinetic");
+ }
+
+ async Task Animate(BoxView box)
+ {
+ box.Animate("animate", d => d, d => { }, 100, 1);
+ }
+
+ async Task Kinetic(BoxView box)
+ {
+ var resultList = new List<Tuple<double, double>>();
+
+ box.AnimateKinetic("kinetic", (distance, velocity) =>
+ {
+ resultList.Add(new Tuple<double, double>(distance, velocity));
+ return true;
+ }, 100, 1);
+ }
+
+#if UITEST
+ [Test]
+ public void DoesNotCrash()
+ {
+ RunningApp.Tap(q => q.Marked("Animate"));
+ RunningApp.WaitForElement(q => q.Marked("Success"));
+ }
+#endif
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
index 1b56d80f..279f60fa 100644
--- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
+++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems
@@ -91,6 +91,7 @@
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla39702.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla40173.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)Bugzilla39821.cs" />
<Compile Include="$(MSBuildThisFileDirectory)CarouselAsync.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34561.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Bugzilla34727.cs" />
diff --git a/Xamarin.Forms.Controls/App.cs b/Xamarin.Forms.Controls/App.cs
index d75b35af..31c356e3 100644
--- a/Xamarin.Forms.Controls/App.cs
+++ b/Xamarin.Forms.Controls/App.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
+using Xamarin.Forms.Controls.Issues;
namespace Xamarin.Forms.Controls
{
@@ -23,8 +24,9 @@ namespace Xamarin.Forms.Controls
{
_testCloudService = DependencyService.Get<ITestCloudService>();
InitInsights();
- // MainPage = new MainPageLifeCycleTests ();
- MainPage = new MasterDetailPage {
+ //MainPage = new MainPageLifeCycleTests();
+ MainPage = new MasterDetailPage
+ {
Master = new ContentPage { Title = "Master", BackgroundColor = Color.Red },
Detail = CoreGallery.GetMainPage()
};
diff --git a/Xamarin.Forms.Core/AnimationExtensions.cs b/Xamarin.Forms.Core/AnimationExtensions.cs
index efc3e405..957d002e 100644
--- a/Xamarin.Forms.Core/AnimationExtensions.cs
+++ b/Xamarin.Forms.Core/AnimationExtensions.cs
@@ -42,11 +42,29 @@ namespace Xamarin.Forms
public static bool AbortAnimation(this IAnimatable self, string handle)
{
- CheckAccess();
-
var key = new AnimatableKey(self, handle);
- return AbortAnimation(key) && AbortKinetic(key);
+ if (!s_animations.ContainsKey(key) && !s_kinetics.ContainsKey(key))
+ {
+ return false;
+ }
+
+ Action abort = () =>
+ {
+ AbortAnimation(key);
+ AbortKinetic(key);
+ };
+
+ if (Device.IsInvokeRequired)
+ {
+ Device.BeginInvokeOnMainThread(abort);
+ }
+ else
+ {
+ abort();
+ }
+
+ return true;
}
public static void Animate(this IAnimatable self, string name, Animation animation, uint rate = 16, uint length = 250, Easing easing = null, Action<double, bool> finished = null,
@@ -67,8 +85,9 @@ namespace Xamarin.Forms
self.Animate(name, x => x, callback, rate, length, easing, finished, repeat);
}
- public static void Animate<T>(this IAnimatable self, string name, Func<double, T> transform, Action<T> callback, uint rate = 16, uint length = 250, Easing easing = null,
- Action<T, bool> finished = null, Func<bool> repeat = null)
+ public static void Animate<T>(this IAnimatable self, string name, Func<double, T> transform, Action<T> callback,
+ uint rate = 16, uint length = 250, Easing easing = null,
+ Action<T, bool> finished = null, Func<bool> repeat = null)
{
if (transform == null)
throw new ArgumentNullException(nameof(transform));
@@ -77,8 +96,75 @@ namespace Xamarin.Forms
if (self == null)
throw new ArgumentNullException(nameof(self));
- CheckAccess();
+ Action animate = () => AnimateInternal(self, name, transform, callback, rate, length, easing, finished, repeat);
+
+ if (Device.IsInvokeRequired)
+ {
+ Device.BeginInvokeOnMainThread(animate);
+ }
+ else
+ {
+ animate();
+ }
+ }
+
+
+ public static void AnimateKinetic(this IAnimatable self, string name, Func<double, double, bool> callback, double velocity, double drag, Action finished = null)
+ {
+ Action animate = () => AnimateKineticInternal(self, name, callback, velocity, drag, finished);
+
+ if (Device.IsInvokeRequired)
+ {
+ Device.BeginInvokeOnMainThread(animate);
+ }
+ else
+ {
+ animate();
+ }
+ }
+
+ public static bool AnimationIsRunning(this IAnimatable self, string handle)
+ {
+ var key = new AnimatableKey(self, handle);
+ return s_animations.ContainsKey(key);
+ }
+
+ public static Func<double, double> Interpolate(double start, double end = 1.0f, double reverseVal = 0.0f, bool reverse = false)
+ {
+ double target = reverse ? reverseVal : end;
+ return x => start + (target - start) * x;
+ }
+
+ static void AbortAnimation(AnimatableKey key)
+ {
+ if (!s_animations.ContainsKey(key))
+ {
+ return;
+ }
+
+ Info info = s_animations[key];
+ info.Tweener.ValueUpdated -= HandleTweenerUpdated;
+ info.Tweener.Finished -= HandleTweenerFinished;
+ info.Tweener.Stop();
+ info.Finished?.Invoke(1.0f, true);
+
+ s_animations.Remove(key);
+ }
+ static void AbortKinetic(AnimatableKey key)
+ {
+ if (!s_kinetics.ContainsKey(key))
+ {
+ return;
+ }
+
+ Ticker.Default.Remove(s_kinetics[key]);
+ s_kinetics.Remove(key);
+ }
+
+ static void AnimateInternal<T>(IAnimatable self, string name, Func<double, T> transform, Action<T> callback,
+ uint rate, uint length, Easing easing, Action<T, bool> finished, Func<bool> repeat)
+ {
var key = new AnimatableKey(self, name);
AbortAnimation(key);
@@ -107,10 +193,8 @@ namespace Xamarin.Forms
tweener.Start();
}
- public static void AnimateKinetic(this IAnimatable self, string name, Func<double, double, bool> callback, double velocity, double drag, Action finished = null)
+ static void AnimateKineticInternal(IAnimatable self, string name, Func<double, double, bool> callback, double velocity, double drag, Action finished = null)
{
- CheckAccess();
-
var key = new AnimatableKey(self, name);
AbortKinetic(key);
@@ -118,8 +202,7 @@ namespace Xamarin.Forms
double sign = velocity / Math.Abs(velocity);
velocity = Math.Abs(velocity);
- int tick = Ticker.Default.Insert(step =>
- {
+ int tick = Ticker.Default.Insert(step => {
long ms = step;
velocity -= drag * ms;
@@ -142,56 +225,6 @@ namespace Xamarin.Forms
s_kinetics[key] = tick;
}
- public static bool AnimationIsRunning(this IAnimatable self, string handle)
- {
- CheckAccess();
-
- var key = new AnimatableKey(self, handle);
-
- return s_animations.ContainsKey(key);
- }
-
- public static Func<double, double> Interpolate(double start, double end = 1.0f, double reverseVal = 0.0f, bool reverse = false)
- {
- double target = reverse ? reverseVal : end;
- return x => start + (target - start) * x;
- }
-
- static bool AbortAnimation(AnimatableKey key)
- {
- if (!s_animations.ContainsKey(key))
- {
- return false;
- }
-
- Info info = s_animations[key];
- info.Tweener.ValueUpdated -= HandleTweenerUpdated;
- info.Tweener.Finished -= HandleTweenerFinished;
- info.Tweener.Stop();
- info.Finished?.Invoke(1.0f, true);
-
- return s_animations.Remove(key);
- }
-
- static bool AbortKinetic(AnimatableKey key)
- {
- if (!s_kinetics.ContainsKey(key))
- {
- return false;
- }
-
- Ticker.Default.Remove(s_kinetics[key]);
- return s_kinetics.Remove(key);
- }
-
- static void CheckAccess()
- {
- if (Device.IsInvokeRequired)
- {
- throw new InvalidOperationException("Animation operations must be invoked on the UI thread");
- }
- }
-
static void HandleTweenerFinished(object o, EventArgs args)
{
var tweener = o as Tweener;