wpf动画——缓动动画Animation Easing(2)

在wpf或者silverlight中,经常用到Storyboard来完成一些动画的效果,本例将说明使用缓动函数关联动画 Animation Easing的方法:


1.新建一个wpf应用程序(silverlight亦可),xaml简单修改布局如下:

 

<Window x:Class="WpfApplication51.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid Name="root" Loaded="root_Loaded">
      
    </Grid>
</Window>

 

对应我们添加了一个Grid的Loaded事件处理:

 private void root_Loaded(object sender, RoutedEventArgs e)
        {
        }

然后我们准备一个故事布景canvas,一个圆elipse,几条准线line,用来描述一个缓动动画过程:

        Canvas canvas = new Canvas()
        {
            Height = 300,
            Width = 400,
            Background = new SolidColorBrush(Colors.Silver),
        };
        Ellipse e1 = new Ellipse()
        {
            Height = 20,
            Width = 20,
            Fill = new SolidColorBrush(Colors.Green),
            RenderTransform = new TranslateTransform(-10, -10)
        };
        Line l1 = new Line()
        {
            Stroke = new SolidColorBrush(Colors.Gray),
            X1 = 0,
            Y1 = 100,
            X2 = 400,
            Y2 = 100
        };
        Line l2 = new Line()
        {
            Stroke = new SolidColorBrush(Colors.Gray),
            X1 = 0,
            Y1 = 200,
            X2 = 400,
            Y2 = 200
        };

    private void root_Loaded(object sender, RoutedEventArgs e)
        {
            this.root.Children.Add(this.canvas);
            this.canvas.Children.Add(this.l1);
            this.canvas.Children.Add(this.l2);
            this.canvas.Children.Add(this.e1);
            this.e1.SetValue(Canvas.TopProperty, 200.0);
            this.e1.SetValue(Canvas.LeftProperty, 50.0);
          }

这里我们让动画的主角-圆,Transform变换了一下,纵横坐标都偏移了一个半径距离,目的是让圆的圆心位置在原点位置

F5运行一下,得到一个开幕:


2.接下来我们让这个圆动起来,

首先说明一下,一个动画故事StoryBoard可以由很多的关键帧集合DoubleAnimationUsingKeyFrames组成,

每个DoubleAnimationUsingKeyFrames关键帧集合是由很多KeyFrame帧组成,

每个KeyFrame帧记录了时间点keytime,目标值 value,以及是否会用到的EasingFunction缓动处理函数,

“工欲善其事,必先利其器”,so,先写两个方法用来添加丰富我们的动画吧:

        /// <summary>
        /// story故事版增加key关键帧集
        /// </summary>
        /// <param name="sb">story故事版</param>
        /// <param name="ks">key关键帧集</param>
        /// <param name="dobj">动画目标</param>
        /// <param name="property">动画属性</param>
        void StoryAddKey(Storyboard sb, DoubleAnimationUsingKeyFrames ks, DependencyObject dobj, PropertyPath property)
        {
            sb.Children.Add(ks);
            Storyboard.SetTarget(ks, dobj);
            Storyboard.SetTargetProperty(ks, property);
        }

        /// <summary>
        /// key关键帧集增加帧frame
        /// </summary>
        /// <param name="kfs">关键帧集</param>
        /// <param name="ms">时间点</param>
        /// <param name="value">数值</param>
        /// <param name="efun">缓动处理</param>
        void KeyAddFrame(DoubleAnimationUsingKeyFrames kfs, double ms, double value, EasingFunctionBase efun)
        {
            EasingDoubleKeyFrame kf = new EasingDoubleKeyFrame();
            kf.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(ms));
            kf.Value = value;
            kf.EasingFunction = efun;
            kfs.KeyFrames.Add(kf);
        }

        /// <summary>
        /// property属性链
        /// </summary>
        DependencyProperty[] propertyChain = new DependencyProperty[]
        {
            Canvas.TopProperty,
            Canvas.LeftProperty
        };

这里的属性链,在处理动画目标属性的时候,可以很方便的选择要处理的属性,

