使用 MatrixTransform 实现平滑动画?

发布于 2024-08-16 08:37:04 字数 2211 浏览 5 评论 0原文

我正在尝试制作一个矩阵动画​​,同时缩放和转置画布。我发现的唯一方法是使用 MatrixTransform 和 MatrixAnimationUsingKeyFrames。由于似乎没有任何内置矩阵插值(仅适用于路径/旋转),因此似乎唯一的选择是尝试自己构建插值和 DiscreteMatrixKeyFrame 。

我对此做了一个基本的实现,但它并不完全顺利,我不确定这是否是最好的方法以及如何处理帧速率等。有人有改进建议吗?这是代码:

        MatrixAnimationUsingKeyFrames anim = new MatrixAnimationUsingKeyFrames();
        int duration = 1;
        anim.KeyFrames = Interpolate(new Point(0, 0), centerPoint, 1, factor,100,duration);
        this.matrixTransform.BeginAnimation(MatrixTransform.MatrixProperty, anim,HandoffBehavior.Compose);


public MatrixKeyFrameCollection Interpolate(Point startPoint, Point endPoint, double startScale, double endScale, double framerate,double duration)
    {
        MatrixKeyFrameCollection keyframes = new MatrixKeyFrameCollection();

        double steps = duration * framerate;
        double milliSeconds = 1000 / framerate;
        double timeCounter = 0;



        double diffX = Math.Abs(startPoint.X-  endPoint.X);
        double xStep = diffX / steps;

        double diffY = Math.Abs(startPoint.Y - endPoint.Y);
        double yStep = diffY / steps;

        double diffScale= Math.Abs(startScale- endScale);
        double scaleStep = diffScale / steps;


        if (endPoint.Y < startPoint.Y)
        {
            yStep =  -yStep;
        }

        if (endPoint.X < startPoint.X)
        {
            xStep =  -xStep;
        }


        if (endScale < startScale)
        {
            scaleStep =  -scaleStep;
        }


        Point currentPoint = new Point();
        double currentScale = startScale;

        for (int i = 0; i < steps; i++)
        {
            keyframes.Add(new DiscreteMatrixKeyFrame(new Matrix(currentScale, 0, 0, currentScale, currentPoint.X, currentPoint.Y), KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(timeCounter))));
            currentPoint.X += xStep;
            currentPoint.Y += yStep;
            currentScale += scaleStep;
            timeCounter += milliSeconds;

        }

        keyframes.Add(new DiscreteMatrixKeyFrame(new Matrix(endScale, 0, 0, endScale, endPoint.X, endPoint.Y), KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0))));

        return keyframes;

    }

I'm trying to do an Matrix animation where I both scale and transpose a canvas at the same time. The only approach I found was using a MatrixTransform and MatrixAnimationUsingKeyFrames. Since there doesnt seem to be any interpolation for matrices built in (only for path/rotate) it seems the only choice is to try and build the interpolation and DiscreteMatrixKeyFrame's yourself.

I did a basic implementation of this but it isnt exactly smooth and I'm not sure if this is the best way and how to handle framerates etc. Anyone have suggestions for improvement? Here's the code:

        MatrixAnimationUsingKeyFrames anim = new MatrixAnimationUsingKeyFrames();
        int duration = 1;
        anim.KeyFrames = Interpolate(new Point(0, 0), centerPoint, 1, factor,100,duration);
        this.matrixTransform.BeginAnimation(MatrixTransform.MatrixProperty, anim,HandoffBehavior.Compose);


public MatrixKeyFrameCollection Interpolate(Point startPoint, Point endPoint, double startScale, double endScale, double framerate,double duration)
    {
        MatrixKeyFrameCollection keyframes = new MatrixKeyFrameCollection();

        double steps = duration * framerate;
        double milliSeconds = 1000 / framerate;
        double timeCounter = 0;



        double diffX = Math.Abs(startPoint.X-  endPoint.X);
        double xStep = diffX / steps;

        double diffY = Math.Abs(startPoint.Y - endPoint.Y);
        double yStep = diffY / steps;

        double diffScale= Math.Abs(startScale- endScale);
        double scaleStep = diffScale / steps;


        if (endPoint.Y < startPoint.Y)
        {
            yStep =  -yStep;
        }

        if (endPoint.X < startPoint.X)
        {
            xStep =  -xStep;
        }


        if (endScale < startScale)
        {
            scaleStep =  -scaleStep;
        }


        Point currentPoint = new Point();
        double currentScale = startScale;

        for (int i = 0; i < steps; i++)
        {
            keyframes.Add(new DiscreteMatrixKeyFrame(new Matrix(currentScale, 0, 0, currentScale, currentPoint.X, currentPoint.Y), KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(timeCounter))));
            currentPoint.X += xStep;
            currentPoint.Y += yStep;
            currentScale += scaleStep;
            timeCounter += milliSeconds;

        }

        keyframes.Add(new DiscreteMatrixKeyFrame(new Matrix(endScale, 0, 0, endScale, endPoint.X, endPoint.Y), KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0))));

        return keyframes;

    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

