【问题标题】:How can I animate an image along a circular path in WPF?如何在 WPF 中沿圆形路径为图像设置动画?
【发布时间】:2010-07-27 07:39:45
【问题描述】:

我需要在 WPF 中创建一个涉及图像的相当复杂的动画,其中图像必须沿着圆形路径移动。 Here is a picture of what it looks like...

这是我的 xaml:

<ControlTemplate x:Key="ValveStyle" TargetType="{x:Type CheckBox}">
  <ControlTemplate.Resources>
    <PathGeometry x:Key="CirclePathGeometry" PresentationOptions:Freeze="True">
      <PathGeometry.Figures>
        <PathFigureCollection>
          <PathFigure StartPoint="340,120">
            <PathFigure.Segments>
              <PathSegmentCollection>
                <ArcSegment Size="100,50" RotationAngle="0" IsLargeArc="False" SweepDirection="CounterClockwise"
                    Point="-30,120" />
              </PathSegmentCollection>
            </PathFigure.Segments>
          </PathFigure>
        </PathFigureCollection>
      </PathGeometry.Figures>
    </PathGeometry>
  </ControlTemplate.Resources>
  <Viewbox Stretch="Uniform">
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup x:Name="CheckStates">
        <VisualStateGroup.Transitions>
          <VisualTransition GeneratedDuration="00:00:00.2000000" />
        </VisualStateGroup.Transitions>
        <VisualState x:Name="Unchecked" />
        <VisualState x:Name="Checked">
          <Storyboard>

            <!--DoubleAnimationUsingPath BeginTime="00:00:00" Storyboard.TargetName="image3" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Source="X"
                DoubleAnimationUsingPath.PathGeometry="{StaticResource CirclePathGeometry}" />
        <DoubleAnimationUsingPath BeginTime="00:00:00" Storyboard.TargetName="image3" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)" Source="Y"
                DoubleAnimationUsingPath.PathGeometry="{StaticResource CirclePathGeometry}" /-->

            <MatrixAnimationUsingPath Storyboard.TargetName="Image3MatrixTransform" Storyboard.TargetProperty="Matrix"
                MatrixAnimationUsingPath.PathGeometry="{StaticResource CirclePathGeometry}" />
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000"
                Storyboard.TargetName="image1"
                Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-90" />
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000"
                Storyboard.TargetName="image2"
                Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-90" />
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000"
                Storyboard.TargetName="image"
                Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="-90" />
            </DoubleAnimationUsingKeyFrames>
          </Storyboard>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <VisualStateManager.CustomVisualStateManager>
      <ic:ExtendedVisualStateManager />
    </VisualStateManager.CustomVisualStateManager>
    <Canvas Width="685" Height="527">
      <Image Source="/VT;component/Images/Valve121.1-Base.png" Stretch="Fill" />
      <Image x:Name="image" Source="/VT;component/Images/Valve121.1-LeftBar1.png" Stretch="Fill"
          Canvas.Left="61.991" Canvas.Top="49.329" RenderTransformOrigin="0.286,0.5428">
        <Image.RenderTransform>
          <TransformGroup>
            <ScaleTransform />
            <SkewTransform />
            <RotateTransform />
            <TranslateTransform />
          </TransformGroup>
        </Image.RenderTransform>
      </Image>
      <Image x:Name="image2" Source="/VT;component/Images/Valve121.1-RightBar.png" Stretch="Fill"
          Canvas.Left="436.491" Canvas.Top="49.388" RenderTransformOrigin="0.249,0.756">
        <Image.RenderTransform>
          <TransformGroup>
            <ScaleTransform />
            <SkewTransform />
            <RotateTransform />
            <TranslateTransform />
          </TransformGroup>
        </Image.RenderTransform>
      </Image>
      <Image x:Name="image3" Source="/VT;component/Images/Valve121.1-HzBar.png" Stretch="Fill"
          Canvas.Left="277.491" Canvas.Top="58.345">
        <Image.RenderTransform>
          <MatrixTransform x:Name="Image3MatrixTransform" />
        </Image.RenderTransform>
      </Image>
      <Image Source="/VT;component/Images/Valve121.1-Overlay.png" Stretch="Fill" />
      <Image x:Name="image1" Source="/VT;component/Images/Valve121.1-LeftBar2.png" Stretch="Fill"
          Canvas.Left="129.824" Canvas.Top="24.661" RenderTransformOrigin="0.5,0.5">
        <Image.RenderTransform>
          <TransformGroup>
            <ScaleTransform />
            <SkewTransform />
            <RotateTransform />
            <TranslateTransform />
          </TransformGroup>
        </Image.RenderTransform>
      </Image>
      <Path Stroke="Blue" StrokeThickness="10" RenderTransformOrigin="-0.333,-0.227">
        <Path.Data>
          <PathGeometry>
            <PathFigureCollection>
              <PathFigure StartPoint="340,120">
                <PathFigure.Segments>
                  <PathSegmentCollection>
                    <ArcSegment Size="100,50" RotationAngle="0" IsLargeArc="False" SweepDirection="CounterClockwise"
                        Point="-30,120" />
                  </PathSegmentCollection>
                </PathFigure.Segments>
              </PathFigure>
            </PathFigureCollection>
          </PathGeometry>
        </Path.Data>
      </Path>
    </Canvas>
  </Viewbox>
