Масштабирование и повороты относительно центра в WinRT
170Разработка под Windows 8/10 --- Масштабирование и повороты относительно центра
Когда ранее мы рассматривали масштабирование и повороты в контексте событий 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 и позволяет перебирать их движениями пальцев.