English 中文(简体)
在WPF中拖动对象时如何获得平滑的动画效果
原标题:How to achieve smooth animated effect when dragging an object in WPF
  • 时间:2011-02-06 15:05:31
  •  标签:
  • c#
  • wpf

我正在尝试学习一些WPF,我希望能够实现一个简单的游戏。在这个游戏中,画布上有一些项目。就这个问题而言,假设只有一个,它是椭圆

<Canvas Name="canvas">
    <Ellipse Name="ellipse" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Fill="GreenYellow"/>
</Canvas>

用户需要能够任意拖动这些项目。

因此,我实现了以下代码,它似乎可以工作:

public MainWindow()
{
    InitializeComponent();

    Canvas.SetLeft(ellipse, 0);
    Canvas.SetTop(ellipse, 0);

    ellipse.MouseDown += new MouseButtonEventHandler(ellipse_MouseDown);
    ellipse.MouseMove += new MouseEventHandler(ellipse_MouseMove);
    ellipse.MouseUp += new MouseButtonEventHandler(ellipse_MouseUp);
}

void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed)
        return;
    ellipse.CaptureMouse();
    ellipse.RenderTransform = new ScaleTransform(1.25, 1.25, ellipse.Width / 2, ellipse.Height / 2);
    ellipse.Opacity = 0.75;
}

void ellipse_MouseMove(object sender, MouseEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
        return;
    var pos = e.GetPosition(canvas);
    Canvas.SetLeft(ellipse, pos.X - ellipse.Width * 0.5);
    Canvas.SetTop(ellipse, pos.Y - ellipse.Height * 0.5);
}

void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
    if (!ellipse.IsMouseCaptured)
        return;
    ellipse.ReleaseMouseCapture();
    ellipse.RenderTransform = null;
    ellipse.Opacity = 1;
}

现在,如果你尝试这个,你会发现动作是非常锯齿状的。当鼠标向下移动时,椭圆会立即增长并立即更改其透明度。我想把它弄平,这样就不会有突然的跳跃

我已经在<code>Ellipse.OpacityProperty</code>、<code>ScaleTransform.ScaleXProperty<-code>和(特别是)<code>Canvas上使用了<code>DoubleAnimation</code〕。LeftProperty/TopProperty</code>。然而,我遇到了以下问题:

  • 一旦我在Canvas上开始动画。LeftProperty/TopProperty,我就再也不能使用Canvas。SetLeft/Top了,所以拖动椭圆时不会移动。我找不到从对象中删除动画的方法。

  • 如果用户在动画仍在进行时释放鼠标,则ScaleTransform上的“收缩”动画会在“增长”动画到达之前从全尺寸开始,从而导致突然跳跃。如果你疯狂地点击鼠标,对象的大小就会疯狂地跳跃,这是不应该的。

如果需要,您可以看看我失败的代码,它不起作用

如何在WPF中正确实现这些平滑运动

请不要在没有先尝试的情况下发布答案。如果有任何突然的跳跃,结果是不令人满意的。谢谢

最佳回答

不要为每个更改创建一个新的ScaleTransform,而是使用同一个并继续应用新的动画。如果没有为动画指定From属性,它将从当前值开始并执行平滑动画。

为了避免位置跳跃,请记住鼠标在椭圆内的位置,而不是始终将其居中。这样你就不必担心重新居中

在XAML中:

<Ellipse Name="ellipse" Width="100" Height="100"
         Stroke="Black" StrokeThickness="3" Fill="GreenYellow">
    <Ellipse.RenderTransform>
        <ScaleTransform x:Name="scale" CenterX="50" CenterY="50"/>
    </Ellipse.RenderTransform>
</Ellipse>

代码中:

private Point offsetInEllipse;

void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed)
        return;

    ellipse.CaptureMouse();
    offsetInEllipse = e.GetPosition(ellipse);

    var scaleAnimate = new DoubleAnimation(1.25,
        new Duration(TimeSpan.FromSeconds(1)));
    scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
    scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
}

void ellipse_MouseMove(object sender, MouseEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
        return;

    var pos = e.GetPosition(canvas);
    Canvas.SetLeft(ellipse, pos.X - offsetInEllipse.X);
    Canvas.SetTop(ellipse, pos.Y - offsetInEllipse.Y);
}

void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
{
    if (!ellipse.IsMouseCaptured)
        return;
    ellipse.ReleaseMouseCapture();

    var scaleAnimate = new DoubleAnimation(1,
        new Duration(TimeSpan.FromSeconds(1)));
    scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
    scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
}

how do I return Canvas.SetLeft to normal operation after having an animation on the Canvas.LeftProperty?

一种方法是设置填充行为以停止:

ellipse.BeginAnimation(Canvas.LeftProperty, new DoubleAnimation(
    pos.X - ellipse.Width * 0.5, 
    new Duration(TimeSpan.FromSeconds(1)), 
    FillBehavior.Stop));
Canvas.SetLeft(ellipse, pos.X - ellipse.Width * 0.5);

这将导致属性在动画结束后返回到其未设置动画的值。如果在开始动画后设置该值,则未设置动画的值将仅为最终值。

另一种方法是在完成后清除动画:

ellipse.BeginAnimation(Canvas.LeftProperty, null);

不过,这两种情况中的任何一种都会导致它在拖动时跳跃。您可以让拖动每次都启动一个新的动画,但这会让拖动感觉非常滞后。也许你想使用Canvas处理拖动。向左,但使用动画TranslateTransform处理平滑居中?

XAML:

<Ellipse.RenderTransform>
    <TransformGroup>
        <ScaleTransform x:Name="scale" CenterX="50" CenterY="50"/>
        <TranslateTransform x:Name="translate"/>
    </TransformGroup>
</Ellipse.RenderTransform>

代码:

void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed)
        return;

    ellipse.CaptureMouse();

    var scaleAnimate = new DoubleAnimation(1.25,
        new Duration(TimeSpan.FromSeconds(1)));
    scale.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
    scale.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);

    // We are going to move the center of the ellipse to the mouse
    // location immediately, so start the animation with a shift to
    // get it back to the current center and end the animation at 0.  
    var offsetInEllipse = e.GetPosition(ellipse);
    translate.BeginAnimation(TranslateTransform.XProperty, 
        new DoubleAnimation(ellipse.Width / 2 - offsetInEllipse.X, 0, 
            new Duration(TimeSpan.FromSeconds(1))));
    translate.BeginAnimation(TranslateTransform.YProperty, 
        new DoubleAnimation(ellipse.Height / 2 - offsetInEllipse.Y, 0, 
            new Duration(TimeSpan.FromSeconds(1))));

    MoveEllipse(e);
}

void ellipse_MouseMove(object sender, MouseEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
        return;

    MoveEllipse(e);
}

private void MoveEllipse(MouseEventArgs e)
{
    var pos = e.GetPosition(canvas);
    Canvas.SetLeft(ellipse, pos.X - ellipse.Width / 2);
    Canvas.SetTop(ellipse, pos.Y - ellipse.Height / 2);
}
问题回答

您可能应该查看拇指控件。

这是nice CodeProject使用它。

你可以得到一些有效果的动画,它会软化最初的拖动效果。。。。我担心拖动动画可能无法平滑,因为WPF不仅仅是为这类事情制作的,我认为你应该选择XNA:p

This video may suit your needs: http://windowsclient.net/learn/video.aspx?v=280279

正如Quartermeister已经提到的,您不应该为动画指定值中的。这样,动画将从当前值开始,并与当前正在执行的动画相结合。此外,您不应该每次都重新创建转换。

此外,我建议您使用TranslateTransform,而不是设置Canvas的Top/Left属性。它为您提供了移动灵活性,而且您不会被束缚在画布面板上。

所以,我得到的是:

XAML:

<Canvas Name="canvas">
    <Ellipse Name="ellipse" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Fill="GreenYellow"
             RenderTransformOrigin="0.5,0.5">
        <Ellipse.RenderTransform>
            <TransformGroup>
                <ScaleTransform />
                <TranslateTransform />
            </TransformGroup>                
        </Ellipse.RenderTransform>
    </Ellipse>
</Canvas>

隐藏代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Canvas.SetLeft(ellipse, 0);
        Canvas.SetTop(ellipse, 0);

        ellipse.MouseDown += new MouseButtonEventHandler(ellipse_MouseDown);
        ellipse.MouseMove += new MouseEventHandler(ellipse_MouseMove);
        ellipse.MouseUp += new MouseButtonEventHandler(ellipse_MouseUp);
    }

    private ScaleTransform EllipseScaleTransform
    {
        get { return (ScaleTransform)((TransformGroup)ellipse.RenderTransform).Children[0]; }
    }

    private TranslateTransform EllipseTranslateTransform
    {
        get { return (TranslateTransform)((TransformGroup)ellipse.RenderTransform).Children[1]; }
    }

    void ellipse_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.LeftButton != MouseButtonState.Pressed)
            return;

        ellipse.CaptureMouse();
        var pos = e.GetPosition(canvas);

        AnimateScaleTo(1.25);
    }

    void ellipse_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton != MouseButtonState.Pressed || !ellipse.IsMouseCaptured)
            return;

        var pos = e.GetPosition(canvas);

        AnimatePositionTo(pos);
    }

    void ellipse_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (!ellipse.IsMouseCaptured)
            return;
        ellipse.ReleaseMouseCapture();

        AnimateScaleTo(1);
    }

    private void AnimateScaleTo(double scale)
    {
        var animationDuration = TimeSpan.FromSeconds(1);
        var scaleAnimate = new DoubleAnimation(scale, new Duration(animationDuration));
        EllipseScaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimate);
        EllipseScaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimate);
    }

    private void AnimatePositionTo(Point pos)
    {
        var xOffset = pos.X - ellipse.Width * 0.5;
        var yOffset = pos.Y - ellipse.Height * 0.5;

        var animationDuration = TimeSpan.FromSeconds(1);

        EllipseTranslateTransform.BeginAnimation(TranslateTransform.XProperty,
            new DoubleAnimation(xOffset, new Duration(animationDuration)));

        EllipseTranslateTransform.BeginAnimation(TranslateTransform.YProperty,
            new DoubleAnimation(yOffset, new Duration(animationDuration)));
    }
}




相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...