Главная » Разработка для Windows Phone 7 » Сплайны и ключевые кадры Windows Phone 7

0

Три класса ключевых кадров начинаются со слова Spline: SplineDoubleKeyFrame, SplinePointKeyFrame (Сплайновый ключевой кадр типа Point) и SplineColorKeyFrame (Сплайновый ключевой кадр типа Color). Эти классы имеют свойства KeyTime и Value, как и дискретные и линейные ключевые кадры, но также они определяют свойство KeySpline (Ключевой сплайн). Это свойство позволяет создавать ключевой кадр, ускоряющийся или замедляющийся (или и то, и другое) в ходе выполнения, но при этом завершающийся заданным значением в заданное KeyTime время. Изменением скорости управляет сплайн Безье.

KeySpline – это структура с двумя свойствами типа Point: ControlPoint1 (Опорная точка 1) и ControlPoint2. Координаты X и Y каждой из этих точек должны лежать в диапазоне от 0 до 1. Один объект KeySpline эффективно описывает кривую Безье, которая начинается в точке (0, 0) и заканчивается в точке (1, 1) с заданными двумя контрольными точками. Эти четыре точки полностью определяют кривую Безье, и создать произвольную кривую, петлю например, невозможно. Но мы увидим, что при этом обеспечивается достаточная гибкость.

Концептуально в ходе выполнения ключевого кадра координата X этого сплайна представляет нормализованное время, изменяющееся линейно в диапазоне от 0 до 1. Координата Y – это нормализованное значение анимации, также изменяющееся в диапазоне от 0 до 1, но нелинейным образом.

Безусловно, почувствовать сплайновые ключевые кадры можно, только поэкспериментировав с ними. Специально для этого у меняя припасена программка, которая даже называется SplineKeyFrameExperiment (Эксперимент со сплайновым ключевым кадром):

Перемещать опорные точки можно с помощью голубых полупрозрачных кругов. На ApplicationBar имеется только одна кнопка с подписью «animate» (анимация):

Проект Silverlight: SplineKeyFrameExperiment Файл: MainPage.xaml (фрагмент)

<phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar>

<shell:ApplicationBarIconButton IconUri="/Images/appbar.transport.play.rest.png"

Text="animate"

Click="OnAppbarAnimateButtonClick" />

</shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar>

При нажатии кнопки белый шарик внизу сетки начинает перемещаться линейно слева направо, представляя линейное увеличение времени. Белый шарик, расположенный справа сетки, перемещается нелинейно сверху вниз соответственно форме сплайна.

Для простоты компоновка приложения строится на базе сетки с фиксированной шириной и высотой в 400 пикселов, поэтому для меньшего экрана это приложение придется немного подкорректировать.

Область содержимого представляет собой серый квадрат со стороной 400 пикселов с горизонтальными и вертикальными линиями, равномерно проведенными через каждые 40 пикселов. Каждая линия сетки представляет 0,1 единицы для отображения сплайна.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

<Grid.RowDefinitions>

<RowDefinition Height="*" /> <RowDefinition Height="Auto" />

</Grid.RowDefinitions>

<Grid Name="graphGrid" Grid.Row="0"

HorizontalAlignment="Center" VerticalAlignment="Center">

<!— Фон —> <Path Fill="#808080"

Data="M 0 0 L 400 0, 400 400, 0 400 Z" />

<!– Горизонтальные линии –>

<Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 0 400 0" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 40 400 40" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 80 400 80" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 120 400 120" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 160 400 160" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 200 400 200" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 240 400 240" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 280 400 280" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 320 400 320" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 360 400 360" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}" Points="0 400 400 400" />

<!– Вертикальные линии — >

<Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="0 0 0 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="4 0 0 40 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="80 0 80 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="120 0 120 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="160 0 160 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="200 0 200 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="240 0 240 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="280 0 280 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="320 0 320 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}"

Points="3 60 0 360 400" /> <Polyline Stroke="{StaticResource PhoneForegroundBrush}" Points="4 0 0 0 400 400" />

</Grid>

<TextBlock Name="txtblk" Grid.Row="1" TextAlignment="Center"

Margin="0, 24" />

</Grid>

TextBlock внизу экрана используется для отображения значений двух опорных точек.

Ниже приведена разметка для кривой Безье, которая всегда начинается в верхнем левом углу сетки, который представляет точку (0, 0), и заканчивается в нижнем правом углу сетки, который представляет точку (1, 1). Две опорные точки (Point1 и Point2 объекта BezierSegment) задаются пользователем.

Данный фрагмент XAML также включает две касательные линии, соединяющие конечные точки с опорными. Я бы предпочел использовать привязку данных для связывания различных свойств этих элементов друг с другом, но в Silverlight 3 целями привязки могут быть только свойства элементов, а в данном случае свойства являются производными от PathSegment.

