2011-02-06 101 views
2

我想學習一些WPF,希望能夠實現一個簡單的遊戲。在這個遊戲中,有一些項目在Canvas。對於這個問題的目的,讓我們說這裏只有一個,這是一個Ellipse如何在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; 
} 

現在,如果你試試這個,你會看到運動是非常參差不齊。當你向下滑動時,橢圓會立即增長並立即改變其透明度。我想平滑這一點,所以有沒有突然跳躍。

我已經嘗試使用明顯的事情一DoubleAnimationEllipse.OpacityPropertyScaleTransform.ScaleXProperty和(特別是)Canvas.LeftProperty/TopProperty。但是,我遇到了以下問題:

  • 只要我一開始就Canvas.LeftProperty/TopProperty動畫,我永遠不能再次使用Canvas.SetLeft/Top,所以它被拖到當橢圓不動。我找不到從對象中移除動畫的方法。

  • 如果用戶在動畫仍在發生的情況下釋放鼠標,ScaleTransform上的「縮小」動畫從「成長」動畫到達之前的全尺寸開始,這會導致突然跳躍。如果你瘋狂地點擊鼠標,對象的大小會瘋狂跳躍,而不應該跳躍。

如果需要,您可以look at my failed code, which doesn’t work

如何在WPF中正確實現這些平滑運動?

請不要發佈一個答案,而不要先嚐試它。如果有突然的跳躍,結果是不令人滿意的。謝謝!

回答

3

而不是創建的每個變化的新ScaleTransform的,使用同一個,並保持應用新的動畫。如果您沒有爲動畫指定From屬性,它將從當前值開始並執行平滑動畫。

要避免位置跳過,請記住鼠標在橢圓內的位置,而不是始終居中對齊。這樣你就不用擔心重新編輯它。(您可以撥打BeginAnimation使用空時間表,停止當前的動畫,但隨後你將只能收到第一的MouseMove跳躍。)

在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); 
} 

我怎麼有一個 後返回Canvas.SetLeft到 正常運行動畫Canvas.LeftProperty

一種方法是設置FillBehavior停止:

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> 

代碼:

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); 
} 
2

你可能應該看看Thumb控制。

這是一個nice CodeProject的用法。

+0

所以......我應該先嚐試過了? – 2011-02-06 15:39:03

+0

爲什麼downvoting它?它幾乎是他想要的東西:p – Machinarius 2011-02-06 16:03:39

+0

我不明白爲什麼這個答案被低估了,我已經使用了該代碼項目文章中顯示的方法,它似乎完全按照這個問題的要求進行。 – mattythomas2000 2011-02-06 16:43:50

1

正如Quartermeister已經提到的,你不應該指定動畫from值。這樣動畫將以當前值開始,並與當前正在執行的動畫結合。你也不應該每次都重新創建轉換。

除此之外,我建議您使用TranslateTransform而不是設置Top/Left屬性Canvas。它使您可以靈活移動,而且不受Canvas面板限制。

所以,這裏是我得到了什麼:

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))); 
    } 
}