腹黑女流氓 2024-08-23 08:37:04

试试这个!只要你不旋转/剪切,它就能达到目的。

using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace MapControl
{
    public class LinearMatrixAnimation : AnimationTimeline
    {

        public Matrix? From
        {
            set { SetValue(FromProperty, value);}
            get { return (Matrix)GetValue(FromProperty); }
        }
        public static DependencyProperty FromProperty = DependencyProperty.Register("From", typeof(Matrix?), typeof(LinearMatrixAnimation), new PropertyMetadata(null));

        public Matrix? To
        {
            set { SetValue(ToProperty, value); }
            get { return (Matrix)GetValue(ToProperty); }
        }
        public static DependencyProperty ToProperty = DependencyProperty.Register("To", typeof(Matrix?), typeof(LinearMatrixAnimation), new PropertyMetadata(null));

        public LinearMatrixAnimation()
        {            
        }

        public LinearMatrixAnimation(Matrix from, Matrix to, Duration duration)
        {
            Duration = duration;
            From = from;
            To = to;
        }

        public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
        {
            if (animationClock.CurrentProgress == null)
            {
                return null;
            }

            double progress = animationClock.CurrentProgress.Value;
            Matrix from = From ?? (Matrix)defaultOriginValue;

            if (To.HasValue)
            {
                Matrix to = To.Value;
                Matrix newMatrix = new Matrix(((to.M11 - from.M11) * progress)+from.M11, 0, 0, ((to.M22 - from.M22) * progress)+from.M22,
                                              ((to.OffsetX - from.OffsetX) * progress) + from.OffsetX, ((to.OffsetY - from.OffsetY) * progress)+ from.OffsetY);
                return newMatrix;
            }

            return Matrix.Identity;
        }

        protected override System.Windows.Freezable CreateInstanceCore()
        {
            return new LinearMatrixAnimation();
        }

        public override System.Type  TargetPropertyType
        {
            get { return typeof(Matrix); }
        }
    }
}

Try this! As long as you aren't rotating/shearing it will do the trick.

using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace MapControl
{
    public class LinearMatrixAnimation : AnimationTimeline
    {

        public Matrix? From
        {
            set { SetValue(FromProperty, value);}
            get { return (Matrix)GetValue(FromProperty); }
        }
        public static DependencyProperty FromProperty = DependencyProperty.Register("From", typeof(Matrix?), typeof(LinearMatrixAnimation), new PropertyMetadata(null));

        public Matrix? To
        {
            set { SetValue(ToProperty, value); }
            get { return (Matrix)GetValue(ToProperty); }
        }
        public static DependencyProperty ToProperty = DependencyProperty.Register("To", typeof(Matrix?), typeof(LinearMatrixAnimation), new PropertyMetadata(null));

        public LinearMatrixAnimation()
        {            
        }

        public LinearMatrixAnimation(Matrix from, Matrix to, Duration duration)
        {
            Duration = duration;
            From = from;
            To = to;
        }

        public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
        {
            if (animationClock.CurrentProgress == null)
            {
                return null;
            }

            double progress = animationClock.CurrentProgress.Value;
            Matrix from = From ?? (Matrix)defaultOriginValue;

            if (To.HasValue)
            {
                Matrix to = To.Value;
                Matrix newMatrix = new Matrix(((to.M11 - from.M11) * progress)+from.M11, 0, 0, ((to.M22 - from.M22) * progress)+from.M22,
                                              ((to.OffsetX - from.OffsetX) * progress) + from.OffsetX, ((to.OffsetY - from.OffsetY) * progress)+ from.OffsetY);
                return newMatrix;
            }

            return Matrix.Identity;
        }

        protected override System.Windows.Freezable CreateInstanceCore()
        {
            return new LinearMatrixAnimation();
        }

        public override System.Type  TargetPropertyType
        {
            get { return typeof(Matrix); }
        }
    }
}
街道布景 2024-08-23 08:37:04

我已经实现了 MatrixAnimation 类,它支持平滑的平移、缩放和旋转动画。它还支持缓动功能! 此处找到它

