Группы преобразований в WinRT

195

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

Одним из классов, производных от Transform, является класс TransformGroup. У него имеется свойство Children типа TransformCollection, которое может использоваться для построения сложного преобразования из нескольких потомков Transform. Преобразование RotateTransform можно определить следующим образом:

<RotateTransform Angle="A" CenterX="CX" CenterY="CY" />

где X, CX и CY - непосредственные данные привязки. Такое преобразование эквивалентно следующей группе TransformGroup:

<TransformGroup>
    <TranslateTransform X="-CX" Y="-CY" />
    <RotateTransform Angle="A" />
    <TranslateTransform X="CX" Y="CY" />
</TransformGroup>

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

Следующая программа ImageRotate содержит ссылку на растровое изображение с моего веб-сайта. Ширина изображения составляет 800 пикселов, а высота - 450 пикселов. Чтобы повернуть это изображение вокруг его центра преобразованием RotateTransform, мне бы следовало задать свойствам CenterX и CenterY половины этих величин (400 и 225), но вместо этого я использовал пару объектов TranslateTransform:

<Page ...>

    <Grid Background="#FF1D1D1D">
        <Image Source="http://professorweb.ru/my/windows8/rt/level1/files/win8logo.png"
               Stretch="None" VerticalAlignment="Center"
               HorizontalAlignment="Center">
            <Image.RenderTransform>
                <TransformGroup>
                    <TranslateTransform X="-400" Y="-220" />
                    <RotateTransform x:Name="rtTransform" />
                    <TranslateTransform X="400" Y="220" />
                </TransformGroup>
            </Image.RenderTransform>
        </Image>
    </Grid>

    <Page.Triggers>
        <EventTrigger>
            <BeginStoryboard>
                <Storyboard RepeatBehavior="Forever">
                    <DoubleAnimation Storyboard.TargetName="rtTransform"
                                     Storyboard.TargetProperty="Angle"
                                     From="0" To="360" Duration="0:0:2.8">
                        <DoubleAnimation.EasingFunction>
                            <ElasticEase EasingMode="EaseInOut" />
                        </DoubleAnimation.EasingFunction>
                    </DoubleAnimation>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Page.Triggers>
</Page>

Анимация ElasticEase в режиме EaseInOut заставляет изображение качаться туда-сюда после поворота, но из рисунка видно, что поворот осуществляется вокруг центра изображения:

Вращение по центру с помощью группы преобразований

На следующей иллюстрации изображены отдельные стадии процесса: самый светлый элемент TextBlock расположен в центре страницы. Следующий, чуть более темный элемент TextBlock демонстрирует эффект преобразования TranslateTransform, которое сдвигает TextBlock влево на половину ширины и вверх на половину высоты. Еще более темный элемент TextBlock поворачивается относительно своей базовой точки - левого верхнего угла исходного элемента TextBlock. Последний, черный элемент TextBlock сдвигается на половину ширины и высоты. Результат представляет собой исходный элемент TextBlock, повернутый относительно центра:

Демонстрация возможностей группы преобразований

А вот как выглядит файл XAML, создавший это изображение:

<Page ...>

    <Page.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Text" Value="Вращение относительно центра" />
            <Setter Property="FontSize" Value="40" />
            <Setter Property="VerticalAlignment" Value="Center" />
            <Setter Property="HorizontalAlignment" Value="Center" />
        </Style>
    </Page.Resources>

    <Grid Background="#FF1D1D1D">
        <TextBlock Name="txb" Foreground="#D0D0D0" />

        <TextBlock Foreground="#A0A0A0">
            <TextBlock.RenderTransform>
                <TranslateTransform x:Name="translateBack1" />
            </TextBlock.RenderTransform>
        </TextBlock>

        <TextBlock Foreground="#707070">
            <TextBlock.RenderTransform>
                <TransformGroup>
                    <TranslateTransform x:Name="translateBack2" />
                    <RotateTransform Angle="45" />
                </TransformGroup>
            </TextBlock.RenderTransform>
        </TextBlock>

        <TextBlock Foreground="{StaticResource ApplicationForegroundThemeBrush}">
            <TextBlock.RenderTransform>
                <TransformGroup>
                    <TranslateTransform x:Name="translateBack3" />
                    <RotateTransform Angle="45" />
                    <TranslateTransform x:Name="translate" />
                </TransformGroup>
            </TextBlock.RenderTransform>
        </TextBlock>
    </Grid>
</Page>

Значения X и Y для всех тегов TranslateTransform задаются из обработчика Loaded:

using Windows.UI.Xaml.Controls;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            Loaded += (sender, args) =>
            {
                translateBack1.X =
                translateBack2.X =
                translateBack3.X = -(translate.X = txb.ActualWidth / 2);

                translateBack1.Y =
                translateBack2.Y =
                translateBack3.Y = -(translate.Y = txb.ActualHeight / 2);
            };
        }
    }
}

Преобразования могут объединяться для создания очень интересных эффектов, которые на первый взгляд требуют применения математических вычислений и познаний в области компьютерной графики. В следующем файле XAML элемент Polygon определяет простую фигуру, похожую на пропеллер, после чего к этой фигуре применяются три преобразования: RotateTransform, TranslateTransform и снова RotateTransform:

<Page ...>

    <Grid Background="#FF1D1D1D">
        <Polygon Points="40   0,  60  0, 53 47, 100  40, 100 60, 53 53, 
                         60 100, 40 100, 47 53, 0  60,  0  40, 47 47"
                 Stroke="#FF1D1D1D"
                 Fill="LimeGreen"
                 VerticalAlignment="Center"
                 HorizontalAlignment="Center"
                 RenderTransformOrigin="0.5 0.5">
            <Polygon.RenderTransform>
                <TransformGroup>
                    <RotateTransform x:Name="rt1" />
                    <TranslateTransform X="300" />
                    <RotateTransform x:Name="rt2" />
                </TransformGroup>
            </Polygon.RenderTransform>
        </Polygon>
    </Grid>

    <Page.Triggers>
        <EventTrigger>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="rt1"
                                     Storyboard.TargetProperty="Angle"
                                     From="0" To="360" Duration="0:0:0.5"
                                     RepeatBehavior="Forever" />

                    <DoubleAnimation Storyboard.TargetName="rt2"
                                     Storyboard.TargetProperty="Angle"
                                     From="0" To="360" Duration="0:0:6"
                                     RepeatBehavior="Forever" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Page.Triggers>
</Page>

Storyboard содержит два объекта DoubleAnimation. Первый объект DoubleAnimation применяется к первому объекту RotateTransform для вращения пропеллера вокруг центра с частотой два оборота в секунду. TranslateTransform перемещает вращающийся пропеллер на 300 пикселов правее центра страницы, а второй объект DoubleAnimation применяется ко второму объекту RotateTransform для повторного вращения пропеллера. Однако на этот раз поворот происходит относительно исходного центра, поэтому пропеллер описывает вокруг центра страницы круги радиусом 300 пикселов с частотой 10 оборотов в минуту:

Вращение с перемещением элемента

Вероятно, теперь вам понятно, как работает свойство RenderTransformOrigin. Оно эквивалентно выполнению TranslateTransform с отрицательными значениями X и Y до преобразования, заданного свойством RenderTransform, и выполнению другого преобразования TranslateTransform с положительными значениями X и Y после RenderTransform.

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