Свойства линейной анимации

99

Свойство From

Свойство From задает начальное значение для свойства Width. Если щелкать на кнопке несколько раз, то всякий раз ширина кнопки будет сбрасываться в 150, и анимация запустится вновь. Так будет, даже если щелкнуть на кнопке в процессе уже запущенной анимации.

В этом примере раскрывается еще одна сторона анимации WPF, а именно: всякое свойство зависимости должно обрабатываться только одной анимацией в каждый момент времени. Запуск второй анимации приводит к автоматической отмене первой.

Во многих ситуациях не нужно, чтобы анимация всегда начиналась с исходного значения From. Обычно на то имеются две причины:

Приведенный пример подпадает под вторую категорию. Если щелкнуть на кнопке в процессе ее роста, то ширина будет тут же сброшена до 150 пикселей, что может оказаться неприемлемым. Для решения этой проблемы просто исключите оператор, устанавливающий свойство From:

DoubleAnimation da = new DoubleAnimation();
da.To = this.Width - 20;
da.Duration = TimeSpan.FromSeconds(5);
btn.BeginAnimation(Button.WidthProperty, da);

Существует одна сложность. Чтобы такой прием работал, анимируемое свойство должно иметь ранее установленное значение. В данном примере это означает, что кнопка должна иметь жестко закодированную ширину (заданную либо в дескрипторе кнопки, либо примененную установкой стиля). Проблема в том, что во многих контейнерах компоновки принято не указывать ширину, а позволять контейнеру управлять ею на основе свойств выравнивания элементов. В рассматриваемом случае применяется значение ширины по умолчанию, которое равно специальному значению Double.NaN (где NaN означает "not a number" — "не число"). Выполнять анимацию свойства, имеющего такое значение, с применением линейной интерполяции нельзя.

Итак, каково же решение? Во многих случаях оно сводится к жесткому кодированию ширины кнопки. Как вскоре можно будет убедиться, анимация часто требует более точного управления размерами элементов и их позиционированием. Фактически, наиболее часто используемым контейнером компоновки для "анимируемого" содержимого является Canvas, поскольку он позволяет легко перемещать содержимое по своей поверхности (с возможностью перекрытия) и изменять его размер. Canvas также наиболее облегченный контейнер компоновки, поскольку ему не приходится выполнять никакой дополнительной работы по компоновке при изменении свойства, подобного Width.

В текущем примере доступен и другой выбор. Извлечь текущее значение кнопки можно с использованием свойства ActualWidth, которое содержит текущую отображаемую ширину. Анимация свойства ActualWidth невозможна (оно доступно только для чтения), но можно использовать его для установки свойства From анимации:

da.From = btn.ActualWidth;

Такой прием работает как с анимацией на основе кода (показанной в примере), так и с декларативной анимацией, которая рассматривается позже (что потребует применения выражения привязки для получения значения ActualWidth).

В этом примере важно пользоваться именно свойством ActualWidth, а не Width. Это связано с тем, что Width отражает желаемую ширину, которая была выбрана, а ActualWidth — используемую в данный момент текущую отображаемую ширину. В случае применения автоматической компоновки, скорее всего, значение Width не будет жестко кодироваться, так что свойство Width просто вернет Double.NaN, и при попытке запуска анимации возникнет исключение.

При использовании текущего значения в качестве начальной точки анимации следует помнить об еще одной проблеме: это может изменить скорость анимации. Причина в том, что длительность анимации не подстраивается, чтобы учесть меньшую дистанцию между начальным и конечным значением. Например, предположим, что создается кнопка, которая не использует значение From и выполняет анимацию, начиная с текущей позиции. Если щелкнуть на кнопке в момент, когда она почти достигла своей максимальной ширины, начнется новая анимация. Эта анимация сконфигурирована так, чтобы длиться пять секунд (задано в свойстве Duration), и это несмотря на то, что до максимальной ширины ей останется несколько пикселей. В результате рост кнопки тут же замедлится.

Этот эффект проявляется только при перезапуске анимации, которая почти завершена. Хотя это существенный недостаток, большинство разработчиков не пытаются написать код для его преодоления. Вместо этого просто считается, что такое поведение более-менее приемлемо.

