xamarin / Xamarin.Forms

Xamarin.Forms is no longer supported. Migrate your apps to .NET MAUI.
https://aka.ms/xamarin-upgrade
Other
5.62k stars 1.87k forks source link

[Android] Device animations disabled issue #3694

Closed AndreiMisiukevich closed 6 years ago

AndreiMisiukevich commented 6 years ago

Hi there

If user disabled animations on his device, Animation.Commit would do nothing. So, committing animation doesn't affect view's at all. In my opinion, it would be better handle it next way:

if animations are disabled, we call handler on last step at once.

I fixed such issue for my plugin by adding

namespace PanCardView.Utility
{
    public interface IAnimationsChecker
    {
        bool AreAnimationsEnabled { get; }
    }
}
using PanCardView.Utility;
using Android.Provider;
using Android.App;
using PanCardView.Droid;
using Android.Runtime;

[assembly: Xamarin.Forms.Dependency(typeof(AnimationsChecker))]
namespace PanCardView.Droid
{
    [Preserve(AllMembers = true)]
    public class AnimationsChecker : IAnimationsChecker
    {
        public bool AreAnimationsEnabled 
        {
            get
            {
                try
                {
                    var resolver = Application.Context.ContentResolver;
                    var scale = Settings.Global.AnimatorDurationScale;
                    return Settings.Global.GetFloat(resolver, scale, 1) > 0;
                }
                catch
                {
                    return true;
                }
            }
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using Xamarin.Forms;
using System.Threading.Tasks;

namespace PanCardView.Utility
{
    public sealed class AnimationWrapper : IEnumerable
    {
        private readonly List<AnimationWrapper> _children;

        public AnimationWrapper(Action<double> callback = null, double start = 0, double end = 1)
        {
            _children = new List<AnimationWrapper>();
            Callback = callback;
            Start = start;
            End = end;
        }

        public IEnumerator GetEnumerator() => _children.GetEnumerator();

        public double Start { get; private set; }

        public double End { get; private set; }

        public double BeginsAt { get; private set; }

        public double FinishsAt { get; private set; }

        public Action<double> Callback { get; private set; }

        public void Add(double beginAt, double finishAt, AnimationWrapper animation)
        {
            animation.BeginsAt = beginAt;
            animation.FinishsAt = finishAt;
            _children.Add(animation);
        }

        public Task Commit(View view, string name, uint rate = 16u, uint length = 250u, Easing easing = null)
        {
            if (DependencyService.Get<IAnimationsChecker>()?.AreAnimationsEnabled ?? true)
            {
                var tcs = new TaskCompletionSource<bool>();
                PrepareAnimation(this).Commit(view, name, rate, length, easing, (d, b) => tcs.SetResult(true));
                return tcs.Task;
            }

            try
            {
                view.BatchBegin();
                CommitWithoutAnimation(this);
            }
            finally
            {
                view.BatchCommit();
            }
            return Task.FromResult(true);
        }

        private void CommitWithoutAnimation(AnimationWrapper animation)
        {
            foreach (AnimationWrapper childAnimation in animation)
            {
                CommitWithoutAnimation(childAnimation);
            }
            animation.Callback?.Invoke(animation.End);
        }

        private Animation PrepareAnimation(AnimationWrapper animationWrapper)
        {
            var parentAnimation = animationWrapper.Callback != null
                ? new Animation(animationWrapper.Callback, animationWrapper.Start, animationWrapper.End)
                : new Animation();

            foreach (AnimationWrapper childAnimation in animationWrapper)
            {
                var anim = PrepareAnimation(childAnimation);
                parentAnimation.Add(childAnimation.BeginsAt, childAnimation.FinishsAt, anim);
            }
            return parentAnimation;
        }
    }
}

I skipped some properties (Repeat and finished (Action), because i have no need in them, but i think it will be possible to add them as well as existing ones)

More info: there https://github.com/AndreiMisiukevich/CardView/issues/93

GalaxiaGuy commented 6 years ago

One important note is animations will generally be disabled during battery saver mode, which is were I first came across this.

Should this also scale animations appropriately if AnimatorDurationScale is not 0 or 1? I don't know if there are any circumstances that can cause that besides changing it in the device developer options.

Also, the random snippet I found suggests the code you have works only on API 17 (JellyBeanMr1, 4.2) or above, and lower versions should use Settings.System instead of Settings.Global:

var resolver = Application.Context.ContentResolver;
var scale = Settings.System.AnimatorDurationScale;
return Settings.System.GetFloat(resolver, scale, 1) > 0;
PureWeen commented 6 years ago

@AndreiMisiukevich just making sure I'm understanding. If we have the following animation

var animation = new Animation (v =>
{
    System.Diagnostics.Debug.WriteLine($"Animation happening:{v}");
    thing.Scale = v;
}, 1, 20);

animation.Commit(page, "SimpleAnimation", 16, 10000, Easing.Linear, (v, c) =>
{
    System.Diagnostics.Debug.WriteLine($"Animation Completed");
    thing.Scale = 1;
}, () => false);

Right now it'll output

Animation happening:1
Animation happening:20
Animation happening:Animation Completed

Are you saying it should just be

Animation happening:20
Animation happening:Animation Completed