Если вы были студентом технической специальности, то наверняка помните курс, посвященный конечным автоматам. Эта простая, но очень емкая модель (конечный автомат, он же finite state machine, он же FSM) используется довольно широко, хотя и большинство программистов о ней незаслуженно забывают. Сегодня мы поговорим о конечных автоматах и их применении в создании комплексных анимаций в приложениях на Xamarin.Forms.
Анимации в Xamarin.Forms
Если вы уже используете Xamarin.Forms в реальных проектах, то наверняка сталкивались со встроенным механизмом анимаций. Если нет, то рекомендуем начать знакомство со статей «Creating Animations with Xamarin.Forms» и «Compound Animations».
Чаще всего требуется анимировать следующие свойства:
- Scale, масштаб элемента;
- Opacity, прозрачность;
- Translation, дополнительное смещение по x, y, относительно полученного при компоновке положения;
- Rotation, вращение вокруг осей x, y, z.
В Xamarin.Forms для задания обозначенных свойств используются механизмы ОС низкого уровня, что отлично сказывается на производительности — нет проблем анимировать сразу целую кучу объектов. В нашем примере мы остановимся именно на этих свойствах, но при желании вы сможете самостоятельно расширить описанные ниже механизмы.
Конечные автоматы
Если описать конечный автомат человеческим языком, то это некий объект, который может находится в различных устойчивых состояниях (например, “загрузка” или “ошибка”). Свои состояния автомат меняет под воздействием внешних событий. Количество состояний конечно. Примерами таких автоматов являются лифты и светофоры.
Если вы по какой-то причине не учились в институте на технической специальности или не изучали теорию, то рекомендуем начать знакомство с этой статьи.
Какое это все имеет отношение к анимациям и тем более к Xamarin.Forms? Давайте посмотрим.
Один экран, много состояний, есть анимации перехода
В статье «Работаем с состояниями экранов в Xamarin.Forms» мы уже описывали компонент StateContainer, упрощающий разработку сложных интерфейсов и подходящий для большинства экранов в бизнес-приложениях. Этот компонент хорошо работает, когда у нас все состояния существуют независимо друг от друга и между ними достаточно простого перехода «один исчез — второй появился».
Но что делать, если необходимо реализовать комплексный и анимированный переход из одного состояния в другое? Чтобы выезжало, вращалось и прыгало.
В качестве примера давайте рассмотрим экран ввода адреса и работы с картой, как это реализуется в большинстве навигаторов.
Представим, что у нас анимированные переходы между следующими состояниями ОДНОГО экрана:
Как видим, у нас получается такой конечный автомат:
Необходимо реализовать следующие анимации при переходе из состояния в состояние:
- При входе в FindAddress нужно скрыть с анимацией старый контент и плавно показать новый. Плюс для пикантности будем анимировать кнопки во время появления;
- При переходе в ShowRoute необходимо скрыть старое состояние, а снизу экрана должна выехать табличка с информацией о маршруте;
- При переходе в Drive необходимо скрыть старое состояние и сверху должна выехать табличка с информацией о маршруте;
- При переходе в Main (кроме первого запуска) необходимо скрыть текущее состояние и плавно отобразить кнопку, добавим к ней также небольшую анимацию изменения масштаба.
Пишем свой автомат
Мы возьмем самую простую реализацию:
- У автомата есть фиксированный набор состояний, которые задаются при инициализации;
- Каждое из состояний описывается набором необходимых анимаций (конечные значения properties) для элементов UI;
- При входе в новое состояние параллельно запускаются все анимации из массива, добавленного при инициализации автомата.
Никакую историю переходов хранить не будем, также не важно по какому пользовательскому событию автомат перешел из одного состояния в другое. Есть только переход в новое состояние, который сопровождается анимациями.
Итак, простейший автомат, который мы назовем Storyboard, будет выглядеть следующим образом:
public enum AnimationType {
Scale,
Opacity,
TranslationX,
TranslationY,
Rotation
}
public class Storyboard {
readonly Dictionary<string, ViewTransition[]> _stateTransitions = new Dictionary<string, ViewTransition[]>();
public void Add(object state, ViewTransition[] viewTransitions) {
var stateStr = state?.ToString().ToUpperInvariant();
_stateTransitions.Add(stateStr, viewTransitions);
}
public void Go(object newState, bool withAnimation = true) {
var newStateStr = newState?.ToString().ToUpperInvariant();
// Get all ViewTransitions
var viewTransitions = _stateTransitions[newStateStr];
// Get transition tasks
var tasks = viewTransitions.Select(viewTransition => viewTransition.GetTransition(withAnimation));
// Run all transition tasks
Task.WhenAll(tasks);
}
}
public class ViewTransition {
// Skipped. See complete sample in repository below
public async Task GetTransition(bool withAnimation) {
VisualElement targetElement;
if( !_targetElementReference.TryGetTarget(out targetElement) )
throw new ObjectDisposedException("Target VisualElement was disposed");
if( _delay > 0 ) await Task.Delay(_delay);
withAnimation &= _length > 0;
switch ( _animationType ) {
case AnimationType.Scale:
if( withAnimation )
await targetElement.ScaleTo(_endValue, _length, _easing);
else
targetElement.Scale = _endValue;
break;
// See complete sample in repository below
default:
throw new ArgumentOutOfRangeException();
}
}
}
В примере выше опущены проверки входных данных, полную версию можете найти в репозитории (ссылка в конце статьи).
Как видим, при переходе в новое состояние просто в параллели происходят плавные изменения необходимых свойств. Есть также возможность перейти в новое состояние без анимации.
Используем конечный автомат
Итак, автомат у нас есть и мы можем подключить его для задания необходимых состояния элементов. Пример добавления нового состояния:
_storyboard.Add(States.Drive, new[] {
new ViewTransition(ShowRouteView, AnimationType.TranslationY, 200),
new ViewTransition(ShowRouteView, AnimationType.Opacity, 0, 0, delay: 250),
new ViewTransition(DriveView, AnimationType.TranslationY, 0, 300, delay: 250), // Active and visible
new ViewTransition(DriveView, AnimationType.Opacity, 1, 0) // Active and visible
});
Как видим, для состояния Drive мы задали массив индивидуальных анимаций. ShowRouteView и DriveView — обычные View, заданные в XAML, пример ниже.
А вот для перехода в новое состояние достаточно простоы вызвать метод Go():
_storyboard.Go(States.ShowRoute);
Кода получается относительно немного и групповые анимации создаются по факту просто набором чисел. Работать наш конечный автомат может не только со страницами, но и с отдельными View, что расширяет варианты его применения. Использовать Storyboard лучше внутри кода страницы (Page), не перемешивая его с бизнес-логикой.
Также приведем пример XAML, в котором описаны все элементы пользовательского интерфейса.
Если вы решите добавить возможность смены цвета элементов с помощью анимаций, то рекомендуем познакомиться с реализацией, описанной в статье «Building Custom Animations in Xamarin.Forms».
Полный код проекта из статьи вы можете найти в нашем репозитории:
https://bitbucket.org/binwell/statemachine.
И как всегда, задавайте ваши вопросы в комментариях. До связи!
Об авторе
Вячеслав Черников — руководитель отдела разработки компании Binwell, Microsoft MVP и Xamarin Certified Developer. В прошлом — один из Nokia Champion и Qt Certified Specialist, в настоящее время — специалист по платформам Xamarin и Azure. В сферу mobile пришел в 2005 году, с 2008 года занимается разработкой мобильных приложений: начинал с Symbian, Maemo, Meego, Windows Mobile, потом перешел на iOS, Android и Windows Phone. Статьи Вячеслава вы также можете прочитать в блоге на Medium.
Другие статьи автора:
- Что общего между конечными автоматами, анимацией и Xamarin.Forms
- 7 лучших ферм устройств для тестирования мобильных приложений
- Автоматизируем неавтоматизируемое, или про Xamarin в реальных проектах
- Авторизация OAuth для Xamarin-приложений
- Деплоим мобильный софт с помощью devops-конвейера Microsoft
- DevOps на службе человека
- Удобный REST для Xamarin-приложений
- Быстрое создание MVP (minimum viable product) на базе Microsoft Azure и Xamarin.Forms
- Готовим Xamarin.Forms: настройка окружения и первые шаги
- Повышаем эффективность работы в Xamarin.Forms
- Работаем с состояниями экранов в Xamarin.Forms
- Подключаем Facebook SDK для Xamarin.Forms
- Подключаем ВКонтакте SDK для Xamarin.Forms
Автор: Microsoft