iosanimationxamarin.ioscore-animationcamediatiming

Change Speed of Explicit Animation using CAAnimation.CurrentMediaTime()


I'm trying to change the speed of an Explicit iOS Animation (so think CABasicAnimation or CAKeyFrameAnimation) using CAAnimation.CurrentMediaTime. This method is much better than stoping the animation, then restarting it with a new duration. I'm SO CLOSE to making this all work but I'm having one little problem. I can Start, Pause, Resume, and change speeds sometimes, but the change speed function is buggy. Can anyone shed some light onto the situation? I have some very simple code and an example project below.

Class level variables

UIView MyImage;
double timeSincePause = 0;
double pauseTime = 0;

My Pause Function (That works)

MyImage.Layer.Speed = 0.0f;
MyImage.Layer.TimeOffset = MyImage.Layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime(), MyImage.Layer) - timeSincePause;

My Stop Function (That works)

pauseTime = MyImage.Layer.TimeOffset;
MyImage.Layer.Speed = 1.0f;
MyImage.Layer.TimeOffset = 0.0f;
MyImage.Layer.BeginTime = 0.0f;
timeSincePause = (MyImage.Layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime(), MyImage.Layer) - pauseTime);
MyImage.Layer.BeginTime = timeSincePause;

My Change Speed Function (That doesn't work) To reproduce the problem simply launch the project and hit the change speed button a few times. Notice that when it goes from 100% to 30% speed it changes speed correctly, but when it goes from 30% speed to 100% speed the position of the object jumps ahead as if the speed was never changed to 30%. This is clearly a problem with the Time value I'm saving, I've experimented and couldn't get it to work.

MyImage.Layer.TimeOffset = MyImage.Layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime(), MyImage.Layer);
MyImage.Layer.BeginTime = CAAnimation.CurrentMediaTime();
// I have it set so the speed changes back and forth between 100% and 30%.
if ( MyImage.Layer.Speed == 1.0f ) 
    MyImage.Layer.Speed = 0.3f;
else
    MyImage.Layer.Speed = 1.0f;

Here's a link to the very simple example project writtin in C# with Xamarin Studio iOS: https://www.dropbox.com/s/ndfo12rbemsnp1s/ChangeAnimationSpeed.zip

--------- Additional Information ----------

I've determined the problem is that I'm not factoring in the Time lost/gained as a result of changing speed... I'm just not sure how to take that into account yet... So I basically need to add another class level variable called "totalTimeLostResultingFromSpeedChange" and keep track of that and factor that in somehow...


Solution

  • I finally solved this problem after working on it for days on and off! Here's the C# Xamarin iOS project. And below are the key functions. This is some valuable stuff for anyone not using a game engine to animate in iOS.

    Video

    Class level variables

        UIView MyImage;
    
        double currentPauseTime = 0;
        double totalPauseTime = 0;
        double currentChangeSpeedTime = 0;
        double currentTimeLostResultingFromSpeedChange = 0;
        double totalTimeLostResultingFromSpeedChange = 0;
    
        float currentSpeed = 0;
    

    Key Functions

        public void PauseAnimation()
        {
            if ( MyImage.Layer.Speed != 0.0f )
            {
                Update_TotalTimeLostResultingFromSpeedChange();
    
                MyImage.Layer.Speed = 0.0f;
                MyImage.Layer.TimeOffset = MyImage.Layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime(), MyImage.Layer) - totalPauseTime - totalTimeLostResultingFromSpeedChange;
                currentPauseTime = MyImage.Layer.TimeOffset;
            }
        }
    
    
        public void ResumeAnimation()
        {
            if ( MyImage.Layer.Speed == 0.0f )
            {
                MyImage.Layer.Speed = 1.0f;
                MyImage.Layer.TimeOffset = 0.0f;
                MyImage.Layer.BeginTime = 0.0f;
    
                // Because we reset the BeginTime and TimeOffset, Reset the totalTimeLostResultingFromSpeedChange
                Reset_TotalTimeLostResultingFromSpeedChange();
    
                totalPauseTime = (MyImage.Layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime(), MyImage.Layer) - currentPauseTime);
                MyImage.Layer.BeginTime = totalPauseTime;
            }
    
    
            ChangeAnimationSpeed( currentSpeed );
        }
    
    
        public void ChangeAnimationSpeed( float speed )
        {
            if ( speed != 0.0f )
            {
                Update_TotalTimeLostResultingFromSpeedChange();
    
                currentChangeSpeedTime = MyImage.Layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime(), MyImage.Layer);
                MyImage.Layer.TimeOffset = currentChangeSpeedTime - totalPauseTime - totalTimeLostResultingFromSpeedChange;
                MyImage.Layer.BeginTime = CAAnimation.CurrentMediaTime();
    
                currentSpeed = speed;
                MyImage.Layer.Speed = currentSpeed;
            }
        }
    
    
        public void Update_TotalTimeLostResultingFromSpeedChange()
        {
            double value = MyImage.Layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime(), MyImage.Layer) - currentChangeSpeedTime;
            currentTimeLostResultingFromSpeedChange = value - (value * MyImage.Layer.Speed);
            totalTimeLostResultingFromSpeedChange += currentTimeLostResultingFromSpeedChange;
        }
    
    
        public void Reset_TotalTimeLostResultingFromSpeedChange()
        {
            totalTimeLostResultingFromSpeedChange = 0;
        }