Проект Silverlight: SplineKeyFrameExperiment Файл: MainPage.xaml (фрагмент)

<!– Bezier curve –>

<Path Stroke="{StaticResource PhoneBackgroundBrush}"> <Path.Data>

<PathGeometry>

<PathFigure StartPoint="0 0">

<BezierSegment x:Name="bezierSegment" Point1="200 80" Point2="200 320" Point3="4 0 0 400" />

</PathFigure> </PathGeometry> </Path.Data> </Path>

<!– Tangent lines — >

<Path Stroke="{StaticResource PhoneAccentBrush}"> <Path.Data>

<PathGeometry>

<PathFigure StartPoint="0 0">

<LineSegment x:Name="tangentLine1" Point="200 80" />

</PathFigure> </PathGeometry> </Path.Data> </Path>

<Path Stroke="{StaticResource PhoneAccentBrush}"> <Path.Data>

<PathGeometry>

<PathFigure StartPoint="4 0 0 400">

<LineSegment x:Name="tangentLine2" Point="200 320" />

</PathFigure> </PathGeometry> </Path.Data> </Path>

В данном фрагменте описываются два небольших белых шарика, которые отображаются внизу и справа сетки. Один из них представляет время, и другой – анимируемый объект:

<!– Balls — >

<Path Fill="{StaticResource PhoneForegroundBrush}"> <Path.Data>

<EllipseGeometry x:Name="timeBall" RadiusX="10" RadiusY="10" Center="0 400" />

</Path.Data> </Path>

<Path Fill="{StaticResource PhoneForegroundBrush}"> <Path.Data>

<EllipseGeometry x:Name="animaBall" RadiusX="10" RadiusY="10" Center="4 0 0 0" />

</Path.Data> </Path>

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

Проект Silverlight: SplineKeyFrameExperiment Файл: MainPage.xaml (фрагмент)

<!– Tracking lines — > <Line x:Name="timeTrackLine"

Stroke="{StaticResource PhoneBackgroundBrush}" Y2="400" />

<Line x:Name="animaTrackLine"

Stroke="{StaticResource PhoneBackgroundBrush}" X2="400" />

Наконец, два полупрозрачных круга распознают касание и используются для перемещения опорных точек по сетке:

Проект Silverlight: SplineKeyFrameExperiment Файл: MainPage.xaml (фрагмент)

<!– Draggers — > <Path Name="dragger1"

Fill="{StaticResource PhoneAccentBrush}" Opacity="0.5"> <Path.Data>

<EllipseGeometry x:Name="dragger1Geometry" RadiusX="50" RadiusY="50" Center="200 80" />

</Path.Data> </Path>

<Path Name="dragger2"

Fill="{StaticResource PhoneAccentBrush}" Opacity="0.5"> <Path.Data>

<EllipseGeometry x:Name="dragger2Geometry" RadiusX="50" RadiusY="50" Center="200 320" />

</Path.Data> </Path>

Центры этих двух объектов EllipseGeometry обеспечивают две опорные точки для объекта KeySpline. В файле выделенного кода конструктор инициализирует располагающийся внизу экрана TextBlock значениями, нормализованными в диапазоне от 0 до 1:

Проект Silverlight: SplineKeyFrameExperiment Файл: MainPage.xaml.cs (фрагмент)

public partial class MainPage : PhoneApplicationPage {

public MainPage() {

InitializeComponent(); UpdateTextBlock();

}

void UpdateTextBlock() {

txtblk.Text = String.Format("pt1 = {0:F2}\npt2 = {1:F2}",

NormalizePoint(dragger1Geometry.Center), NormalizePoint(dragger2Geometry.Center));

}

Point NormalizePoint(Point pt) {

return new Point(pt.X / 400, pt.Y / 400);

}

}

В виду отсутствия привязок данных в XAML перегруженный метод OnManipulationDelta должен изменять два дополнительных свойства типа Point (кроме TextBlock) при каждом перемещении прозрачных кругов:

Проект Silverlight: SplineKeyFrameExperiment Файл: MainPage.xaml.cs (фрагмент)

protected override void OnManipulationDelta(ManipulationDeltaEventArgs args) {

Point translation = args.DeltaManipulation.Translation;

if (args.ManipulationContainer == dragger1) {

Point pt = new Point(Clamp(dragger1Geometry.Center.X + translation.X), Clamp(dragger1Geometry.Center.Y + translation.Y));

dragger1Geometry.Center = pt; bezierSegment.Point1 = pt; tangentLine1.Point = pt; UpdateTextBlock();

}

if (args.ManipulationContainer == dragger2) {

Point pt = new Point(Clamp(dragger2Geometry.Center.X + translation.X), Clamp(dragger2Geometry.Center.Y + translation.Y));

dragger2Geometry.Center = pt; bezierSegment.Point2 = pt; tangentLine2.Point = pt; UpdateTextBlock();

}

base.OnManipulationDelta(args);

}