</ControlTemplate>

我面临几个问题:

  • 图像 (image3) 似乎没有沿着我设置的路径正确移动,所以我想我在那里犯了一个错误......
  • 图像 (image3) 的动画在所有其他动画完成后开始。我希望所有动画同时开始。

DoubleAnimationUsingPathMatrixAnimationUsingPath 我都试过了,但都没有按我想要的方式工作。甚至可以混合关键帧动画和路径动画吗?

是否可以在 WPF 中制作这样的动画?如果是这样,我该如何实现?

【问题讨论】:

  • 我推荐使用 Expression Blend 来完成这些任务。网上有几个教程解释了如何解决你刚才提到的任务。我不喜欢使用 WYIWYG 应用程序,但对于动画或创建复杂几何图形,这些应用程序通常是黄金。
  • @Torsten:我已经在使用 Blend 并且我同意它是一个非常有用的工具,我无法想象没有它可以做这种事情 ;-)... 但是,这仍然是一些高级动画,我想我已经达到了 Blend 的极限,否则,我忽略了一个功能......
  • 好吧,我有点害怕你会说 XD 那么我相信你也知道可以并行地为对象设置动画。我记得使用本教程入门:kirupa.com/net/intro_blend_animation_pg1.htm 我的猜测是简单地为您的 image3 定义另一个时间线(或为每个对象定义一个单独的时间线)。分组还有助于让所有对象一起移动。也许这些提示又太琐碎了,我希望它们不是。

标签: c# wpf animation


【解决方案1】:

如下定义MainWindow(或Page)类:

<Window x:Class="TestCustomTab.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
    <Window.Resources>
        <PathGeometry x:Key="path" Figures="M 0,50 A 50,50 0 1 1 100,50 " />         
        <Storyboard x:Key="ellipseSB">
            <DoubleAnimationUsingPath Storyboard.TargetName="ellipse"
                                      Storyboard.TargetProperty="(Canvas.Left)"
                                      PathGeometry="{StaticResource path}"
                                      Duration="0:0:5" RepeatBehavior="Forever"
                                      AutoReverse="True" Source="X" />
            <DoubleAnimationUsingPath Storyboard.TargetName="ellipse"
                                      Storyboard.TargetProperty="(Canvas.Top)"
                                      PathGeometry="{StaticResource path}"
                                      Duration="0:0:5" RepeatBehavior="Forever"
                                      AutoReverse="True" Source="Y" />
        </Storyboard>        
    </Window.Resources>
    <Grid x:Name="grid">
        <Canvas>
            <Ellipse x:Name="ellipse" Width="10" Height="10" Stroke="Black" Fill="Black"
                     RenderTransformOrigin="0.5,0.5" />            
            <Path Stroke="Red" StrokeThickness="1" Canvas.Left="5" Canvas.Top="5"
                  Data="{StaticResource path}"/>
        </Canvas>        
        <Button VerticalAlignment="Bottom" Content="Start" Click="Button_Click"/>
    </Grid>
