Оригинал статьи взят отсюда: Сервелат, анимация и старый добрый code-behind
Автор (на хабре): ThePretender
Решил немножко покопаться в Silverlight, да смастерить на нём что-нибудь прикольное. Это прикольное, конечно, должно шевелиться, переливаться и плавно подёргиваться, ибо вебдваноль у нас или где? :). И вот тут мне пришлось столкнуться с неплохой, по сути, системой анимаций в WPF/Silverlight. Покурив MSDN, я бодренько приступил к написанию анимаций в XAML. Одну написал, вторую, третью… А потом мне захотелось сделать так, чтобы они шли в определённой последовательности. И вот тут-то я и понял, что XAML, зараза, очень избыточный. Для описания интерфейсов он подходит идеально: сразу видно, что к чему относится и надобность в визуальном редакторе отпадает чуть менее, чем полностью. Но вот когда пытаешься написать в этом XAMLе какую-то логику, начинает проявляться вся его несуразность. Покурив гугл, я был сильно удивлён тем, что большинство людей упорно пытаются впихнуть в XAML абсолютно всё. Ругаются, путаются в коде, плачут, но продолжают писать. Прямо как те мыши с кактусом, чесслово. И тут мне пришла идея аккуратно описать анимации обычным кодом на C#. Мы, так сказать, олдфаги, рисовали интерфейс прямыми вызовами к WinAPI, неужто нас какие-то анимации испугают? :) В результате получился вот такой портабельный класс 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. Скачать исходники с тестовыми проектами
|