Урок 7. Анимация
В этом уроке ближе познакомимся с командой AnimatedValueSetCommand, которая позволяет анимировать изменение свойства объекта. Например, можно настроить выезд панели из-за края экрана, задав начальную и конечную точки для координаты Left или Right.
Давайте, создадим для нашего приложения боковое всплывающее меню, которое будет появляться с плавной анимацией и затемнением основного экрана.
Скачайте архив с изображениями и разархивируйте его в папку проекта \Template\Projects\1. Template\MobileForms\Images.
Боковое меню
На главном экране добавим кнопку, по которой будет выезжать справа на лево боковая панель меню. На этой панели разместим пункты меню для перехода в карточку пользователя и настроек приложения.
Удалим кнопку BackPictureBox, по которой возвращаемся на экран входа - для выхода из учетки будем использовать пункт меню Выйти, который создадим позже. Не забудем удалить условие и Execution, связанные с этой кнопкой.
Создадим иконку для вызова бокового меню, которую назовем MenuPictureBox:
<MyObject Name="MenuPictureBox" Type="PictureBox" Assembly="BaseControls">
<Top>
<Calculate>
<Expression>Ceiling(({0} - {1}) / 2)</Expression>
<Items>
<Item>
<Object Name="HeadPanel">
<Property Name="Height" />
</Object>
</Item>
<Item>
<Object Name="MenuPictureBox">
<Property Name="Height" />
</Object>
</Item>
</Items>
</Calculate>
</Top>
<Right>50</Right>
<Height>30</Height>
<Width>30</Width>
<Image>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>Images\menu-light.png</Then>
</Case>
<Case>Images\menu-dark.png</Case>
</Switch>
</Image>
</MyObject>Для HeadTitleLabel скорректируем координату Width с учетом MenuPictureBox. Если не исправить расчет ширины Label, то объект будет перекрывать иконку вызова меню.
Запустим приложение:


Теперь добавим описание бокового меню, которое будет состоять из:
панели InnerMenuPanel, содержащей описание пунктов меню;
панели FogPanel, создающей затемнение основного контента;
панели MenuPanel, являющейся контейнером для FogPanel и InnerMenuPanel.
<MyObject Name="MenuPanel" Type="Panel" Assembly="BaseControls">
<Top>0</Top>
<Left>0</Left>
<Height>
<Form>
<Property Name="Height" />
</Form>
</Height>
<Width>
<Form>
<Property Name="Width" />
</Form>
</Width>
<Visible>False</Visible>
<MyObject Name="FogPanel" Type="Panel" Assembly="BaseControls">
<Top>0</Top>
<Left>0</Left>
<Height>
<Object Name="MenuPanel">
<Property Name="Height" />
</Object>
</Height>
<Width>
<Object Name="MenuPanel">
<Property Name="Width" />
</Object>
</Width>
<Opacity>0</Opacity>
<BackColor>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>Black</Then>
</Case>
<Case>MostlyBlack</Case>
</Switch>
</BackColor>
</MyObject>
<MyObject Name="InnerMenuPanel" Type="Panel" Assembly="BaseControls">
<Top>0</Top>
<Left>
<Object Name="MenuPanel">
<Property Name="Width" />
</Object>
</Left>
<Height>
<Object Name="MenuPanel">
<Property Name="Height" />
</Object>
</Height>
<Width>
<Calculate>
<Expression>{0} - 56</Expression>
<Items>
<Item>
<Object Name="MenuPanel">
<Property Name="Width" />
</Object>
</Item>
</Items>
</Calculate>
</Width>
<BackColor>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>MostlyBlack</Then>
</Case>
<Case>VeryLightGray</Case>
</Switch>
</BackColor>
</MyObject>
</MyObject>Контейнер MenuPanel будем использовать для включения/выключения видимости всех элементов меню. По умолчанию для свойства Visible укажем значение False - элементы меню скрыты от пользователя и не перехватят фокус при тапе по экрану.
Панель FogPanel будет занимать все пространство формы, чтобы затемнять контент под меню. Для свойства Opacity укажем значение 0 - панель прозрачна на старте анимации появления бокового меню.
Панель InnerMenuPanel будет уже контейнера, что позволит видеть часть основного контента. Для свойства Left укажем значение равное ширине контейнера - панель находится за пределами экрана на старте анимации.
Отлично, теперь займемся настройкой анимации. И начнем с появления бокового меню.
Анимация
Показываем меню
Показывать боковое меню будем, если пользователь тапнул по иконке меню. Чтобы отловить это событие, создадим условие типа ClickCondition:
<Condition Name="MenuPictureBoxClickCondition" Type="ClickCondition" Assembly="Conditions">
<Object Name="MenuPictureBox" />
</Condition>Так как панель MenuPanel используем для управления видимостью всех элементов меню, то создадим команду для изменения значения свойства Visible:
<Command Name="MenuPanelVisibleValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Object Name="MenuPanel">
<Property Name="Visible">
<Input />
</Property>
</Object>
</Command>Теперь можем создать команду AnimatedValueSetCommand для анимации появления панели:
<Command Name="ShowMenuPanelAnimatedValueSetCommand" Type="AnimatedValueSetCommand" Assembly="Commands">
<Object Name="InnerMenuPanel">
<Property Name="Left" />
</Object>
<StartValue>
<Object Name="MenuPanel">
<Property Name="Width" />
</Object>
</StartValue>
<FinishValue>
<Formula>
<Minus DataType="IntegerDataType">
<Item>
<Object Name="MenuPanel">
<Property Name="Width" />
</Object>
</Item>
<Item>
<Object Name="InnerMenuPanel">
<Property Name="Width" />
</Object>
</Item>
</Minus>
</Formula>
</FinishValue>
<Length>200</Length>
<Easing>Linear</Easing>
</Command>Так как панель меню будет выезжать в правой части экрана, то будет удобнее менять значение свойства Left. В качестве начального значения в тэге <StartValue> укажем значение равное ширине контейнера - панель находится за пределами экрана. А в качестве конечного значения анимации в тэге <FinishValue> укажем разность координат Width контейнера MenuPanel и панели InnerMenuPanel.
В тэге <Length> укажем длительность анимации в миллисекундах, а в тэге <Easing> укажем имя режима скорости воспроизведения анимации - в нашем случае это будет воспроизведение с равномерной скоростью без скачков и ускорений.
Соберем условие и команды в Execution:
<Execution>
<ConditionExpression>
<Condition Name="MenuPictureBoxClickCondition" />
</ConditionExpression>
<Commands>
<Command Name="MenuPanelVisibleValueSetCommand">True</Command>
<Command Name="ShowMenuPanelAnimatedValueSetCommand" />
</Commands>
</Execution>Первым делом делаем видимым контейнер с элементами меню, а затем вызываем анимацию появления панели.
Скрываем меню
Создадим вторую команду AnimatedValueSetCommand для изменения значения свойства Left у панели InnerMenuPanel, чтобы панель плавно уезжала за границы экрана. Для этого значения в тэгах <StartValue> и <FinishValue> поменяем местами:
<Command Name="HideMenuPanelAnimatedValueSetCommand" Type="AnimatedValueSetCommand" Assembly="Commands">
<Object Name="InnerMenuPanel">
<Property Name="Left" />
</Object>
<StartValue>
<Formula>
<Minus DataType="IntegerDataType">
<Item>
<Object Name="MenuPanel">
<Property Name="Width" />
</Object>
</Item>
<Item>
<Object Name="InnerMenuPanel">
<Property Name="Width" />
</Object>
</Item>
</Minus>
</Formula>
</StartValue>
<FinishValue>
<Object Name="MenuPanel">
<Property Name="Width" />
</Object>
</FinishValue>
<Length>200</Length>
<Easing>Linear</Easing>
<FinishedCommands>
<Command Name="MenuPanelVisibleValueSetCommand">False</Command>
</FinishedCommands>
</Command>Стоит учитывать, что команды типа AnimatedValueSetCommand выполняются параллельно, а значит команды, описанные в последовательности команд после вызова команды AnimatedValueSetCommand, начнут выполнятся не дожидаясь завершения анимации.
В нашем случае, если в Execution вызывать команду MenuPanelVisibleValueSetCommand следом за вызовом команды HideMenuPanelAnimatedValueSetCommand, то элементы меню станут невидимыми еще до окончания анимации.
Поэтому команды типа AnimatedValueSetCommand обладают тэгом <FinishedCommands>, в котором указывается последовательность команд, выполняемая после завершения анимации.
Для закрытия бокового меню будем отслеживать жест смахивания панели InnerMenuPanel вправо. Для этого создадим событийное условие типа SwipeCondition:
<Condition Name="InnerMenuPanelSwipeRightCondition" Type="SwipeCondition" Assembly="Conditions">
<Object Name="InnerMenuPanel" />
<Direction>Right</Direction>
</Condition>В тэге <Object> указываем имя объекта, для которого будем отслеживать событие смахивания, а в тэге <Direction> - направление свайпа.
Соберем условие и команду в Execution:
<Execution>
<ConditionExpression>
<Condition Name="InnerMenuPanelSwipeRightCondition" />
</ConditionExpression>
<Commands>
<Command Name="HideMenuPanelAnimatedValueSetCommand" />
</Commands>
</Execution>Затенение экрана
Для создания эффекта плавного затемнения будем менять прозрачность панели FogPanel через ее set-проперти Opacity, используя команду типа AnimatedValueSetCommand:
<Command Name="FogPanelСhangeOpacityAnimatedValueSetCommand" Type="AnimatedValueSetCommand" Assembly="Commands">
<Object Name="FogPanel">
<Property Name="Opacity" />
</Object>
<StartValue>
<Switch>
<Case>
<When>
<Input Name="Show" />
</When>
<Then>0</Then>
</Case>
<Case>0.5</Case>
</Switch>
</StartValue>
<FinishValue>
<Switch>
<Case>
<When>
<Input Name="Show" />
</When>
<Then>0.5</Then>
</Case>
<Case>0</Case>
</Switch>
</FinishValue>
<Length>200</Length>
<Easing>Linear</Easing>
</Command>С помощью <Input Name="Show" /> будем определять, надо затемнять или убирать затемнение - менять местами начальное и конечное значение анимации.
Объединим новую команду и команду скрывания панели меню влево в одну последовательность:
<Command Name="HideMenuSequentialCommand" Type="SequentialCommand" Assembly="Commands">
<Commands>
<Command Name="FogPanelСhangeOpacityAnimatedValueSetCommand">
<Input Name="Show">False</Input>
</Command>
<Command Name="HideMenuPanelAnimatedValueSetCommand" />
</Commands>
</Command>Эту последовательность команд будем использовать на элементах меню, чтобы по возвращении с дочернего экрана, панель была скрыта.
Так же добавим возможность скрывать панель меню через тап по затемненному участку экрана, для этого создадим условие:
<Condition Name="MenuFogPanelClickCondition" Type="ClickCondition" Assembly="Conditions">
<Object Name="FogPanel" />
</Condition>Скорректируем Execution на отображение и сворачивание бокового меню:
<Execution>
<ConditionExpression>
<Condition Name="MenuPictureBoxClickCondition" />
</ConditionExpression>
<Commands>
<Command Name="MenuPanelVisibleValueSetCommand">True</Command>
<Command Name="FogPanelСhangeOpacityAnimatedValueSetCommand">
<Input Name="Show">True</Input>
</Command>
<Command Name="ShowMenuPanelAnimatedValueSetCommand" />
</Commands>
</Execution>
<Execution>
<ConditionExpression>
<Or>
<Condition Name="MenuFogPanelClickCondition" />
<Condition Name="InnerMenuPanelSwipeRightCondition" />
</Or>
</ConditionExpression>
<Commands>
<Command Name="HideMenuSequentialCommand" />
</Commands>
</Execution>Запустим приложение, чтобы проверить отображение бокового меню и затемнение фона.