double Clamp(double input)

return Math.Max(0, Math.Min(400, input));

}

При нажатии кнопки ApplicationBar приложение должно задать четырем разным анимациям одинаковые объекты KeySpline и затем запустить выполнение Storyboard:

Проект Silverlight: SplineKeyFrameExperiment Файл: MainPage.xaml.cs (фрагмент)

void OnAppbarAnimateButtonClick(object sender, EventArgs args) {

Point controlPoint1 = NormalizePoint(dragger1Geometry.Center); Point controlPoint2 = NormalizePoint(dragger2Geometry.Center);

splineKeyFrame1.KeySpline = new KeySpline(); splineKeyFrame1.KeySpline.ControlPoint1 = controlPoint1; splineKeyFrame1.KeySpline.ControlPoint2 = controlPoint2;

splineKeyFrame2.KeySpline = new KeySpline(); splineKeyFrame2.KeySpline.ControlPoint1 = controlPoint1; splineKeyFrame2.KeySpline.ControlPoint2 = controlPoint2;

splineKeyFrame3.KeySpline = new KeySpline(); splineKeyFrame3.KeySpline.ControlPoint1 = controlPoint1; splineKeyFrame3.KeySpline.ControlPoint2 = controlPoint2;

splineKeyFrame4.KeySpline = new KeySpline(); splineKeyFrame4.KeySpline.ControlPoint1 = controlPoint1; splineKeyFrame4.KeySpline.ControlPoint2 = controlPoint2;

storyboard.Begin();

}

Эта раскадровка определена в коллекции Resources страницы:

Проект Silverlight: SplineKeyFrameExperiment Файл: MainPage.xaml (фрагмент)

<phone:PhoneApplicationPage.Resources> <Storyboard x:Name="storyboard" SpeedRatio="0.25"> <PointAnimation Storyboard.TargetName="timeBall"

Storyboard.TargetProperty="Center"

From="0 400" To="400 400" Duration="0:0:1" />

<DoubleAnimation Storyboard.TargetName="timeTrackLine" Storyboard.TargetProperty="X1" From="0" To="4 0 0" Duration="0:0:1" />

<DoubleAnimation Storyboard.TargetName="timeTrackLine" Storyboard.TargetProperty="X2" From="0" To="4 0 0" Duration="0:0:1" />

<DoubleAnimation Storyboard.TargetName="animaTrackLine" Storyboard.TargetProperty="X1" From="0" To="4 0 0" Duration="0:0:1" />

<PointAnimationUsingKeyFrames Storyboard.TargetName="animaBall"

Storyboard.TargetProperty="Center"> <DiscretePointKeyFrame KeyTime="0:0:0" Value="400 0" /> <SplinePointKeyFrame x:Name="splineKeyFrame1"

KeyTime="0:0:1" Value="400 400" />

</PointAnimationUsingKeyFrames>

<DoubleAnimationUsingKeyFrames Storyboard.TargetName="timeTrackLine"

Storyboard.TargetProperty="Y1"> <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" /> <SplineDoubleKeyFrame x:Name="splineKeyFrame2"

KeyTime="0:0:1" Value="400" /> </DoubleAnimationUsingKeyFrames>

<DoubleAnimationUsingKeyFrames Storyboard.TargetName="animaTrackLine"

Storyboard.TargetProperty="Y1"> <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" /> <SplineDoubleKeyFrame x:Name="splineKeyFrame3"

KeyTime="0:0:1" Value="400" /> </DoubleAnimationUsingKeyFrames>

<DoubleAnimationUsingKeyFrames Storyboard.TargetName="animaTrackLine"

Storyboard.TargetProperty="Y2"> <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0" /> <SplineDoubleKeyFrame x:Name="splineKeyFrame4"

KeyTime="0:0:1" Value="400" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </phone:PhoneApplicationPage.Resources>

Итак, попробуем. Если поместить обе опорные точки в точку (1, 0), получим анимацию, стартующую очень медленно и ускоряющуюся к ее окончанию. Задание обеим опорным точкам координат (0, 1) обеспечивает обратный эффект. Зададим для первой опорной точки координаты (1, 0) и для второй – (0, 1), и получим анимацию, медленную в начале, затем ускоряющуюся и затем опять замедляющуюся в конце. Меняем местами значения координат опорных точек – и получаем обратный эффект.

Конечно, не обязательно использовать предельные значения. Задавая промежуточные величины, можно обеспечить более интересные эффекты. Например, значения (0.25, 0) и (0.6, 0.2) позволят смоделировать свободное падение. Для моделирования перемещения объекта вверх с последующим падением вследствие земного притяжения, вычтите из 1 каждую из этих координат.

Конечно же, у меня есть пример.

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии