C# 在WPF中拖动对象时如何实现平滑动画效果
我正在尝试学习一些WPF,我希望能够实现一个简单的游戏。在这个游戏中,C# 在WPF中拖动对象时如何实现平滑动画效果,c#,wpf,C#,Wpf,我正在尝试学习一些WPF,我希望能够实现一个简单的游戏。在这个游戏中,画布上有一些物品。对于这个问题,假设只有一个,它是一个椭圆: <Canvas Name="canvas"> <Ellipse Name="ellipse" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Fill="GreenYellow"/> </Canvas> 现在,如果你试试这个,你会发现动作非常参差不齐
画布上有一些物品。对于这个问题,假设只有一个,它是一个椭圆
:
<Canvas Name="canvas">
<Ellipse Name="ellipse" Width="100" Height="100" Stroke="Black" StrokeThickness="3" Fill="GreenYellow"/>
</Canvas>
现在,如果你试试这个,你会发现动作非常参差不齐。当您按下鼠标时,椭圆会立即增长并立即更改其透明度。我想把这一点弄平滑,这样就不会有突然的跳跃。
我在Ellipse.OpacityProperty
、ScaleTransform.ScaleProperty
和(特别是)Canvas.LeftProperty/TopProperty上使用了DoubleAnimation
,尝试了一些显而易见的事情。但是,我遇到了以下问题:
- 一旦我在
Canvas.LeftProperty/TopProperty
上开始动画,我就不能再使用Canvas.SetLeft/Top
,因此椭圆在拖动时不会移动。我找不到从对象中删除动画的方法
- 如果用户在动画仍在进行时释放鼠标,
ScaleTransform
上的“收缩”动画在“增长”动画到达之前从完整大小开始,这会导致突然跳跃。如果疯狂地单击鼠标,对象的大小会疯狂地跳跃,而它不应该这样做
如果你需要,你可以
如何在WPF中正确执行这些平滑动作?
请不要在没有尝试的情况下发布答案。如果有任何突然的跳跃,结果是不令人满意的。谢谢 您可能应该查看控件
这里有一个使用它的例子 你可以得到一些带有特效的动画,这会软化最初的拖动效果。。。。我担心拖拽动画可能不会平滑,因为WPF不仅仅是为这类事情制作的,我认为你应该选择XNA:p
此视频可能适合您的需要:
不要为每次更改创建新的ScaleTransform,而是使用相同的ScaleTransform并继续应用新动画。如果不为动画指定From
属性,它将从当前值开始并执行平滑动画
为避免位置跳过,请记住鼠标在椭圆内的位置,而不是始终将其居中。这样你就不用担心重新居中了。(您可以使用空时间线调用BeginAnimation来停止当前动画,但您只需在第一个鼠标移动中跳转即可。)
在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>
如何将Canvas.SetLeft
返回到
手术后的正常操作
画布上的动画。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.Left处理拖动,但是使用动画TranslateTransform处理平滑居中
XAML:
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scale" CenterX="50" CenterY="50"/>
<TranslateTransform x:Name="translate"/>
</TransformGroup>
</Ellipse.RenderTransform>
正如Quartermeister已经提到的,您不应该为动画指定from
值。这样,动画将从当前值开始,并与当前正在执行的动画相结合。此外,您不应该每次都重新创建转换
除此之外,我建议您使用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)));
}
}
为什么要投否决票?这正是他想要的:pI不明白为什么这个答案被否决了,我使用了代码项目文章中显示的方法,它似乎完全符合这个问题的要求。代码项目页面如何回答这个问题而不提及动画?即使我用拇指,那又如何回答任何问题呢?所有这些都依然存在。将我的答案与此混合,你就会得到你的应用:)动画处理平滑最初的拖动效果,这会平滑你的拖动。这大约是我第十次听说“WPF不是为这种事情而设计的”。如果WPF中的动画API连最基本的动画都做不到,那么它的用途是什么呢?不要因为提到“游戏”就破坏了你对它的理解。“游戏”并不自动意味着“XNA”。如果我问“我如何在我的WPF游戏中制作一个按钮”,你肯定不会立即建议XNA?…非常感谢,迄今为止最好的答案。。。但是,您避免了使用鼠标将对象居中的问题,而不是实际解决它。问题仍然存在:在Canvas.LeftProperty
上有动画后,如何将Canvas.SetLeft
返回到正常操作?我怀疑在您的解决方案中,scale.ScaleX
也会出现同样的问题…@Timwi:既然可以避免问题,为什么还要解决问题?:)让Canvas.SetLeft恢复正常是很容易的,但是如果您以简单的方式进行操作,那么当您第一次移动鼠标时,您会得到一个跳跃。我更新了我的答案,使其能够平滑居中。@Timwi:scale.ScaleX
很好,因为我从来没有直接设置它,我只是不断地更改动画。做得好,我很乐意为您所做的大编辑第二次更新它。只是想澄清一下:我关于scale.ScaleX的意思是,如果我试图直接设置它,同样的问题也会发生。但无论如何。谢谢你的帮助,你教了我很多关于WPF的知识。
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);
}
<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)));
}
}