Анимация перехода между страницами

164

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

Например, можно создать класс FadeElementEffect для плавного удаления элемента с экрана путем его затенения. После этого удалять элемент можно с помощью следующего кода:

FadeElementEffect fade = new FadeElementEffect(); 
fade.Animate(canvas);

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

Переход между страницами

Система навигации Silverlight предполагает создание средств перехода к другим страницам приложения. Для этого обычно используется базовый контейнер в качестве корневого элемента приложения. В базовый контейнер можно добавлять пользовательские элементы управления и по мере необходимости удалять их. Переход от одной страницы к другой состоит в удалении одних элементов управления и добавлении других.

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

Для реализации любого из этих методов нужно добавить обе страницы (старую и новую) в корневой элемент одну поверх другой. Легче всего это сделать, разместив обе страницы в одной ячейке Grid, но можно разместить их и в контейнере Canvas. Затем нужно анимировать свойства вышележащей страницы. Например, можно изменять свойство Opacity, чтобы плавно прорисовать страницу, или свойства объекта TranslateTransform для вывода замещающей линии. Можно даже создать ряд других эффектов, например расширять новую страницу с угла, одновременно осветляя ее.

Далее рассматривается создание эффекта занавеса, который плавно закрывает старую страницу и открывает новую (показано на рисунке ниже). На среднем рисунке выведено по половине каждой страницы - граница между страницами размыта.

Переход между страницами методом занавеса

Корневой элемент приложения — контейнер Grid, поэтому в классе Application необходим следующий код:

// Стартовый элемент управления
private Grid RootGrid = new Grid();

// Загрузка первой страницы 
private void Application_Startup(object sender, StartupEventArgs e)
{
      this.RootVisual = RootGrid;
      RootGrid.Children.Add(new Page1());
}

Анимировать переход на другую страницу легче всего путем кодирования перехода непосредственно в классе App с помощью метода Navigate(). Однако приложение будет более гибким (хотя его создание — немного более трудоемким), если поместить код анимации в отдельный класс. Стандартизировав анимацию в абстрактном классе или интерфейсе, вы получите возможность легко добавлять новые эффекты.

В рассматриваемом примере все переходы наследуют абстрактный класс PageTransitonBase, в полях которого хранятся раскадровка, предыдущая страница и новая страница:

public abstract class PageTransitionBase
{
        protected Storyboard storyboard = new Storyboard();
        protected UserControl oldPage;
        protected UserControl newPage;

        public PageTransitionBase()
        {
            storyboard.Completed += TransitionCompleted;
        }
        
    ...

Для перехода от одной страницы к другой приложение вызывает метод PageTransitionBase.Navigate(). Вызванный метод добавляет обе страницы в контейнер Grid, вызывает метод PrepareStoryboard(), чтобы подготовить раскадровку, и запускает анимацию:

...
   
public void Navigate(UserControl newPage)
{
            // Установка страниц
            this.newPage = newPage;
            Grid grid = (Grid)Application.Current.RootVisual;
            oldPage = (UserControl)grid.Children[0];

            // Вставка новой страницы
            grid.Children.Insert(0, newPage);

            // Подготовка анимации
            PrepareStoryboard();

            // Запуск анимации         
            storyboard.Begin();
}

...

Метод PrepareStoryboard() - абстрактней, поэтому его нужно переопределить в производном классе, создающем необходимый объект анимации. Обработчик события TransitionCompleted() реагирует на завершение анимации, удаляя старую страницу:

        ...

        private void TransitionCompleted(object sender, EventArgs e)
        {
            // Удаление старой страницы
            Grid grid = (Grid)Application.Current.RootVisual;
            grid.Children.Remove(oldPage);
        }

        // Абстрактный метод, создающий конфигурацию анимации
        protected abstract void PrepareStoryboard();
}

Этот же обработчик по необходимости можно использовать для очистки приложения. Однако в данном примере анимация обрабатывает старую страницу, которая после перехода отбрасывается, поэтому очистка не нужна.

