Решил немножко покопаться в Silverlight, да смастерить на нём что-нибудь прикольное. Это прикольное, конечно, должно шевелиться, переливаться и плавно подёргиваться, ибо вебдваноль у нас или где? :). И вот тут мне пришлось столкнуться с неплохой, по сути, системой анимаций в WPF/Silverlight. Покурив MSDN, я бодренько приступил к написанию анимаций в XAML. Одну написал, вторую, третью… А потом мне захотелось сделать так, чтобы они шли в определённой последовательности. И вот тут-то я и понял, что XAML, зараза, очень избыточный. Для описания интерфейсов он подходит идеально: сразу видно, что к чему относится и надобность в визуальном редакторе отпадает чуть менее, чем полностью. Но вот когда пытаешься написать в этом XAMLе какую-то логику, начинает проявляться вся его несуразность. Покурив гугл, я был сильно удивлён тем, что большинство людей упорно пытаются впихнуть в XAML абсолютно всё. Ругаются, путаются в коде, плачут, но продолжают писать. Прямо как те мыши с кактусом, чесслово. И тут мне пришла идея аккуратно описать анимации обычным кодом на C#. Мы, так сказать, олдфаги, рисовали интерфейс прямыми вызовами к WinAPI, неужто нас какие-то анимации испугают? :)
В результате получился вот такой портабельный класс AnimationBag. Портабельный он потому, что его безо всяких изменений можно использовать как в WPF, так и в Silverlight.
Как видно из кода, класс предельно простой. Вот пример использования.
XAML:
Code-behind:
Сам класс представляет собой что-то вроде словаря с ключами — именами анимаций и значениями — анимациями. В методе InitAnimations в коллекцию добавляются две анимации, при этом указывается имя, контрол, над которым будет производится действо, свойство этого контрола и сам объект анимации. Его можно создавать ручками, а можно добавить статические хэлперы к уже имеющемуся методу для DoubleAnimation. Кроме всего прочего, метод AddAnimation может принимать два делегата, которые будут выполняться до и после самой анимации. Например, здесь после завершения анимации “fadeOut” сразу запускается “fadeIn”.
В итоге, получился довольно удобный механизм, позволяющий описывать анимации одной строкой кода вместо килобайтов перегруженного XAML.
Скачать исходники с тестовыми проектами
В результате получился вот такой портабельный класс AnimationBag. Портабельный он потому, что его безо всяких изменений можно использовать как в WPF, так и в Silverlight.
public class AnimationItem
{
public event EventHandler Completed;
private void OnStoryboardComplete(object sender, EventArgs e)
{
if (Completed != null)
Completed(this, EventArgs.Empty);
}
private Storyboard storyboard;
public Storyboard Storyboard
{
get { return storyboard; }
set
{
if (storyboard != null)
storyboard.Completed -= OnStoryboardComplete;
storyboard = value;
storyboard.Completed += OnStoryboardComplete;
}
}
public Action BeginAction { get; set; }
public Action EndAction { get; set; }
}
public class AnimationBag
{
private readonly Dictionary<string, AnimationItem> storyboards = new Dictionary<string, AnimationItem>();
public void AddAnimation(string name, DependencyObject control, string propertyName, Timeline animation, Action beginAction = null, Action endAction = null)
{
Storyboard board = new Storyboard();
AnimationItem item = new AnimationItem { BeginAction = beginAction, EndAction = endAction };
Storyboard.SetTarget(animation, control);
Storyboard.SetTargetProperty(animation, new PropertyPath(propertyName));
board.Children.Add(animation);
if (endAction != null)
item.Completed += item_Completed;
item.Storyboard = board;
storyboards[name] = item;
}
private void item_Completed(object sender, EventArgs e)
{
KeyValuePair<string, AnimationItem> pair = storyboards.Where(x => x.Value.Equals(sender)).FirstOrDefault();
if (pair.Value != null && pair.Value.EndAction != null)
pair.Value.EndAction.Invoke();
}
public void StartAnimation(string name)
{
if (!storyboards.ContainsKey(name))
return;
if (storyboards[name].BeginAction != null)
storyboards[name].BeginAction.Invoke();
storyboards[name].Storyboard.Begin();
}
public void StopAnimation(string name)
{
if (!storyboards.ContainsKey(name))
return;
storyboards[name].Storyboard.Stop();
if (storyboards[name].EndAction != null)
storyboards[name].EndAction.Invoke();
}
public static DoubleAnimation CreateDoubleAnimation(double? to, long durationMs, double? from = null, bool repeat = false)
{
DoubleAnimation ret = new DoubleAnimation { To = to, From = from, Duration = new Duration(TimeSpan.FromMilliseconds(durationMs)) };
if (repeat)
ret.RepeatBehavior = RepeatBehavior.Forever;
return ret;
}
}
Как видно из кода, класс предельно простой. Вот пример использования.
XAML:
<Window x:Class="Animation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Rectangle Height="110" HorizontalAlignment="Left" Margin="55,71,0,0" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Width="181" Fill="#FF9D3434" />
<Button Content="Button" Height="36" HorizontalAlignment="Left" Margin="286,93,0,0" Name="button1" VerticalAlignment="Top" Width="149" Click="button1_Click" />
</Grid>
</Window>
Code-behind:
public partial class MainWindow : Window
{
private readonly AnimationBag animations = new AnimationBag();
public MainWindow()
{
InitializeComponent();
InitAnimations();
}
private void InitAnimations()
{
animations.AddAnimation(
"fadeOut",
rectangle1,
"Opacity",
AnimationBag.CreateDoubleAnimation(0, 500),
null,
() => animations.StartAnimation("fadeIn"));
animations.AddAnimation(
"fadeIn",
rectangle1,
"Opacity",
AnimationBag.CreateDoubleAnimation(1, 500));
}
private void button1_Click(object sender, RoutedEventArgs e)
{
animations.StartAnimation("fadeOut");
}
}
Сам класс представляет собой что-то вроде словаря с ключами — именами анимаций и значениями — анимациями. В методе InitAnimations в коллекцию добавляются две анимации, при этом указывается имя, контрол, над которым будет производится действо, свойство этого контрола и сам объект анимации. Его можно создавать ручками, а можно добавить статические хэлперы к уже имеющемуся методу для DoubleAnimation. Кроме всего прочего, метод AddAnimation может принимать два делегата, которые будут выполняться до и после самой анимации. Например, здесь после завершения анимации “fadeOut” сразу запускается “fadeIn”.
В итоге, получился довольно удобный механизм, позволяющий описывать анимации одной строкой кода вместо килобайтов перегруженного XAML.
Скачать исходники с тестовыми проектами