Skip to content

Commit

Permalink
Added an animation to Navigator and a FlyTo() function (Mapsui#627)
Browse files Browse the repository at this point in the history
* Added added an animation to Navigator and a FlyTo() function for testing this animation

* Added animation to the most methods of Navigator

* Added an animator class with entries, so that each value could be animated

* Replaced changes of Navigator with original code

* Reverted Navigator setting from AnimatedNavigator to normal Navigator
  • Loading branch information
charlenni authored and radderz committed Aug 14, 2019
1 parent 7b7c2e8 commit a3321f5
Show file tree
Hide file tree
Showing 8 changed files with 828 additions and 1 deletion.
485 changes: 485 additions & 0 deletions Mapsui/AnimatedNavigator.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Mapsui/Navigator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class Navigator : INavigator
private readonly Map _map;
private readonly IViewport _viewport;

public EventHandler Navigated { get; set; }
public EventHandler Navigated { get; set; }

public Navigator(Map map, IViewport viewport)
{
Expand Down
116 changes: 116 additions & 0 deletions Mapsui/Utilities/Animation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Timers;

namespace Mapsui.Utilities
{
public class Animation
{
private Timer _timer;
private Stopwatch _stopwatch;
private long _stopwatchStart;
private long _durationTicks;

public Animation(long duration)
{
Duration = duration;

// Create timer for animation
_timer = new Timer
{
Interval = 16,
AutoReset = true
};
_timer.Elapsed += HandleTimerElapse;
}

public EventHandler<AnimationEventArgs> Started { get; set; }
public EventHandler<AnimationEventArgs> Stopped { get; set; }
public EventHandler<AnimationEventArgs> Ticked { get; set; }

/// <summary>
/// Duration of the whole animation cycle in milliseconds
/// </summary>
public long Duration { get; } = 300;

/// <summary>
/// Animations, that should be made
/// </summary>
public List<AnimationEntry> Entries { get; } = new List<AnimationEntry>();

/// <summary>
/// True, if animation is running
/// </summary>
public bool IsRunning { get => _timer != null && _timer.Enabled; }

public void Start()
{
if (IsRunning)
{
Stop(false);
}

// Animation in ticks;
_durationTicks = Duration * Stopwatch.Frequency / 1000;

_stopwatch = Stopwatch.StartNew();
_stopwatchStart = _stopwatch.ElapsedTicks;
_timer.Start();

Started?.Invoke(this, new AnimationEventArgs(0));
}

/// <summary>
/// Stop a running animation if there is one
/// </summary>
/// <param name="gotoEnd">Should final of each list entry be called</param>
public void Stop(bool gotoEnd = true)
{
if (!_timer.Enabled)
return;

_timer.Stop();
_stopwatch.Stop();

double ticks = _stopwatch.ElapsedTicks - _stopwatchStart;
var value = ticks / _durationTicks;

if (gotoEnd)
{
foreach(var entry in Entries)
{
entry.Final();
}
}

Stopped?.Invoke(this, new AnimationEventArgs(value));
}

/// <summary>
/// Timer tick for animation
/// </summary>
/// <param name="sender">Sender of this tick</param>
/// <param name="e">Timer tick arguments</param>
private void HandleTimerElapse(object sender, ElapsedEventArgs e)
{
double ticks = _stopwatch.ElapsedTicks - _stopwatchStart;
var value = ticks / _durationTicks;

if (value >= 1.0)
{
Stop(true);
return;
}

// Calc new values
foreach(var entry in Entries)
{
if (value >= entry.AnimationStart && value <= entry.AnimationEnd)
entry.Tick(value);
}

Ticked?.Invoke(this, new AnimationEventArgs(value));
}
}
}
82 changes: 82 additions & 0 deletions Mapsui/Utilities/AnimationEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace Mapsui.Utilities
{
public class AnimationEntry
{
private double _animationDelta;
private Action<AnimationEntry, double> _tick;
private Action<AnimationEntry> _final;

public AnimationEntry(object start, object end,
double animationStart = 0, double animationEnd = 1,
Easing easing = null,
Action<AnimationEntry, double> tick = null,
Action<AnimationEntry> final = null)
{
AnimationStart = animationStart;
AnimationEnd = animationEnd;

Start = start;
End = end;

Easing = easing ?? Easing.Linear;

_animationDelta = AnimationEnd - AnimationStart;

_tick = tick;
_final = final;
}

/// <summary>
/// When this animation starts in animation cycle. Value between 0 and 1.
/// </summary>
public double AnimationStart { get; }

/// <summary>
/// When this animation ends in animation cycle. Value between 0 and 1.
/// </summary>
public double AnimationEnd { get; }

/// <summary>
/// Object holding the starting value
/// </summary>
public object Start { get; }

/// <summary>
/// Object holding the end value
/// </summary>
public object End { get; }

/// <summary>
/// Easing to use for this animation
/// </summary>
public Easing Easing { get; }

/// <summary>
/// Called when a value should changed
/// </summary>
/// <param name="value">Position in animation cycle between 0 and 1</param>
public void Tick(double value)
{
if (value < AnimationStart || value > AnimationEnd)
return;

// Each tick gets a value between 0 and 1 for its own cycle
// Its independent from the global animation cycle
var v = (value - AnimationStart) / _animationDelta;

_tick(this, v);
}

/// <summary>
/// Called when the animation cycle is at the end
/// </summary>
public void Final()
{
_final(this);
}
}
}
12 changes: 12 additions & 0 deletions Mapsui/Utilities/AnimationEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Mapsui.Utilities
{
public class AnimationEventArgs
{
public AnimationEventArgs(double value)
{
Value = value;
}

public double Value { get; }
}
}
98 changes: 98 additions & 0 deletions Mapsui/Utilities/Easing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// Tweener.cs
//
// Author:
// Jason Smith <jason.smith@xamarin.com>
//
// Copyright (c) 2012 Xamarin Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;

namespace Mapsui.Utilities
{
public class Easing
{
public static readonly Easing Linear = new Easing(x => x);

public static readonly Easing SinOut = new Easing(x => Math.Sin(x * Math.PI * 0.5f));
public static readonly Easing SinIn = new Easing(x => 1.0f - Math.Cos(x * Math.PI * 0.5f));
public static readonly Easing SinInOut = new Easing(x => -Math.Cos(Math.PI * x) / 2.0f + 0.5f);

public static readonly Easing CubicIn = new Easing(x => x * x * x);
public static readonly Easing CubicOut = new Easing(x => Math.Pow(x - 1.0f, 3.0f) + 1.0f);

public static readonly Easing CubicInOut = new Easing(x => x < 0.5f ? Math.Pow(x * 2.0f, 3.0f) / 2.0f : (Math.Pow((x - 1) * 2.0f, 3.0f) + 2.0f) / 2.0f);

public static readonly Easing BounceOut;
public static readonly Easing BounceIn;

public static readonly Easing SpringIn = new Easing(x => x * x * ((1.70158f + 1) * x - 1.70158f));
public static readonly Easing SpringOut = new Easing(x => (x - 1) * (x - 1) * ((1.70158f + 1) * (x - 1) + 1.70158f) + 1);

readonly Func<double, double> _easingFunc;

static Easing()
{
BounceOut = new Easing(p =>
{
if (p < 1 / 2.75f)
{
return 7.5625f * p * p;
}
if (p < 2 / 2.75f)
{
p -= 1.5f / 2.75f;

return 7.5625f * p * p + .75f;
}
if (p < 2.5f / 2.75f)
{
p -= 2.25f / 2.75f;

return 7.5625f * p * p + .9375f;
}
p -= 2.625f / 2.75f;

return 7.5625f * p * p + .984375f;
});

BounceIn = new Easing(p => 1.0f - BounceOut.Ease(1 - p));
}

public Easing(Func<double, double> easingFunc)
{
if (easingFunc == null)
throw new ArgumentNullException("easingFunc");

_easingFunc = easingFunc;
}

public double Ease(double v)
{
return _easingFunc(v);
}

public static implicit operator Easing(Func<double, double> func)
{
return new Easing(func);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using Mapsui.Samples.Common.Maps;
using Mapsui.UI;
using Mapsui.UI.Forms;

namespace Mapsui.Samples.Forms
{
public class AnimationSample : IFormsSample
{
static int markerNum = 1;
static Random rnd = new Random();

public string Name => "Animation Sample";

public string Category => "Forms";

public bool OnClick(object sender, EventArgs args)
{
var mapView = sender as MapView;
var e = args as MapClickedEventArgs;

var navigator = (AnimatedNavigator)mapView.Navigator;
navigator.FlyTo(e.Point.ToMapsui(), mapView.Viewport.Resolution * 2);

return true;
}

public void Setup(IMapControl mapControl)
{
mapControl.Map = OsmSample.CreateMap();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<DependentUpon>MapPage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)AnimationSample.cs" />
<Compile Include="$(MSBuildThisFileDirectory)MyLocationSample.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PinSample.cs" />
<Compile Include="$(MSBuildThisFileDirectory)PolygonSample.cs" />
Expand Down

0 comments on commit a3321f5

Please sign in to comment.