Как добиться плавного анимированного эффекта при перетаскивании объекта в WPF - PullRequest
2 голосов
/ 06 февраля 2011

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

<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 начинается с полного размера до того, как анимация «роста» достигает ее, что вызывает внезапный скачок. Если вы отчаянно щелкаете мышью, размер объекта резко увеличивается, чего не должно быть.

Если вам нужно, вы можете посмотреть на мой сбойный код, который не работает .

Как правильно реализовать эти плавные движения в WPF?

Пожалуйста, не публикуйте ответ, не попробовав его сначала. Если есть внезапные скачки, результат неудовлетворительный. Спасибо!

Ответы [ 4 ]

3 голосов
/ 06 февраля 2011

Вместо создания нового 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 на Stop:

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 голосов
/ 06 февраля 2011

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

Вот хороший CodeProject , использующий его.

1 голос
/ 06 февраля 2011

Как уже упоминал 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)));
    }
}
1 голос
/ 06 февраля 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...