Отлично! Теперь можем добавить элементы бокового меню.
Пункты меню
Добавим на форму описание стиля шрифта для заголовков пунктов меню:
<FontStyle Name="MenuItemFont" Font="Tahoma" Size="18" Bold="True" />Все пункты меню будем описывать внутри контейнера InnerMenuPanel. Каждый пункт будет представлен контейнером (Panel), содержащим иконку (PictureBox) и название (Label) пункта меню.
Добавим описание пункта меню Профиль, скопировав код:
<MyObject Name="ProfilePanel" Type="Panel" Assembly="BaseControls">
<Top>30</Top>
<Left>0</Left>
<Height>60</Height>
<Width>
<Object Name="InnerMenuPanel">
<Property Name="Width" />
</Object>
</Width>
<MyObject Name="ProfilePictureBox" Type="PictureBox" Assembly="BaseControls">
<Top>15</Top>
<Left>15</Left>
<Height>30</Height>
<Width>30</Width>
<Image>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>Images\account-light.png</Then>
</Case>
<Case>Images\account-dark.png</Case>
</Switch>
</Image>
</MyObject>
<MyObject Name="ProfileLabel" Type="Label" Assembly="BaseControls">
<Top>0</Top>
<Left>
<Calculate>
<Expression>{0} + 20</Expression>
<Items>
<Item>
<Object Name="ProfilePictureBox">
<Property Name="Right" />
</Object>
</Item>
</Items>
</Calculate>
</Left>
<Height>
<Object Name="ProfilePanel">
<Property Name="Height" />
</Object>
</Height>
<Width>
<Calculate>
<Expression>{0} - {1}</Expression>
<Items>
<Item>
<Object Name="ProfilePanel">
<Property Name="Width" />
</Object>
</Item>
<Item>
<Object Name="ProfileLabel">
<Property Name="Left" />
</Object>
</Item>
</Items>
</Calculate>
</Width>
<TextAlign>MiddleLeft</TextAlign>
<FontStyle>MenuItemFont</FontStyle>
<ForeColor>
<Switch>
<Case>
<When>
<Condition Name="AppThemeDarkEqualCondition" />
</When>
<Then>LightGray</Then>
</Case>
<Case>Gray</Case>
</Switch>
</ForeColor>
<Text>Профиль</Text>
</MyObject>
</MyObject>Запустим приложение и проверим отображение пункта меню:


Самостоятельно создайте оставшиеся пункты меню, чтобы у вас получился интерфейс:


Отлично! Давайте на пункт меню Настройки добавим команду перехода на экран настроек, форма для которого уже реализована в проекте.
Первым делом необходимо определить условие тапа по пункту меню:
<Condition Name="SettingsLabelClickCondition" Type="ClickCondition" Assembly="Conditions">
<Object Name="SettingsLabel" />
</Condition>
<Condition Name="SettingsPictureBoxClickCondition" Type="ClickCondition" Assembly="Conditions">
<Object Name="SettingsPictureBox" />
</Condition>
<Condition Name="SettingsMenuClickNestedCondition" Type="NestedCondition" Assembly="Conditions">
<AlwaysChange Value="True" />
<ConditionExpression>
<Or>
<Condition Name="SettingsPictureBoxClickCondition" />
<Condition Name="SettingsLabelClickCondition" />
</Or>
</ConditionExpression>
</Condition>Проверяем условие тапа по объектам Label и PictureBox потому, что объект Panel технически находится под своими элементами, которые перехватывают событие тапа. По этой причине Label растянули на высоту и на всю доступную ширину контейнера. Условие NestedCondition используем для удобства использования. Обратите внимание на использование тэга <AlwaysChange>, который переводит условие сравнения в событийное условие для корректного срабатывания Execution.
Самостоятельно добавьте на главную форму команду открытия формы TemplateSettings.xml.
Создадим команду для изменения цвета фона SettingsPanel, при выборе пункта меню Настройки:
<Command Name="SettingsPanelSetBackColorValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
<Object Name="SettingsPanel">
<Property Name="BackColor">
<Input />
</Property>
</Object>
</Command>С помощью ээтой команды будем имитировать нажатие кнопки
Теперь можем собрать Execution, который будет отслеживать тап по пункту Настройки и открывать соответствующий экран:
<Execution>
<ConditionExpression>
<Condition Name="SettingsMenuClickNestedCondition" />
</ConditionExpression>
<Commands>
<Command Name="SettingsPanelSetBackColorValueSetCommand">SoftBlue</Command>
<Command Name="SettingsFormShowCommand" />
<Command Name="SettingsPanelSetBackColorValueSetCommand">
<Object Name="InnerMenuPanel">
<Property Name="BackColor" />
</Object>
</Command>
<Command Name="HideMenuSequentialCommand" />
</Commands>
</Execution>Теперь стоит учесть момент, когда пользователь изменил адрес сервера и при сохранении необходимо выходит на стартовый экран приложения, чтобы подключится по новому адресу и получить новый список пользователей для входа в приложение.
Реализуйте самотоятельно эту логику.
Итоги
На уроке мы рассмотрели процесс аутентификации пользователя, .
Ответы
В архиве присутствуют xml-файлы форм для мобильного приложения и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.
Архив не содержит xml-файлы форм десктопного приложения.
Last updated