zoukankan      html  css  js  c++  java
  • WPF/WP/Silverlight/Metro App代码创建动画的思路

    在2010年之前,我都是用Blend创建动画,添加触发器实现自动动画,后来写成代码创建的方式。如今Blend已经集成到Visual Studio安装镜像中了,最新的VS2015安装,Blend的操作界面已经十分接近VS,难怪有人吐槽Win10 Insider Preview(10025之前版本)的图标设计都是程序员搞出来的——这靠近VS的界面是怎么回事,不是应该更接近于Photoshop、Flash什么的吗?

    如果你们公司没有大牛设计师,估计是不太可能使用Blend的。

    这是一个点击Show或者Hide按钮使页面中蓝色Grid淡出或者淡入显示的例子。创建两个Storyboard,在2秒位置分别设置蓝色Grid的Opacity为100%和0%。然后在按钮的Click事件处理代码中找到Storyboard资源并执行。

            private void Show_Click(object sender, RoutedEventArgs e)
            {
                ((Storyboard)(this.Resources["ShowGrid"])).Begin();
            }
    
            private void Hide_Click(object sender, RoutedEventArgs e)
            {
                ((Storyboard)(this.Resources["HideGrid"])).Begin();
            }

    作为码农的封装癖好,为了更方便的调用代码动画,我写了UIElement的扩展方法,来扩充界面元素的动画调用。

    grid.PlayFadeMoveAnimation(TimeSpan.FromMilliseconds(800), destOpacity: 1, fromX: 50, destX: 0,easingFunctionForMove: new CircleEase())
        .Completed += (ss, se) =>{/*动画播放完成*/};

    PlayFadeMoveAnimation扩展方法就是播放淡入淡出并且做移动动画的效果。当时受到了JQuery的“连续方法调用”的影响,这个方法会返回个Storyboard。

    上面代码的解释是grid立即播放动画效果:在800ms里,使grid的Opacity变成1(淡入效果),并且水平坐标从50移动到0(右侧50距离开始移动到原始位置),移动时候使用默认的CircleEase函数效果。

    例子

    1.闪烁

    grid.PlayTwinklingAnimation(new[] { new TimeSpan(0, 0, 1), new TimeSpan(0, 0, 1) }, new[] { 0.1, 1.0 }, RepeatBehavior.Forever);

    2.淡入淡出

    grid.PlayFadeAnimation(TimeSpan.FromMilliseconds(500), 1);
    
    grid.PlayFadeAnimation(TimeSpan.FromMilliseconds(500), 0);

    3.淡入淡出平移

    grid.PlayFadeMoveAnimation(TimeSpan.FromMilliseconds(800), destOpacity: 1, fromX: 50, destX: 0, easingFunctionForMove: new CircleEase());
    
    grid.PlayFadeMoveAnimation(TimeSpan.FromMilliseconds(800), destOpacity: 0, fromX: 0, destX: 50);

    4.平移缩放

    grid.PlayMoveAnimation(TimeSpan.FromMilliseconds(500), destX: 0, destY: 0, scaleX: 1, scaleY: 1);
    
    grid.PlayMoveAnimation(TimeSpan.FromMilliseconds(500), destX: 500, destY: 500, scaleX: 0.2, scaleY: 0.2);

    下面是实现代码:

      1 using System;
      2 using Windows.UI.Xaml;
      3 using Windows.UI.Xaml.Media;
      4 using Windows.UI.Xaml.Media.Animation;
      5 
      6 public static class AnimationHelper
      7 {
      8     #region Animation
      9 
     10     private const double DoubleEpsilon = 0.001;
     11 
     12     /// <summary>
     13     /// 播放闪烁动画
     14     /// </summary>
     15     /// <param name="uiElement">作用UI元素</param>
     16     /// <param name="timeSpanValues">播放时间</param>
     17     /// <param name="opacityValues">透明类清单</param>
     18     /// <param name="repeatBehavior">重复次数</param>
     19     /// <param name="autoReserse">是否翻转播放</param>
     20     /// <returns>当成功播放动画时会返回一个Storyboard对象</returns>
     21     public static Storyboard PlayTwinklingAnimation(
     22         this UIElement uiElement,
     23         TimeSpan[] timeSpanValues,
     24         double[] opacityValues,
     25         RepeatBehavior repeatBehavior,
     26         bool autoReserse = false)
     27     {
     28         if (timeSpanValues == null || opacityValues == null
     29             || timeSpanValues.Length == 0
     30             || timeSpanValues.Length != opacityValues.Length)
     31         {
     32             return null;
     33         }
     34 
     35         var animation = new DoubleAnimationUsingKeyFrames();
     36         for (int i = 0; i < timeSpanValues.Length; i++)
     37         {
     38             var keyframe = new SplineDoubleKeyFrame { KeyTime = timeSpanValues[i], Value = opacityValues[i] };
     39             animation.KeyFrames.Add(keyframe);
     40         }
     41 
     42         animation.RepeatBehavior = repeatBehavior;
     43         animation.AutoReverse = autoReserse;
     44 
     45         Storyboard.SetTarget(animation, uiElement);
     46         Storyboard.SetTargetProperty(animation, "Opacity");
     47 
     48         var storyboard = new Storyboard();
     49         storyboard.Children.Add(animation);
     50         storyboard.Begin();
     51 
     52         return storyboard;
     53     }
     54 
     55 
     56     /// <summary>
     57     /// 播放淡入淡出动画
     58     /// </summary>
     59     /// <param name="uiElement">作用UI元素</param>
     60     /// <param name="timeSpan">动画时长</param>
     61     /// <param name="destOpacity">目标的Opacity</param>
     62     /// <param name="changeVisibility">是否允许改变UI元素的Visibility属性,建议“是”</param>
     63     /// <param name="easingFunction">easingFunction</param>
     64     /// <returns>当成功播放动画时会返回一个Storyboard对象</returns>
     65     public static Storyboard PlayFadeAnimation(
     66         this UIElement uiElement,
     67         TimeSpan timeSpan,
     68         double destOpacity,
     69         bool changeVisibility = true,
     70         EasingFunctionBase easingFunction = null)
     71     {
     72         if (changeVisibility)
     73         {
     74             if (uiElement.Visibility == Visibility.Collapsed &&
     75                 destOpacity < DoubleEpsilon)
     76             {
     77                 uiElement.Opacity = 0;
     78                 return null;
     79             }
     80 
     81             if (Math.Abs(uiElement.Opacity - destOpacity) < DoubleEpsilon)
     82             {
     83                 uiElement.Visibility = destOpacity < DoubleEpsilon ? Visibility.Collapsed : Visibility.Visible;
     84                 return null;
     85             }
     86         }
     87         else if (Math.Abs(uiElement.Opacity - destOpacity) < DoubleEpsilon)
     88         {
     89             return null;
     90         }
     91 
     92         if (changeVisibility &&
     93             destOpacity > DoubleEpsilon &&
     94             uiElement.Visibility != Visibility.Visible)
     95         {
     96             uiElement.Opacity = 0;
     97             uiElement.Visibility = Visibility.Visible;
     98         }
     99 
    100         var animation = new DoubleAnimation
    101         {
    102             From = uiElement.Opacity,
    103             To = destOpacity,
    104             Duration = new Duration(timeSpan)
    105         };
    106 
    107         if (easingFunction != null)
    108         {
    109             animation.EasingFunction = easingFunction;
    110         }
    111 
    112         Storyboard.SetTarget(animation, uiElement);
    113         Storyboard.SetTargetProperty(animation, "Opacity");
    114 
    115         animation.Completed += (sender, e) =>
    116         {
    117             uiElement.Opacity = destOpacity;
    118 
    119             if (changeVisibility)
    120             {
    121                 if (destOpacity < DoubleEpsilon)
    122                 {
    123                     uiElement.Visibility = Visibility.Collapsed;
    124                 }
    125             }
    126         };
    127 
    128         var storyboard = new Storyboard();
    129         storyboard.Children.Add(animation);
    130         storyboard.FillBehavior = FillBehavior.HoldEnd;
    131         storyboard.Begin();
    132 
    133         return storyboard;
    134     }
    135 
    136 
    137     /// <summary>
    138     /// 播放移动动画(注:参数destX、destY、scaleX和scaleY至少指定一个)
    139     /// </summary>
    140     /// <param name="frameworkElement">作用UI元素</param>
    141     /// <param name="timeSpan">动画时长</param>
    142     /// <param name="destX">目标的坐标X</param>
    143     /// <param name="destY">目标的坐标Y</param>
    144     /// <param name="scaleX">目标的缩放X</param>
    145     /// <param name="scaleY">目标的缩放Y</param>
    146     /// <param name="centerX">目标的缩放中心点X</param>
    147     /// <param name="centerY">目标的缩放中心点Y</param>
    148     /// <param name="easingFunction">EasingFunction</param>
    149     /// <returns>当成功播放动画时会返回一个Storyboard对象</returns>
    150     public static Storyboard PlayMoveAnimation(
    151         this FrameworkElement frameworkElement,
    152         TimeSpan timeSpan,
    153         double destX = double.NaN,
    154         double destY = double.NaN,
    155         double scaleX = double.NaN,
    156         double scaleY = double.NaN,
    157         double centerX = double.NaN,
    158         double centerY = double.NaN,
    159         EasingFunctionBase easingFunction = null)
    160     {
    161         if (double.IsNaN(destX) && double.IsNaN(destY) && double.IsNaN(scaleX) && double.IsNaN(scaleY))
    162         {
    163             throw new ArgumentException("destX destY scaleX scaleX");
    164         }
    165 
    166         var storyboard = new Storyboard();
    167 
    168         var translateTransform = frameworkElement.GetTranform<TranslateTransform>();
    169         if (!double.IsNaN(destX))
    170         {
    171             if (Math.Abs(translateTransform.X - destX) > DoubleEpsilon)
    172             {
    173                 var animation = new DoubleAnimation
    174                 {
    175                     From = translateTransform.X,
    176                     To = destX,
    177                     Duration = new Duration(timeSpan),
    178                     EasingFunction = easingFunction
    179                 };
    180 
    181                 Storyboard.SetTarget(animation, translateTransform);
    182                 Storyboard.SetTargetProperty(animation, "X");
    183                 storyboard.Children.Add(animation);
    184             }
    185         }
    186 
    187         if (!double.IsNaN(destY))
    188         {
    189             if (Math.Abs(translateTransform.Y - destY) > DoubleEpsilon)
    190             {
    191                 var animation = new DoubleAnimation
    192                 {
    193                     From = translateTransform.Y,
    194                     To = destY,
    195                     Duration = new Duration(timeSpan),
    196                     EasingFunction = easingFunction
    197                 };
    198 
    199                 Storyboard.SetTarget(animation, translateTransform);
    200                 Storyboard.SetTargetProperty(animation, "Y");
    201                 storyboard.Children.Add(animation);
    202             }
    203         }
    204 
    205         var scaleTransform = frameworkElement.GetTranform<ScaleTransform>();
    206         if (!double.IsNaN(centerX)) scaleTransform.CenterX = centerX;
    207         if (!double.IsNaN(centerY)) scaleTransform.CenterX = centerY;
    208         if (!double.IsNaN(scaleX))
    209         {
    210             if (Math.Abs(scaleTransform.ScaleX - scaleX) > DoubleEpsilon)
    211             {
    212                 var animation = new DoubleAnimation
    213                 {
    214                     From = scaleTransform.ScaleX,
    215                     To = scaleX,
    216                     Duration = new Duration(timeSpan),
    217                     EasingFunction = easingFunction
    218                 };
    219 
    220                 Storyboard.SetTarget(animation, scaleTransform);
    221                 Storyboard.SetTargetProperty(animation, "ScaleX");
    222                 storyboard.Children.Add(animation);
    223             }
    224         }
    225 
    226         if (!double.IsNaN(scaleY))
    227         {
    228             if (Math.Abs(scaleTransform.ScaleY - scaleY) > DoubleEpsilon)
    229             {
    230                 var animation = new DoubleAnimation
    231                 {
    232                     From = scaleTransform.ScaleY,
    233                     To = scaleY,
    234                     Duration = new Duration(timeSpan),
    235                     EasingFunction = easingFunction
    236                 };
    237 
    238                 Storyboard.SetTarget(animation, scaleTransform);
    239                 Storyboard.SetTargetProperty(animation, "ScaleY");
    240                 storyboard.Children.Add(animation);
    241             }
    242         }
    243 
    244         if (storyboard.Children.Count > 0)
    245         {
    246             storyboard.Begin();
    247             return storyboard;
    248         }
    249 
    250         return null;
    251     }
    252 
    253     /// <summary>
    254     /// 播放一个包含透明度和移动变化的动画
    255     /// </summary>
    256     /// <param name="uiElement">作用UI元素</param>
    257     /// <param name="timeSpan">动画时长</param>
    258     /// <param name="destOpacity">目标Opacity</param>
    259     /// <param name="changeVisibility">是否允许改变UI元素的Visibility属性,建议“是”</param>
    260     /// <param name="fromX">起始坐标X</param>
    261     /// <param name="fromY">起始坐标Y</param>
    262     /// <param name="destX">目标的坐标X</param>
    263     /// <param name="destY">目标的坐标Y</param>
    264     /// <param name="easingFunctionForFade">EasingFunction</param>
    265     /// <param name="easingFunctionForMove">EasingFunction</param>
    266     /// <returns>当成功播放动画时会返回一个Storyboard对象</returns>
    267     public static Storyboard PlayFadeMoveAnimation(
    268         this UIElement uiElement,
    269         TimeSpan timeSpan,
    270         double destOpacity,
    271         bool changeVisibility = true,
    272         double fromX = double.NaN,
    273         double fromY = double.NaN,
    274         double destX = double.NaN,
    275         double destY = double.NaN,
    276         EasingFunctionBase easingFunctionForFade = null,
    277         EasingFunctionBase easingFunctionForMove = null
    278         )
    279     {
    280         var storyboard = new Storyboard { FillBehavior = FillBehavior.HoldEnd };
    281 
    282         if (changeVisibility &&
    283             destOpacity > DoubleEpsilon &&
    284             uiElement.Visibility != Visibility.Visible)
    285         {
    286             uiElement.Opacity = 0;
    287             uiElement.Visibility = Visibility.Visible;
    288         }
    289         var fadeAnimation = new DoubleAnimation
    290         {
    291             From = uiElement.Opacity,
    292             To = destOpacity,
    293             Duration = new Duration(timeSpan)
    294         };
    295         if (easingFunctionForFade != null)
    296         {
    297             fadeAnimation.EasingFunction = easingFunctionForFade;
    298         }
    299         Storyboard.SetTarget(fadeAnimation, uiElement);
    300         Storyboard.SetTargetProperty(fadeAnimation, "Opacity");
    301         fadeAnimation.Completed += (sender, e) =>
    302         {
    303             uiElement.Opacity = destOpacity;
    304 
    305             if (changeVisibility)
    306             {
    307                 if (destOpacity < DoubleEpsilon)
    308                 {
    309                     uiElement.Visibility = Visibility.Collapsed;
    310                 }
    311             }
    312         };
    313         storyboard.Children.Add(fadeAnimation);
    314 
    315         if (!double.IsNaN(destX) || !double.IsNaN(destY))
    316         {
    317             var translateTransform = uiElement.GetTranform<TranslateTransform>();
    318             if (!double.IsNaN(destX))
    319             {
    320                 var x = double.IsNaN(fromX) ? translateTransform.X : fromX;
    321                 if (Math.Abs(x - destX) > DoubleEpsilon)
    322                 {
    323                     var animation = new DoubleAnimation
    324                     {
    325                         From = x,
    326                         To = destX,
    327                         Duration = new Duration(timeSpan),
    328                         EasingFunction = easingFunctionForMove
    329                     };
    330 
    331                     Storyboard.SetTarget(animation, translateTransform);
    332                     Storyboard.SetTargetProperty(animation, "X");
    333                     storyboard.Children.Add(animation);
    334                 }
    335             }
    336             if (!double.IsNaN(destY))
    337             {
    338                 var y = double.IsNaN(fromY) ? translateTransform.X : fromY;
    339                 if (Math.Abs(y - destY) > DoubleEpsilon)
    340                 {
    341                     var animation = new DoubleAnimation
    342                     {
    343                         From = y,
    344                         To = destY,
    345                         Duration = new Duration(timeSpan),
    346                         EasingFunction = easingFunctionForMove
    347                     };
    348 
    349                     Storyboard.SetTarget(animation, translateTransform);
    350                     Storyboard.SetTargetProperty(animation, "Y");
    351                     storyboard.Children.Add(animation);
    352                 }
    353             }
    354         }
    355 
    356         if (storyboard.Children.Count > 0)
    357         {
    358             storyboard.Begin();
    359             return storyboard;
    360         }
    361 
    362         return null;
    363     }
    364 
    365     #endregion
    366 
    367     #region Transform helper
    368 
    369     /// <summary>
    370     /// 获得或创建一个新的Transform对象
    371     /// </summary>
    372     /// <typeparam name="T">指定一个Transform类型</typeparam>
    373     /// <param name="uiElement">UI元素</param>
    374     /// <returns>一个Transform对象</returns>
    375     public static T GetTranform<T>(this UIElement uiElement)
    376         where T : Transform, new()
    377     {
    378         if (uiElement.RenderTransform == null)
    379         {
    380             var newTransformGroup = new TransformGroup();
    381             var newTransfrom = new T();
    382             newTransformGroup.Children.Add(newTransfrom);
    383 
    384             uiElement.RenderTransform = newTransformGroup;
    385             return newTransfrom;
    386         }
    387 
    388         var transformGroup = uiElement.RenderTransform as TransformGroup;
    389         if (transformGroup != null)
    390         {
    391             if (transformGroup is T)
    392             {
    393                 return transformGroup as T;
    394             }
    395 
    396             var r = GetTranform<T>(transformGroup);
    397             if (r != null)
    398             {
    399                 return r;
    400             }
    401 
    402             var newTransfrom = new T();
    403             transformGroup.Children.Add(newTransfrom);
    404             return newTransfrom;
    405         }
    406 
    407         var transform = uiElement.RenderTransform as T;
    408         if (transform != null)
    409         {
    410             return transform;
    411         }
    412 
    413         var newTransformGroup1 = new TransformGroup();
    414         var newTransfrom1 = new T();
    415 
    416         //如果原来不是MatrixTransform矩阵,则加入
    417         var matrixTransform = uiElement.RenderTransform as MatrixTransform;
    418         if (matrixTransform == null)
    419         {
    420             newTransformGroup1.Children.Add(uiElement.RenderTransform);
    421         }
    422 
    423         newTransformGroup1.Children.Add(newTransfrom1);
    424         uiElement.RenderTransform = newTransformGroup1;
    425         return newTransfrom1;
    426     }
    427 
    428     private static T GetTranform<T>(TransformGroup transformGroup)
    429         where T : Transform, new()
    430     {
    431         foreach (var child in transformGroup.Children)
    432         {
    433             if (child is T)
    434             {
    435                 return (T)child;
    436             }
    437 
    438             var group1 = child as TransformGroup;
    439             if (group1 != null)
    440             {
    441                 var r = GetTranform<T>(group1);
    442                 if (r != null)
    443                 {
    444                     return r;
    445                 }
    446             }
    447         }
    448 
    449         return null;
    450     }
    451 
    452     #endregion
    453 }

    需要特别说一下的是:在Win10 UAP开发中,如Grid这中的界面元素都默认使用了2D的仿射矩阵变换动画,通过修改3*3的矩阵数值就能够表达平移、旋转、缩放。2010年在某家公司做一个WPF地图平面功能时就是直接使用的Matrix实现。因此MatrixTransform不能和其他Transform一起使用。

    解释一下375行的GetTranform<T>方法,它返回或为uiElement创建指定的Transform。当uiElement的RenderTransform属性为空时候,创建TransformGroup,然后将指定的Transform添加到TransformGroup里,使UIElement的RenderTransform为TransformGroup。

    在动画代码里,平移使用TranslateTransform效果,透明度是修改Opacity属性,Opacity为0或者为1时候修改Visibility属性。当然可以在调用PlayXXXAnimation方法时候设置changeVisibility为false来达到不修改Visibility的目的。

    EasingFunctionBase是缓动动画效果,就是Bland里不同的动画曲线函数。

    最后可根据情况丰富自已的动画库。比如模仿按钮被按下的动画效果,然后写成bool类型的FramewrokElement的附加属性。

  • 相关阅读:
    socket
    RBAC
    CMOS和BIOS
    canvas和SVG
    兼容IE,chrome,ff的设为首页、加入收藏及保存到桌面
    HTML标签marquee实现滚动效果
    百度判断手机终端并自动跳转uaredirect.js代码及使用实例
    JavaScript中常用的事件
    解决windows server 2008 r2 登录进入桌面只显示一片蓝色背景
    ng2自定义管道
  • 原文地址:https://www.cnblogs.com/Bob-wei/p/4535210.html
Copyright © 2011-2022 走看看