diff options
author | E.Z. Hart <hartez@users.noreply.github.com> | 2016-04-08 12:26:03 -0600 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-04-08 15:04:34 -0700 |
commit | 3b63b226711fcc0e616d06e3f486f5bf2265761b (patch) | |
tree | b47e375f5b234a188364649d8ce64904dc1032a0 | |
parent | ad1c0537f57b870cf4b47dd43cfae0402d593c55 (diff) | |
download | xamarin-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
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; |