Упомянутую проблему можно скомпенсировать, написав некоторую специальную логику, которая будет модифицировать длительность анимации, хотя это редко стоит потраченных усилий. Чтобы сделать это, необходимо сделать предположение относительно стандартного размера кнопки (что ограничит возможность повторного использования кода). Кроме того, для запуска этого кода анимация должна быть описана программно (а не декларативно, что общепринято, как вы вскоре убедитесь).

Свойство To

Точно так же, как можно опустить свойство From, можно не указывать и свойство То. Фактически можно не задавать оба свойства — и From, и То, — создав анимацию вроде следующей:

DoubleAnimation da = new DoubleAnimation();
da.Duration = TimeSpan.FromSeconds(5);
btn.BeginAnimation(Button.WidthProperty, da);

На первый взгляд эта анимация выглядит как сложный способ не делать вообще ничего. Логично предположить, что поскольку опущены оба свойства From и То, они будут использовать одно и то же значение. Но между ними есть одно тонкое, однако, существенное отличие.

В случае отсутствия From анимация использует текущее значение и учитывает состояние анимации. Например, если кнопка находится в процессе роста, значение From использует увеличенную ширину. Однако если не указано свойство То, анимация использует текущее значение, не принимая во внимание саму анимацию. По сути, это означает, что То принимает первоначальное значение — то, которое было последний раз установлено в коде, в дескрипторе элемента или с помощью стиля. (Это работает благодаря системе разрешения свойств WPF, которая в состоянии вычислить значение свойства на основе нескольких перекрывающихся поставщиков свойств, не отбрасывая никакой информации.)

В примере с кнопкой это означает, что если запустить анимацию роста, а затем прервать ее анимацией, показанной ранее (возможно, щелчком на другой кнопке), то кнопка станет уменьшаться от того размера, до которого успела вырасти, и будет уменьшаться, пока не достигнет исходной ширины, указанной в разметке XAML. С другой стороны, если запустить этот код, когда никакая другая анимация не выполняется, то ничего не произойдет. Это объясняется тем, что значение From (анимируемая ширина) и значение То (исходная ширина) совпадают.

Свойство By

Вместо То можно использовать свойство By. Свойство By служит для создания анимации, которая изменяет значение на определенную величину, а не до определенной величины. Например, для создания анимации, которая увеличивает кнопку на 10 единиц больше ее текущего размера, служит следующий код:

DoubleAnimation da = new DoubleAnimation();
da.By = 10;
da.Duration = TimeSpan.FromSeconds(1);
btn.BeginAnimation(Button.WidthProperty, da);

Такой подход не является необходимым в данном примере, поскольку того же результата можно достичь и с помощью простого вычисления при установке свойства То:

da.То = btn.Width + 10;

Однако значение By более осмысленно, когда анимация определяется в XAML-разметке, поскольку XAML не позволяет выполнять простые вычисления.

Значения By и From можно использовать совместно, но это не сократит объем работы. Значение By просто добавляется к значению From для достижения значения То. Свойство By предоставляется большинством, хотя и не всеми классами, использующими интерполяцию. Например, оно не имеет смысла для нечисловых типов данных, таких как структура Color (применяемая в ColorAnimation).

Есть только один способ получить аналогичное поведение без применения By — можно создать аддитивную анимацию, установив свойство IsAdditive. После этого текущее значение будет автоматически добавляться к обоим значениям — From и То.

Свойство Duration

Свойство Duration достаточно очевидно — оно принимает временной интервал (в миллисекундах, минутах, часах или любых других желаемых единицах) между моментом запуска анимации и временем ее завершения. Хотя длительность анимации в предыдущих примерах установлена с использованием TimeSpan, свойство Duration на самом деле требует объекта Duration. К счастью, Duration и TimeSpan достаточно похожи, и структура Duration предусматривает неявное приведение, с помощью которого при необходимости можно преобразовать System.TimeSpan в System.Windows.Duration.

Вот почему следующая строка кода вполне законна:

da.Duration = TimeSpan.FromSeconds(5);

Так зачем вводить целый новый тип? Duration также включает два специальных значения, которые не могут быть представлены объектом TimeSpan. Это Duration.Automatic и Duration.Forever. Ни одно из этих значений не применимо в текущем примере. (Automatic просто устанавливает анимацию в односекундную длительность, a Forever задает бесконечную длительность анимации, что предотвращает проявление какого-либо эффекта.) Однако эти значения могут оказаться удобными при создании более сложной анимации.

Пройди тесты
Лучший чат для C# программистов