Для реализации перехода на другую страницу методом занавеса необходим как минимум один производный класс, создающий объекты анимации. Здесь рассматривается класс WipeTransition, который плавно удаляет старую страницу, постепенно приоткрывая новую.

Эффект занавеса создается путем анимации кисти, в которой используется полупрозрачная маска OpacityMask. В данном примере в маске применяется кисть LinearGradientBrush. Анимация изменяет смещение маски, постепенно делая прозрачным вышележащий элемент (старую страницу) и открывая нижележащее содержимое (новую страницу). Обычно задают движение занавеса слева направо или сверху вниз, однако, используя разные маски, можно создавать более экзотические эффекты.

Класс WipeTransition переопределяет метод PrepareStoryboard(), главная задача которого — создать маску и добавить ее на старую страницу (вышележащий элемент контейнера Grid). В маске используется градиент с двумя элементами GradientStop. Цвет первого элемента — Black (изображение видно полностью), а второго — Transparent (изображение полностью прозрачное). Сначала оба смещения GradientStop позиционированы слева на странице. Элемент с цветом Black объявлен последним, поэтому его приоритет выше и сначала изображение непрозрачное.

Далее необходимо анимировать смещения кисти LinearGradientBrush. В данном примере оба смещения перемещаются слева направо, открывая нижележащее изображение. Чтобы край занавеса был размытым, смещения находятся не в одинаковых позициях. Смещение Transparent следует за смещением Black с интервалом 0.2 секунды.

Смещение Black доходит до точки 1.2, а не 1, т.е. немного дальше правого края изображения. Это необходимо для того, чтобы оба смещения двигались с одинаковой скоростью (пройденное расстояние должно быть пропорционально продолжительности анимации).

Обе анимации нужно добавить в раскадровку. Запускать раскадровку не нужно, потому что это происходит в базовом классе PageTransitionBase после возврата метода PrepareStoryboard():

public class WipeTransition : PageTransitionBase
{
        protected override void PrepareStoryboard()
        {
            // Создание прозрачной маски
            LinearGradientBrush mask = new LinearGradientBrush();
            mask.StartPoint = new Point(0, 0);
            mask.EndPoint = new Point(1, 0);

            GradientStop transparentStop = new GradientStop();
            transparentStop.Color = Colors.Transparent;
            transparentStop.Offset = 0;
            mask.GradientStops.Add(transparentStop);
            GradientStop visibleStop = new GradientStop();
            visibleStop.Color = Colors.Black;
            visibleStop.Offset = 0;
            mask.GradientStops.Add(visibleStop);

            oldPage.OpacityMask = mask;

            // Создание анимации для маски      
            DoubleAnimation visibleStopAnimation = new DoubleAnimation();
            Storyboard.SetTarget(visibleStopAnimation, visibleStop);
            Storyboard.SetTargetProperty(visibleStopAnimation, new PropertyPath("Offset"));
            visibleStopAnimation.Duration = TimeSpan.FromSeconds(1.2);
            visibleStopAnimation.From = 0;
            visibleStopAnimation.To = 1.2;

            DoubleAnimation transparentStopAnimation = new DoubleAnimation();
            Storyboard.SetTarget(transparentStopAnimation, transparentStop);
            Storyboard.SetTargetProperty(transparentStopAnimation, new PropertyPath("Offset"));
            transparentStopAnimation.BeginTime = TimeSpan.FromSeconds(0.2);
            transparentStopAnimation.From = 0;
            transparentStopAnimation.To = 1;
            transparentStopAnimation.Duration = TimeSpan.FromSeconds(1);

            // Добавление анимации в раскадровку
            storyboard.Children.Add(transparentStopAnimation);
            storyboard.Children.Add(visibleStopAnimation);
        }
}

Для перехода к другой странице используется следующий код (объявлен в коде страницы в обработчике клика по кнопке):

private void Button_Click(object sender, RoutedEventArgs e)
{
            WipeTransition transition = new WipeTransition();
            transition.Navigate(new Page2());
}

Есть много способов улучшить данную технологию:

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