I have implemented MatrixAnimation class which supports smooth translation, scaling and rotation animations. It also supports easing functions! Find it here

纵山崖 2024-08-23 08:37:04

好吧,如果您在 MSDN

http: //msdn.microsoft.com/en-us/library/system.windows.media.animation.discretematrixkeyframe.aspx

您将得到 DiscreteMatrixKeyFrame 导致的答案
突然的变化,你应该使用 LinearDoubleKeyFrame
或 SplineDoubleKeyFrame 与源代码一起!

编辑:啊,我明白了,矩阵变换仅支持离散
转换,所以实际上你有跳跃的问题。
所以我建议使用 RectAnimationUsingKeyFrames

// Create a RectAnimationUsingKeyFrames to
// animate the RectangleGeometry.
RectAnimationUsingKeyFrames rectAnimation = new RectAnimationUsingKeyFrames();
rectAnimation.Duration = TimeSpan.FromSeconds(timeInSeconds);

// Animate position, width, and height in first 2 seconds. LinearRectKeyFrame creates
// a smooth, linear animation between values.
rectAnimation.KeyFrames.Add(
                new LinearRectKeyFrame(
                    new Rect(600,50,200,50), // Target value (KeyValue)
                    KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2))) // KeyTime
                );

// In the next half second, change height to 10. 
   rectAnimation.KeyFrames.Add(
                new LinearRectKeyFrame(
                    new Rect(600, 50, 200, 10), // Target value (KeyValue)
                    KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2.5))) // KeyTime
                );

只需使用 Linear 或 SplineRectKeyFrame,设置持续时间/关键时间和您想要的值
需要。要获得比例,您需要计算末端宽度/高度并设置它,但这应该不是问题。

Well, if you ask that in MSDN

http://msdn.microsoft.com/en-us/library/system.windows.media.animation.discretematrixkeyframe.aspx

you are getting the answer that DiscreteMatrixKeyFrame causes
abrupt changes and that you should use LinearDoubleKeyFrame
or SplineDoubleKeyFrame together with source code !

EDIT: Ah, I see, Matrix transformations supports only discrete
transformations, so you have in fact a problem with jumps.
So what I propose is using a RectAnimationUsingKeyFrames

// Create a RectAnimationUsingKeyFrames to
// animate the RectangleGeometry.
RectAnimationUsingKeyFrames rectAnimation = new RectAnimationUsingKeyFrames();
rectAnimation.Duration = TimeSpan.FromSeconds(timeInSeconds);

// Animate position, width, and height in first 2 seconds. LinearRectKeyFrame creates
// a smooth, linear animation between values.
rectAnimation.KeyFrames.Add(
                new LinearRectKeyFrame(
                    new Rect(600,50,200,50), // Target value (KeyValue)
                    KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2))) // KeyTime
                );

// In the next half second, change height to 10. 
   rectAnimation.KeyFrames.Add(
                new LinearRectKeyFrame(
                    new Rect(600, 50, 200, 10), // Target value (KeyValue)
                    KeyTime.FromTimeSpan(TimeSpan.FromSeconds(2.5))) // KeyTime
                );

Simply use a Linear or SplineRectKeyFrame, set the duration/Keytime and the values you
need. To get the scale, you need to compute the end width/height and set it, but that shouldn't be a problem.

眸中客 2024-08-23 08:37:04

我喜欢@LukeN 的回答。对于简单的平移/缩放动画效果很好。
我向此代码添加了缓动(尽管是手工制作的,不是 WPF 本机缓动)。

private double Sigmoid(double v)
{
    double t = -6 + (v * 12.0);
    return 1.0 / (1.0 + Math.Exp(-t));
}

private double EaseIn(double v)
{
    return 2.0 * Sigmoid(v/2.0);
}

private double EaseOut(double v)
{
    return 2.0 * ( Sigmoid(0.5 + v/2.0) - 0.5);
}

然后在 GetCurrentValue 中执行 progress = Sigmoid(progress)EaseIn(progress)...

I like @LukeN answer. Works nice for simple translate/scale animations.
I added easing to this code (hand made though, not WPF native easing).

private double Sigmoid(double v)
{
    double t = -6 + (v * 12.0);
    return 1.0 / (1.0 + Math.Exp(-t));
}

private double EaseIn(double v)
{
    return 2.0 * Sigmoid(v/2.0);
}

private double EaseOut(double v)
{
    return 2.0 * ( Sigmoid(0.5 + v/2.0) - 0.5);
}

