Масштабирование и повороты относительно центра в WinRT

170

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

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

Файл XAML из проекта CenteredTransforms содержит ссылку на растровое изображение на моем сайте:

<Page ...>

    <Grid Background="#FF1D1D1D">
        <Image Name="image"
               Source="http://professorweb.ru/my/windows8/rt/level1/files/win8logo.png"
               Stretch="None"
               HorizontalAlignment="Left"
               VerticalAlignment="Top">
            <Image.RenderTransform>
                <TransformGroup x:Name="xformGroup">
                    <MatrixTransform x:Name="matrixXform" />
                    <CompositeTransform x:Name="compositeXform" />
                </TransformGroup>
            </Image.RenderTransform>
        </Image>
    </Grid>
</Page>

Обратите внимание: свойству RenderTransform задается группа TransformGroup с преобразованиями MatrixTransform и CompositeTransform. Файл фонового кода разрешает все формы Manipulation, кроме связанных с фиксацией перемещений:

using Windows.Foundation;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        bool manualChange = false;

        public MainPage()
        {
            this.InitializeComponent();

            image.ManipulationMode = ManipulationModes.All &
                                     ~ManipulationModes.TranslateRailsX &
                                     ~ManipulationModes.TranslateRailsY;
        }

        protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e)
        {
            // Преобразование назначается текущим полным преобразованием
            matrixXform.Matrix = xformGroup.Value;

            // Использование для преобразования свойства Position
            Point center = matrixXform.TransformPoint(e.Position);

            // Цент нового инкрементального преобразования
            compositeXform.CenterX = center.X;
            compositeXform.CenterY = center.Y;

            // Задание других свойств
            compositeXform.TranslateX = e.Delta.Translation.X;
            compositeXform.TranslateY = e.Delta.Translation.Y;
            compositeXform.ScaleX = e.Delta.Scale;
            compositeXform.ScaleY = e.Delta.Scale;
            compositeXform.Rotation = e.Delta.Rotation;

            base.OnManipulationDelta(e);
        }
    }
}

Переопределение OnManipulationDelta работает с тремя объектами преобразований, определенными в файле XAML. В любой момент времени свойство Value объекта TransformGroup (относящееся к типу Matrix) представляет полное преобразование, которое определяется как композиция преобразований, представляемых объектами MatrixTransform и CompositeTransform. Обработчик ManipulationDelta сначала задает значение Matrix из TransformGroup свойству MatrixTransform, вследствие чего MatrixTransform становится полным преобразованием на текущий момент. Это преобразование также применяется к свойству Position, которое становится свойствами CenterX и CenterY объекта CompositeTransform. Новые значения из структуры ManipulationDelta могут быть заданы непосредственно другим свойствам CompositeTransform.<,/

Работает ли такое решение? Попробуйте сами, потому что по следующему снимку экрана ничего сказать нельзя.

Центр поворота для сенсорных элементов

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

Чтобы немного упростить применение этого способа, я написал маленький класс с именем ManipulationManager, который выполняет необходимые вычисления с собственной коллекцией преобразований, созданных в конструкторе и сохраненных в полях:

using Windows.Foundation;
using Windows.UI.Input;
using Windows.UI.Xaml.Media;

namespace WinRTTestApp
{
    public class ManipulationManager
    {
        TransformGroup xTransformGroup;
        MatrixTransform matrixXTransform;
        CompositeTransform compositeXTransform;

        public ManipulationManager()
        {
            matrixXTransform = new MatrixTransform();
            xTransformGroup = new TransformGroup();
            xTransformGroup.Children.Add(matrixXTransform);
            compositeXTransform = new CompositeTransform();
            xTransformGroup.Children.Add(compositeXTransform);
            this.Matrix = Matrix.Identity;
        }

        public Matrix Matrix { private set; get; }

        public void AccumulateDelta(Point position, ManipulationDelta delta)
        {
            matrixXTransform.Matrix = xTransformGroup.Value;
            Point center = matrixXTransform.TransformPoint(position);
            compositeXTransform.CenterX = center.X;
            compositeXTransform.CenterY = center.Y;
            compositeXTransform.TranslateX = delta.Translation.X;
            compositeXTransform.TranslateY = delta.Translation.Y;
            compositeXTransform.ScaleX = delta.Scale;
            compositeXTransform.ScaleY = delta.Scale;
            compositeXTransform.Rotation = delta.Rotation;
            this.Matrix = xTransformGroup.Value;
        }
    }
}

Открытый метод AccumulateDelta непосредственно получает значение ManipulationDelta и вычисляет новое свойство Matrix. Это позволяет элементам, с которыми выполняются такие манипуляции, иметь всего одно преобразование:

<Page ...>

    <Grid Background="#FF1D1D1D">
        <Image Name="image"
               Source="http://professorweb.ru/my/windows8/rt/level1/files/win8logo.png"
               Stretch="None"
               HorizontalAlignment="Left"
               VerticalAlignment="Top">
            <Image.RenderTransform>
                <MatrixTransform x:Name="matrixXform" />
            </Image.RenderTransform>
        </Image>
    </Grid>
</Page>

Файл фонового кода создает экземпляр ManipulationManager и использует его для вычисления нового преобразования для Image:

using Windows.Foundation;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;

namespace WinRTTestApp
{
    public sealed partial class MainPage : Page
    {
        ManipulationManager mm = new ManipulationManager();

        public MainPage()
        {
            this.InitializeComponent();

            image.ManipulationMode = ManipulationModes.All &
                                     ~ManipulationModes.TranslateRailsX &
                                     ~ManipulationModes.TranslateRailsY;
        }

        protected override void OnManipulationDelta(ManipulationDeltaRoutedEventArgs e)
        {
            mm.AccumulateDelta(e.Position, e.Delta);
            matrixXform.Matrix = mm.Matrix;

            base.OnManipulationDelta(e);
        }
    }
}

Если на экране находятся несколько объектов, с которыми могут выполняться манипуляции, вы должны создать экземпляр ManipulationManager для каждого объекта. Позже, в проекте PhotoScatter будет использоваться разновидность ManipulationManager, которая отображает изображения из каталога Pictures и позволяет перебирать их движениями пальцев.

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