</Window>

然后在代码隐藏中定义 Button_Click 事件处理程序:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var sb = FindResource("ellipseSB") as Storyboard;
    if (sb != null) sb.Begin();
}

它将精确地沿定义的圆弧段(半圆)为 10×10 椭圆设置动画。

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    我也有同样的情况。尤金的例子很有帮助。我将其稍微扩展以形成完整的圆圈:

    <Window x:Class="TestCustomTab.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300">
        <Window.Resources>
            <PathGeometry x:Key="pathUpperHalf" Figures="M 0,50 A 50,50 0 1 1 100,50 " />
            <PathGeometry x:Key="pathLowerHalf" Figures="M 100,50 A 50,50 0 1 1 0,50" />
            <Storyboard x:Key="ellipseSB" RepeatBehavior="Forever">
                <DoubleAnimationUsingPath Storyboard.TargetName="ellipse"
                    Storyboard.TargetProperty="(Canvas.Left)"
                    PathGeometry="{StaticResource pathUpperHalf}"
                    Duration="0:0:1" RepeatBehavior="1x" AutoReverse="False" BeginTime="0:0:0" Source="X" />
                <DoubleAnimationUsingPath Storyboard.TargetName="ellipse"
                    Storyboard.TargetProperty="(Canvas.Top)"
                    PathGeometry="{StaticResource pathUpperHalf}"
                    Duration="0:0:1" RepeatBehavior="1x" AutoReverse="False" BeginTime="0:0:0" Source="Y" />
    
                <DoubleAnimationUsingPath Storyboard.TargetName="ellipse"
                    Storyboard.TargetProperty="(Canvas.Left)"
                    PathGeometry="{StaticResource pathLowerHalf}"
                    Duration="0:0:1" RepeatBehavior="1x" AutoReverse="False" BeginTime="0:0:1" Source="X" />
                <DoubleAnimationUsingPath Storyboard.TargetName="ellipse"
                    Storyboard.TargetProperty="(Canvas.Top)"
                    PathGeometry="{StaticResource pathLowerHalf}"
                    Duration="0:0:1" RepeatBehavior="1x" AutoReverse="False" BeginTime="0:0:1" Source="Y" />
            </Storyboard>
        </Window.Resources>
        <Grid x:Name="grid">
            <Canvas>
                <Ellipse x:Name="ellipse" Width="10" Height="10" Stroke="Black" Fill="Black" RenderTransformOrigin="0.5,0.5" />
                <Path Stroke="Blue" StrokeThickness="1" Canvas.Left="5" Canvas.Top="5" Data="{StaticResource pathUpperHalf}"/>
                <Path Stroke="Red" StrokeThickness="1" Canvas.Left="5" Canvas.Top="5" Data="{StaticResource pathLowerHalf}"/>
            </Canvas>
            <Button VerticalAlignment="Bottom" Content="Start" Click="Button_Click"/>
        </Grid>
    </Window>
    

    【讨论】:

    • FWIW:没有必要声明两对不同的动画来获得一个完整的圆圈。只需将原圆延长即可返回起点;唯一的“陷阱”是,如果您指定完全相同的点,则什么都不会绘制。但是您可以从其中一个坐标中减去一个非常小的量(例如 0.0001)(在上面的示例中,以接近结尾处变化最快的为准...Y),然后 WPF 就会知道要走很长的路.例如。使用Figures="M 0,50 A 50,50 0 1 1 0,49.9999"
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-27
    • 2021-07-05
    • 2020-11-03
    • 2019-03-22
    • 2021-08-01
    • 1970-01-01
    • 2010-11-11
    相关资源
    最近更新 更多