And then in GetCurrentValue do progress = Sigmoid(progress) or EaseIn(progress)...

酷遇一生 2024-08-23 08:37:04

如果将来无法访问 @pwlodek 链接,并且为了不要忘记他很棒的课程,我将其复制到 SO:

//http://pwlodek.blogspot.com/2010/12/matrixanimation-for-wpf.html
public class MatrixAnimation : MatrixAnimationBase
{
    public Matrix? From
    {
        set { SetValue(FromProperty, value); }
        get { return (Matrix)GetValue(FromProperty); }
    }

    public static DependencyProperty FromProperty =
        DependencyProperty.Register("From", typeof(Matrix?), typeof(MatrixAnimation),
            new PropertyMetadata(null));

    public Matrix? To
    {
        set { SetValue(ToProperty, value); }
        get { return (Matrix)GetValue(ToProperty); }
    }

    public static DependencyProperty ToProperty =
        DependencyProperty.Register("To", typeof(Matrix?), typeof(MatrixAnimation),
            new PropertyMetadata(null));

    public IEasingFunction EasingFunction
    {
        get { return (IEasingFunction)GetValue(EasingFunctionProperty); }
        set { SetValue(EasingFunctionProperty, value); }
    }

    public static readonly DependencyProperty EasingFunctionProperty =
        DependencyProperty.Register("EasingFunction", typeof(IEasingFunction), typeof(MatrixAnimation),
            new UIPropertyMetadata(null));

    public MatrixAnimation()
    {
    }

    public MatrixAnimation(Matrix toValue, Duration duration)
    {
        To = toValue;
        Duration = duration;
    }

    public MatrixAnimation(Matrix toValue, Duration duration, FillBehavior fillBehavior)
    {
        To = toValue;
        Duration = duration;
        FillBehavior = fillBehavior;
    }

    public MatrixAnimation(Matrix fromValue, Matrix toValue, Duration duration)
    {
        From = fromValue;
        To = toValue;
        Duration = duration;
    }

    public MatrixAnimation(Matrix fromValue, Matrix toValue, Duration duration, FillBehavior fillBehavior)
    {
        From = fromValue;
        To = toValue;
        Duration = duration;
        FillBehavior = fillBehavior;
    }

    protected override Freezable CreateInstanceCore()
    {
        return new MatrixAnimation();
    }

    protected override Matrix GetCurrentValueCore(Matrix defaultOriginValue, Matrix defaultDestinationValue, AnimationClock animationClock)
    {
        if (animationClock.CurrentProgress == null)
        {
            return Matrix.Identity;
        }

        var normalizedTime = animationClock.CurrentProgress.Value;
        if (EasingFunction != null)
        {
            normalizedTime = EasingFunction.Ease(normalizedTime);
        }

        var from = From ?? defaultOriginValue;
        var to = To ?? defaultDestinationValue;

        var newMatrix = new Matrix(
                ((to.M11 - from.M11) * normalizedTime) + from.M11,
                ((to.M12 - from.M12) * normalizedTime) + from.M12,
                ((to.M21 - from.M21) * normalizedTime) + from.M21,
                ((to.M22 - from.M22) * normalizedTime) + from.M22,
                ((to.OffsetX - from.OffsetX) * normalizedTime) + from.OffsetX,
                ((to.OffsetY - from.OffsetY) * normalizedTime) + from.OffsetY);

        return newMatrix;
    }
}

后面代码的示例用法:

    protected void AnimateMatrix(MatrixTransform matrixTransform, Matrix toMatrix, TimeSpan duration)
    {
        if (matrixTransform is MatrixTransform mt && toMatrix is Matrix to && duration is TimeSpan ts)
            AnimateMatrix(mt, mt.Matrix, to, duration);
    }
    protected void AnimateMatrix(MatrixTransform matrixTransform, Matrix? fromMatrix, Matrix? toMatrix, TimeSpan duration)
    {
        if (matrixTransform is MatrixTransform mt)
            mt.BeginAnimation(MatrixTransform.MatrixProperty, GetMatrixAnimation(fromMatrix, toMatrix, duration));
    }
    private MatrixAnimation GetMatrixAnimation(Matrix? fromMatrix, Matrix? toMatrix, TimeSpan duration)
    {
        return new MatrixAnimation(fromMatrix ?? Matrix.Identity, toMatrix ?? Matrix.Identity, new Duration(duration));
    }

In case of @pwlodek link will not reachable in the future and to not forget his awesome class, I copied into SO:

//http://pwlodek.blogspot.com/2010/12/matrixanimation-for-wpf.html
public class MatrixAnimation : MatrixAnimationBase
{
    public Matrix? From
    {
        set { SetValue(FromProperty, value); }
        get { return (Matrix)GetValue(FromProperty); }
    }

    public static DependencyProperty FromProperty =
        DependencyProperty.Register("From", typeof(Matrix?), typeof(MatrixAnimation),
            new PropertyMetadata(null));

    public Matrix? To
    {
        set { SetValue(ToProperty, value); }
        get { return (Matrix)GetValue(ToProperty); }
    }

    public static DependencyProperty ToProperty =
        DependencyProperty.Register("To", typeof(Matrix?), typeof(MatrixAnimation),
            new PropertyMetadata(null));

    public IEasingFunction EasingFunction
    {
        get { return (IEasingFunction)GetValue(EasingFunctionProperty); }
        set { SetValue(EasingFunctionProperty, value); }
    }

    public static readonly DependencyProperty EasingFunctionProperty =
        DependencyProperty.Register("EasingFunction", typeof(IEasingFunction), typeof(MatrixAnimation),
            new UIPropertyMetadata(null));

    public MatrixAnimation()
    {
    }

    public MatrixAnimation(Matrix toValue, Duration duration)
    {
        To = toValue;
        Duration = duration;
    }

    public MatrixAnimation(Matrix toValue, Duration duration, FillBehavior fillBehavior)
    {
        To = toValue;
        Duration = duration;
        FillBehavior = fillBehavior;
    }

    public MatrixAnimation(Matrix fromValue, Matrix toValue, Duration duration)
    {
        From = fromValue;
        To = toValue;
        Duration = duration;
    }

    public MatrixAnimation(Matrix fromValue, Matrix toValue, Duration duration, FillBehavior fillBehavior)
    {
        From = fromValue;
        To = toValue;
        Duration = duration;
        FillBehavior = fillBehavior;
    }

    protected override Freezable CreateInstanceCore()
    {
        return new MatrixAnimation();
    }

    protected override Matrix GetCurrentValueCore(Matrix defaultOriginValue, Matrix defaultDestinationValue, AnimationClock animationClock)
    {
        if (animationClock.CurrentProgress == null)
        {
            return Matrix.Identity;
        }

        var normalizedTime = animationClock.CurrentProgress.Value;
        if (EasingFunction != null)
        {
            normalizedTime = EasingFunction.Ease(normalizedTime);
        }

        var from = From ?? defaultOriginValue;
        var to = To ?? defaultDestinationValue;

        var newMatrix = new Matrix(
                ((to.M11 - from.M11) * normalizedTime) + from.M11,
                ((to.M12 - from.M12) * normalizedTime) + from.M12,
                ((to.M21 - from.M21) * normalizedTime) + from.M21,
                ((to.M22 - from.M22) * normalizedTime) + from.M22,
                ((to.OffsetX - from.OffsetX) * normalizedTime) + from.OffsetX,
                ((to.OffsetY - from.OffsetY) * normalizedTime) + from.OffsetY);

        return newMatrix;
    }
}

Sample usage for code behind:

    protected void AnimateMatrix(MatrixTransform matrixTransform, Matrix toMatrix, TimeSpan duration)
    {
        if (matrixTransform is MatrixTransform mt && toMatrix is Matrix to && duration is TimeSpan ts)
            AnimateMatrix(mt, mt.Matrix, to, duration);
    }
    protected void AnimateMatrix(MatrixTransform matrixTransform, Matrix? fromMatrix, Matrix? toMatrix, TimeSpan duration)
    {
        if (matrixTransform is MatrixTransform mt)
            mt.BeginAnimation(MatrixTransform.MatrixProperty, GetMatrixAnimation(fromMatrix, toMatrix, duration));
    }
    private MatrixAnimation GetMatrixAnimation(Matrix? fromMatrix, Matrix? toMatrix, TimeSpan duration)
    {
        return new MatrixAnimation(fromMatrix ?? Matrix.Identity, toMatrix ?? Matrix.Identity, new Duration(duration));
    }
非要怀念 2024-08-23 08:37:04

我能想到的一种方法是将 Matrix 转换为包含 ScaleTransform、RotateTransform 和 TranslateTransform 的 TransformGroup >,然后使用普通动画对它们进行动画处理,然后动画完成后,根据每个变换中的值再次创建矩阵?

One way I can think of is to convert the Matrix to a TransformGroup which contains ScaleTransform, RotateTransform, and a TranslateTransform, then animate these using normal animations, then once the animation is complete, create the Matrix back again from the values in each of the Transforms?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文