2011-02-06 4 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; 
} 

Теперь, если вы попытаетесь это сделать, то вы увидите, что движения очень неровные. Когда вы нажимаете мыши, эллипс растет мгновенно и мгновенно меняет свою прозрачность. Я хочу сгладить это так, что есть никаких внезапных прыжков.

Я пробовал очевидные вещи, используя DoubleAnimation на Ellipse.OpacityProperty, ScaleTransform.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); 
} 
+0

Большое спасибо, лучший ответ до сих пор ... однако вы избежали проблемы центрирования объекта с помощью вместо того, чтобы фактически решить его. Вопрос все еще стоит: как мне вернуть 'Canvas.SetLeft' в нормальную работу после анимации на' Canvas.LeftProperty'? Я подозреваю, что такая же проблема возникает с «scale.ScaleX» в вашем решении ... – Timwi

+0

@Timwi: Зачем решать проблемы, когда их можно избежать? :) Получение Canvas.SetLeft обратно в нормальное состояние легко, но если вы сделаете это легко, тогда вы получите прыжок, когда вы сначала двигаете мышью. Я обновил свой ответ с идеей получить гладкое центрирование. – Quartermeister

+0

@Timwi: 'scale.ScaleX' отлично, потому что я никогда не устанавливал его напрямую, я просто продолжаю менять анимацию. – Quartermeister

2

Возможно, вы должны изучить управление Thumb.

Это nice CodeProject его использование.

+0

Итак ... Я должен был попробовать его первым ? –

+0

Зачем это делать? Его в значительной степени то, что он хочет: p – Machinarius

+0

Я не понимаю, почему этот ответ был пропущен, я использовал метод, показанный в этой статье проекта кода, и, похоже, сделал именно то, что было запрошено в этом вопросе. – mattythomas2000

1

Вы можете получить некоторые анимации с эффектами, и это смягчит начальный эффект перетаскивания ... Я боюсь, что анимация перетаскивания не может быть сглажена, поскольку WPF не был создан только для таких вещей, я думаю идти на XNA вместо: р

Это видео может удовлетворить ваши потребности: http://windowsclient.net/learn/video.aspx?v=280279

+1

Это примерно в десятый раз, когда я слышу, что «WPF не был создан для такого рода вещей». Что такое * * API-интерфейс анимации в WPF, сделанный для этого, если он не может даже выполнять самые основные анимации? – Timwi

+0

Не позволяйте упоминанию «игры» испортить ваше восприятие. «игра» автоматически не означает «XNA». Если я спрошу «как сделать кнопку в моей игре в WPF», вы наверняка не сразу предложите XNA? ... –

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> 

Code-за:

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))); 
    } 
} 
Смежные вопросы