然后用我们建的方法去添加一个动画吧,

动画的过程是让圆在一个二维坐标系中移动:

y纵轴方向:让圆从下面的准线移动到上面的准线;

x横轴方向:让圆水平移动;

       Storyboard sb = new Storyboard()

        {
           
      FillBehavior = FillBehavior.HoldEnd
 
       };


    private void root_Loaded(object sender, RoutedEventArgs e) { this.root.Children.Add(this.canvas); this.canvas.Children.Add(this.l1); this.canvas.Children.Add(this.l2); this.canvas.Children.Add(this.e1); this.e1.SetValue(Canvas.TopProperty, 200.0); this.e1.SetValue(Canvas.LeftProperty, 50.0); //纵坐标动画 DoubleAnimationUsingKeyFrames ks1e1 = new DoubleAnimationUsingKeyFrames(); this.KeyAddFrame(ks1e1, 0.0, 200.0, null); this.KeyAddFrame(ks1e1, 2.0, 100.0, new BackEase() { EasingMode = EasingMode.EaseOut, Amplitude = 1.0 }); this.StoryAddKey(sb, ks1e1, this.e1, new PropertyPath("(0)", this.propertyChain)); //横坐标动画 DoubleAnimationUsingKeyFrames ks2e1 = new DoubleAnimationUsingKeyFrames(); this.KeyAddFrame(ks2e1, 0.0, 50.0, null); this.KeyAddFrame(ks2e1, 2.0, 350.0, null); this.StoryAddKey(sb, ks2e1, this.e1, new PropertyPath("(1)", this.propertyChain)); sb.Begin(); }

这里我们使用了缓动动画中的一种BackEase,设置模式为EaseOut(动画末尾处理),当然你也可以去show其他几种模式:

EaseIn:动画初时处理;

EaseInOut:动画初时及末尾均处理;

这里还设置了Amplitude,其默认值就为1.0,代表缓动动画的幅度,

F5运行一下,发现圆已经按照我们预想的那样去移动,

接下来我们试着把圆移动的轨迹绘制出来,更加直观的观察缓动的处理过程。


3.绘制的方法,添加的代码如下:

        DrawingVisual drawingVisual = new DrawingVisual();
        DrawingContext drawingContext;
        Point pe1 = new Point(50.0, 200.0);
        Image image = new Image()
        {
            Width = 400,
            Height = 300
        };

        private void root_Loaded(object sender, RoutedEventArgs e)
        {
            this.root.Children.Add(this.canvas);
            this.canvas.Children.Add(this.l1);
            this.canvas.Children.Add(this.l2);
            this.canvas.Children.Add(this.e1);
            this.e1.SetValue(Canvas.TopProperty, 200.0);
            this.e1.SetValue(Canvas.LeftProperty, 50.0);

            //纵坐标动画
            DoubleAnimationUsingKeyFrames ks1e1 = new DoubleAnimationUsingKeyFrames();
            this.KeyAddFrame(ks1e1, 0.0, 200.0, null);
            this.KeyAddFrame(ks1e1, 2.0, 100.0, new BackEase() { EasingMode = EasingMode.EaseOut, Amplitude = 1.0 });
            this.StoryAddKey(sb, ks1e1, this.e1, new PropertyPath("(0)", this.propertyChain));

            //横坐标动画
            DoubleAnimationUsingKeyFrames ks2e1 = new DoubleAnimationUsingKeyFrames();
            this.KeyAddFrame(ks2e1, 0.0, 50.0, null);
            this.KeyAddFrame(ks2e1, 2.0, 350.0, null);
            this.StoryAddKey(sb, ks2e1, this.e1, new PropertyPath("(1)", this.propertyChain));

                     this.canvas.LayoutUpdated += new EventHandler(canvas_LayoutUpdated);
            drawingContext = drawingVisual.RenderOpen();
            sb.Completed += new EventHandler(sb_Completed);
            sb.Begin();
        }

        void sb_Completed(object sender, EventArgs e)
        {
            drawingContext.Close();
            RenderTargetBitmap composeImage = new RenderTargetBitmap(400, 300, 0, 0, PixelFormats.Pbgra32);
            composeImage.Render(drawingVisual);
            this.image.Source = composeImage;
            this.canvas.LayoutUpdated -= canvas_LayoutUpdated;
            this.canvas.Children.Add(image);
        }

        void canvas_LayoutUpdated(object sender, EventArgs e)
        {
            Point p1 = new Point(Convert.ToDouble(this.e1.GetValue(Canvas.LeftProperty)), Convert.ToDouble(this.e1.GetValue(Canvas.TopProperty)));
            drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Green), 2.0), pe1, p1);
            pe1 = p1;

          }

