Искажение элементов в WinRT

147

Ранее я упоминал о том, что все классы, производные от Transform, ограничиваются определением двумерных аффинных преобразований, а одной из характеристик аффинного преобразования является сохранение параллельности прямых. Однако аффинное преобразование не обязано сохранять углы между линиями. Например, оно может превратить квадрат в параллелограмм.

В Windows Runtime подобное преобразование называется «отклонением» или «искажением» (skew). Фигура последовательно «наклоняется» в вертикальном или горизонтальном направлении. В каком-то смысле отклонение является самым радикальным из всех аффинных преобразований, но и в нем сохраняется значительная доля исходной геометрии.

Искажение квадрата

В результате применения отклонения к кругу или эллипсу никогда не получится ничего, кроме эллипса:

Искажение эллипса

Аналогичным образом кривая Безье, подвергнутая отклонению, остается кривой Безье.

У класса SkewTransform имеются свойства AngleX и AngleY, которым задаются углы в градусах. Приведенные примеры были созданы преобразованием SkewTransform свойством AngleX, равным 45°, при котором нижняя часть объекта смещается вправо. Чтобы нижняя часть смещалась влево, задайте отрицательное значение угла. Для текста отрицательные значения AngleX создают эффект наклонного текста (похожего на курсив, но без оформительских изменений в символах). В следующем примере свойство AngleX равно -30°:

Искажение текста

С ненулевыми значениями AngleY отклонение происходит в вертикальном направлении. С положительными значениями AngleY правая сторона фигуры смещается вниз:

Искажение по оси Y

С отрицательными значениями правая сторона смещается вверх. По умолчанию левый верхний угол фигуры сохраняет прежнее положение, но это поведение можно изменить при помощи свойств CenterX и CenterY или свойства RenderTransformOrigin. Следующая программа демонстрирует, что происходит при объединении отклонений AngleX и AngleY:

<Page ...>

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="ИСКАЖЕНИЕ"
                   FontSize="160"
                   FontWeight="Bold"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   RenderTransformOrigin="0.5 0.5">
            <TextBlock.RenderTransform>
                <SkewTransform x:Name="skew" />
            </TextBlock.RenderTransform>
        </TextBlock>
    </Grid>

    <Page.Triggers>
        <EventTrigger>
            <BeginStoryboard>
                <Storyboard SpeedRatio="0.5" RepeatBehavior="Forever">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="skew"
                                                   Storyboard.TargetProperty="AngleX">

                        <!-- Туда и обратно за 4 секунды -->
                        <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
                        <LinearDoubleKeyFrame KeyTime="0:0:1" Value="90" />
                        <LinearDoubleKeyFrame KeyTime="0:0:2" Value="0" />
                        <LinearDoubleKeyFrame KeyTime="0:0:3" Value="-90" />
                        <LinearDoubleKeyFrame KeyTime="0:0:4" Value="0" />

                        <!-- Ничего не происходит 4 секунды -->
                        <DiscreteDoubleKeyFrame KeyTime="0:0:8" Value="0" />

                        <!-- Туда и обратно за 4 секунды -->
                        <LinearDoubleKeyFrame KeyTime="0:0:9" Value="90" />
                        <LinearDoubleKeyFrame KeyTime="0:0:10" Value="0" />
                        <LinearDoubleKeyFrame KeyTime="0:0:11" Value="-90" />
                        <LinearDoubleKeyFrame KeyTime="0:0:12" Value="0" />
                    </DoubleAnimationUsingKeyFrames>

                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="skew"
                                                   Storyboard.TargetProperty="AngleY">

                        <!-- Ничего не происходит 4 секунды -->
                        <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" />
                        <DiscreteDoubleKeyFrame KeyTime="0:0:4" Value="0" />

                        <!-- Туда и обратно за 4 секунды -->
                        <LinearDoubleKeyFrame KeyTime="0:0:5" Value="-90" />
                        <LinearDoubleKeyFrame KeyTime="0:0:6" Value="0" />
                        <LinearDoubleKeyFrame KeyTime="0:0:7" Value="90" />
                        <LinearDoubleKeyFrame KeyTime="0:0:8" Value="0" />

                        <!-- Туда и обратно за 4 секунды -->
                        <LinearDoubleKeyFrame KeyTime="0:0:9" Value="-90" />
                        <LinearDoubleKeyFrame KeyTime="0:0:10" Value="0" />
                        <LinearDoubleKeyFrame KeyTime="0:0:11" Value="90" />
                        <LinearDoubleKeyFrame KeyTime="0:0:12" Value="0" />
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Page.Triggers>
</Page>

Я задал свойству SpeedRatio объекта Storyboard значение 0.5, чтобы вам было удобнее следить за эффектами, но для обсуждения происходящего будет использоваться время, указанное в ключевых кадрах. За первые четыре секунды первая анимация изменяет свойство AngleX до 90°, потом обратно до нуля, снова до -90° и снова до нуля. За следующие четыре секунды вторая анимация изменяет свойство AngleY в диапазоне от -90 до 90. В последние четыре секунды две анимации работают вместе.

Как ни странно, такое объединение AngleX и AngleY приводит к повороту. Впрочем, в результате применения математических вычислений фигура также увеличивается.

Пример использования отклонения для текста

Отклонение часто используется для имитации трехмерной глубины, но оно лучше всего работает в сочетании с недеформированными элементами, как будет показано далее.

Эффектное появление

Иногда анимационное преобразование должно применяться к элементу при первой загрузке страницы. Например, элемент может «заехать» на экран со стороны и остановиться, или увеличиться в размерах, или опуститься откуда-то сверху.

Обычно проще начать с позиционирования элемента в его итоговом положении без преобразований. Затем можно переходить к определению преобразований и анимаций таким образом, чтобы элемент оказался в этом месте. Часто значение To в объекте DoubleAnimation преобразования можно опустить, потому что оно совпадает со значением по умолчанию, действующим до начала анимации.

Этот прием продемонстрирован в следующем примере. Для TextBlock определяются некоторые преобразования, но со значениями по умолчанию элемент просто находится в центре экрана. Это конечное положение и ориентация TextBlock - точка, в которой завершаются анимации:

<Page ...>

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="Привет!"
                   FontSize="160"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   RenderTransformOrigin="0.5 1">
            <TextBlock.RenderTransform>
                <TransformGroup>
                    <SkewTransform x:Name="skew" />
                    <TranslateTransform x:Name="translate" />
                </TransformGroup>
            </TextBlock.RenderTransform>
        </TextBlock>
    </Grid>

    <Page.Triggers>
        <EventTrigger>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="translate"
                                     Storyboard.TargetProperty="X"
                                     From="-1000" Duration="0:0:1" />

                    <DoubleAnimationUsingKeyFrames
                                     Storyboard.TargetName="skew"
                                     Storyboard.TargetProperty="AngleX">
                        <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="15" />
                        <LinearDoubleKeyFrame KeyTime="0:0:1" Value="30" />
                        <EasingDoubleKeyFrame KeyTime="0:0:1.5" Value="0">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <ElasticEase />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Page.Triggers>
</Page>

У анимации DoubleAnimation, примененной к TranslateTransform, значение From устанавливает начальное положение TextBlock на 1000 пикселов влево от его итогового положения. Отсутствие значения To означает, что анимация завершается на значении, предшествовавшем анимации, то есть 0.

В это время анимация DoubleAnimationUsingKeyFrames проводит анимацию значения AngleX от 15° до 30°, словно объект TextBlock притягивается к центру экрана. Последний ключевой кадр возвращает AngleX к значению 0, предшествовавшему анимации.

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