这里用到了canvas内部元素布局改变的LayoutUpdated事件,每当圆移动改变自己坐标的时候,就记录下坐标,移动到下一个坐标时,连接两个坐标的连线即移动的路径

这里的绘制使用drawingContext.DrawLine绘制路径线段,动画结束时将绘制得到的DrawingVisual结果放到一个与canvas舞台大小相同的image中用以呈现,

F5运行一下,结果如下图:


4.试着修改缓冲动画的种类,以及幅度Amplitude的值,亦可多show几个圆同时移动,来观察他们的区别,以下给出完整的参考代码(两个圆的轨迹比较):

    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        #region
        Canvas canvas = new Canvas()
        {
            Height = 300,
            Width = 400,
            Background = new SolidColorBrush(Colors.Silver),
        };
        Ellipse e1 = new Ellipse()
        {
            Height = 20,
            Width = 20,
            Fill = new SolidColorBrush(Colors.Green),
            RenderTransform = new TranslateTransform(-10, -10)
        };
        Ellipse e2 = new Ellipse()
        {
            Height = 20,
            Width = 20,
            Fill = new SolidColorBrush(Colors.Yellow),
            RenderTransform = new TranslateTransform(-10, -10)
        };
        Line l1 = new Line()
        {
            Stroke = new SolidColorBrush(Colors.Gray),
            X1 = 0,
            Y1 = 100,
            X2 = 400,
            Y2 = 100
        };
        Line l2 = new Line()
        {
            Stroke = new SolidColorBrush(Colors.Gray),
            X1 = 0,
            Y1 = 200,
            X2 = 400,
            Y2 = 200
        };
        Storyboard sb = new Storyboard()
        {
            FillBehavior = FillBehavior.HoldEnd
        };
        DrawingVisual drawingVisual = new DrawingVisual();
        DrawingContext drawingContext;
        Point pe1 = new Point(50.0, 200.0);
        Point pe2 = new Point(50.0, 200.0);
        Image image = new Image()
        {
            Width = 400,
            Height = 300
        };
        #endregion

        #region

        /// <summary>
        /// story故事版增加key关键帧集
        /// </summary>
        /// <param name="sb">story故事版</param>
        /// <param name="ks">key关键帧集</param>
        /// <param name="dobj">动画目标</param>
        /// <param name="property">动画属性</param>
        void StoryAddKey(Storyboard sb, DoubleAnimationUsingKeyFrames ks, DependencyObject dobj, PropertyPath property)
        {
            sb.Children.Add(ks);
            Storyboard.SetTarget(ks, dobj);
            Storyboard.SetTargetProperty(ks, property);
        }

        /// <summary>
        /// key关键帧集增加帧frame
        /// </summary>
        /// <param name="kfs">关键帧集</param>
        /// <param name="ms">时间点</param>
        /// <param name="value">数值</param>
        /// <param name="efun">缓动处理</param>
        void KeyAddFrame(DoubleAnimationUsingKeyFrames kfs, double ms, double value, EasingFunctionBase efun)
        {
            EasingDoubleKeyFrame kf = new EasingDoubleKeyFrame();
            kf.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(ms));
            kf.Value = value;
            kf.EasingFunction = efun;
            kfs.KeyFrames.Add(kf);
        }

        /// <summary>
        /// property属性链
        /// </summary>
        DependencyProperty[] propertyChain = new DependencyProperty[]
        {
            Canvas.TopProperty,
            Canvas.LeftProperty
        };

        #endregion

        private void root_Loaded(object sender, RoutedEventArgs e)
        {
            this.root.Children.Add(this.canvas);
            this.canvas.Children.Add(this.l1);
            this.canvas.Children.Add(this.l2);
            this.canvas.Children.Add(this.e1);
            this.e1.SetValue(Canvas.TopProperty, 200.0);
            this.e1.SetValue(Canvas.LeftProperty, 50.0);
            this.canvas.Children.Add(this.e2);
            this.e2.SetValue(Canvas.TopProperty, 200.0);
            this.e2.SetValue(Canvas.LeftProperty, 50.0);

            #region e1

            //纵坐标动画
            DoubleAnimationUsingKeyFrames ks1e1 = new DoubleAnimationUsingKeyFrames();
            this.KeyAddFrame(ks1e1, 0.0, 200.0, null);
            this.KeyAddFrame(ks1e1, 2.0, 100.0, new BackEase() { EasingMode = EasingMode.EaseOut, Amplitude = 1.0 });
            this.StoryAddKey(sb, ks1e1, this.e1, new PropertyPath("(0)", this.propertyChain));

            //横坐标动画
            DoubleAnimationUsingKeyFrames ks2e1 = new DoubleAnimationUsingKeyFrames();
            this.KeyAddFrame(ks2e1, 0.0, 50.0, null);
            this.KeyAddFrame(ks2e1, 2.0, 350.0, null);
            this.StoryAddKey(sb, ks2e1, this.e1, new PropertyPath("(1)", this.propertyChain));

            #endregion

            #region e2

            //纵坐标动画
            DoubleAnimationUsingKeyFrames ks1e2 = new DoubleAnimationUsingKeyFrames();
            this.KeyAddFrame(ks1e2, 0.0, 200.0, null);
            this.KeyAddFrame(ks1e2, 2.0, 100.0, null);
            this.StoryAddKey(sb, ks1e2, this.e2, new PropertyPath("(0)", this.propertyChain));

            //横坐标动画
            DoubleAnimationUsingKeyFrames ks2e2 = new DoubleAnimationUsingKeyFrames();
            this.KeyAddFrame(ks2e2, 0.0, 50.0, null);
            this.KeyAddFrame(ks2e2, 2.0, 350.0, null);
            this.StoryAddKey(sb, ks2e2, this.e2, new PropertyPath("(1)", this.propertyChain));

            #endregion

            this.canvas.LayoutUpdated += new EventHandler(canvas_LayoutUpdated);
            drawingContext = drawingVisual.RenderOpen();
            sb.Completed += new EventHandler(sb_Completed);
            sb.Begin();
        }

        void sb_Completed(object sender, EventArgs e)
        {
            drawingContext.Close();
            RenderTargetBitmap composeImage = new RenderTargetBitmap(400, 300, 0, 0, PixelFormats.Pbgra32);
            composeImage.Render(drawingVisual);
            this.image.Source = composeImage;
            this.canvas.LayoutUpdated -= canvas_LayoutUpdated;
            this.canvas.Children.Add(image);
        }

        void canvas_LayoutUpdated(object sender, EventArgs e)
        {
            #region e1

            Point p1 = new Point(Convert.ToDouble(this.e1.GetValue(Canvas.LeftProperty)), Convert.ToDouble(this.e1.GetValue(Canvas.TopProperty)));
            drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Green), 2.0), pe1, p1);
            pe1 = p1;

            #endregion

            #region e2

            Point p2 = new Point(Convert.ToDouble(this.e2.GetValue(Canvas.LeftProperty)), Convert.ToDouble(this.e2.GetValue(Canvas.TopProperty)));
            drawingContext.DrawLine(new Pen(new SolidColorBrush(Colors.Yellow), 2.0), pe2, p2);
            pe2 = p2;

            #endregion
        }

    }

运行结果如下:


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值