WPF/WP/Silverlight/Metro App代码创建动画的思路
2015-05-28 10:15
281 查看
在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资源并执行。
作为码农的封装癖好,为了更方便的调用代码动画,我写了UIElement的扩展方法,来扩充界面元素的动画调用。
PlayFadeMoveAnimation扩展方法就是播放淡入淡出并且做移动动画的效果。当时受到了JQuery的“连续方法调用”的影响,这个方法会返回个Storyboard。
上面代码的解释是grid立即播放动画效果:在800ms里,使grid的Opacity变成1(淡入效果),并且水平坐标从50移动到0(右侧50距离开始移动到原始位置),移动时候使用默认的CircleEase函数效果。
例子:
1.闪烁
2.淡入淡出
3.淡入淡出平移
4.平移缩放
下面是实现代码:
需要特别说一下的是:在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的附加属性。
如果你们公司没有大牛设计师,估计是不太可能使用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);
下面是实现代码:
using System; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; public static class AnimationHelper { #region Animation private const double DoubleEpsilon = 0.001; /// <summary> /// 播放闪烁动画 /// </summary> /// <param name="uiElement">作用UI元素</param> /// <param name="timeSpanValues">播放时间</param> /// <param name="opacityValues">透明类清单</param> /// <param name="repeatBehavior">重复次数</param> /// <param name="autoReserse">是否翻转播放</param> /// <returns>当成功播放动画时会返回一个Storyboard对象</returns> public static Storyboard PlayTwinklingAnimation( this UIElement uiElement, TimeSpan[] timeSpanValues, double[] opacityValues, RepeatBehavior repeatBehavior, bool autoReserse = false) { if (timeSpanValues == null || opacityValues == null || timeSpanValues.Length == 0 || timeSpanValues.Length != opacityValues.Length) { return null; } var animation = new DoubleAnimationUsingKeyFrames(); for (int i = 0; i < timeSpanValues.Length; i++) { var keyframe = new SplineDoubleKeyFrame { KeyTime = timeSpanValues[i], Value = opacityValues[i] }; animation.KeyFrames.Add(keyframe); } animation.RepeatBehavior = repeatBehavior; animation.AutoReverse = autoReserse; Storyboard.SetTarget(animation, uiElement); Storyboard.SetTargetProperty(animation, "Opacity"); var storyboard = new Storyboard(); storyboard.Children.Add(animation); storyboard.Begin(); return storyboard; } /// <summary> /// 播放淡入淡出动画 /// </summary> /// <param name="uiElement">作用UI元素</param> /// <param name="timeSpan">动画时长</param> /// <param name="destOpacity">目标的Opacity</param> /// <param name="changeVisibility">是否允许改变UI元素的Visibility属性,建议“是”</param> /// <param name="easingFunction">easingFunction</param> /// <returns>当成功播放动画时会返回一个Storyboard对象</returns> public static Storyboard PlayFadeAnimation( this UIElement uiElement, TimeSpan timeSpan, double destOpacity, bool changeVisibility = true, EasingFunctionBase easingFunction = null) { if (changeVisibility) { if (uiElement.Visibility == Visibility.Collapsed && destOpacity < DoubleEpsilon) { uiElement.Opacity = 0; return null; } if (Math.Abs(uiElement.Opacity - destOpacity) < DoubleEpsilon) { uiElement.Visibility = destOpacity < DoubleEpsilon ? Visibility.Collapsed : Visibility.Visible; return null; } } else if (Math.Abs(uiElement.Opacity - destOpacity) < DoubleEpsilon) { return null; } if (changeVisibility && destOpacity > DoubleEpsilon && uiElement.Visibility != Visibility.Visible) { uiElement.Opacity = 0; uiElement.Visibility = Visibility.Visible; } var animation = new DoubleAnimation { From = uiElement.Opacity, To = destOpacity, Duration = new Duration(timeSpan) }; if (easingFunction != null) { animation.EasingFunction = easingFunction; } Storyboard.SetTarget(animation, uiElement); Storyboard.SetTargetProperty(animation, "Opacity"); animation.Completed += (sender, e) => { uiElement.Opacity = destOpacity; if (changeVisibility) { if (destOpacity < DoubleEpsilon) { uiElement.Visibility = Visibility.Collapsed; } } }; var storyboard = new Storyboard(); storyboard.Children.Add(animation); storyboard.FillBehavior = FillBehavior.HoldEnd; storyboard.Begin(); return storyboard; } /// <summary> /// 播放移动动画(注:参数destX、destY、scaleX和scaleY至少指定一个) /// </summary> /// <param name="frameworkElement">作用UI元素</param> /// <param name="timeSpan">动画时长</param> /// <param name="destX">目标的坐标X</param> /// <param name="destY">目标的坐标Y</param> /// <param name="scaleX">目标的缩放X</param> /// <param name="scaleY">目标的缩放Y</param> /// <param name="centerX">目标的缩放中心点X</param> /// <param name="centerY">目标的缩放中心点Y</param> /// <param name="easingFunction">EasingFunction</param> /// <returns>当成功播放动画时会返回一个Storyboard对象</returns> public static Storyboard PlayMoveAnimation( this FrameworkElement frameworkElement, TimeSpan timeSpan, double destX = double.NaN, double destY = double.NaN, double scaleX = double.NaN, double scaleY = double.NaN, double centerX = double.NaN, double centerY = double.NaN, EasingFunctionBase easingFunction = null) { if (double.IsNaN(destX) && double.IsNaN(destY) && double.IsNaN(scaleX) && double.IsNaN(scaleY)) { throw new ArgumentException("destX destY scaleX scaleX"); } var storyboard = new Storyboard(); var translateTransform = frameworkElement.GetTranform<TranslateTransform>(); if (!double.IsNaN(destX)) { if (Math.Abs(translateTransform.X - destX) > DoubleEpsilon) { var animation = new DoubleAnimation { From = translateTransform.X, To = destX, Duration = new Duration(timeSpan), EasingFunction = easingFunction }; Storyboard.SetTarget(animation, translateTransform); Storyboard.SetTargetProperty(animation, "X"); storyboard.Children.Add(animation); } } if (!double.IsNaN(destY)) { if (Math.Abs(translateTransform.Y - destY) > DoubleEpsilon) { var animation = new DoubleAnimation { From = translateTransform.Y, To = destY, Duration = new Duration(timeSpan), EasingFunction = easingFunction }; Storyboard.SetTarget(animation, translateTransform); Storyboard.SetTargetProperty(animation, "Y"); storyboard.Children.Add(animation); } } var scaleTransform = frameworkElement.GetTranform<ScaleTransform>(); if (!double.IsNaN(centerX)) scaleTransform.CenterX = centerX; if (!double.IsNaN(centerY)) scaleTransform.CenterX = centerY; if (!double.IsNaN(scaleX)) { if (Math.Abs(scaleTransform.ScaleX - scaleX) > DoubleEpsilon) { var animation = new DoubleAnimation { From = scaleTransform.ScaleX, To = scaleX, Duration = new Duration(timeSpan), EasingFunction = easingFunction }; Storyboard.SetTarget(animation, scaleTransform); Storyboard.SetTargetProperty(animation, "ScaleX"); storyboard.Children.Add(animation); } } if (!double.IsNaN(scaleY)) { if (Math.Abs(scaleTransform.ScaleY - scaleY) > DoubleEpsilon) { var animation = new DoubleAnimation { From = scaleTransform.ScaleY, To = scaleY, Duration = new Duration(timeSpan), EasingFunction = easingFunction }; Storyboard.SetTarget(animation, scaleTransform); Storyboard.SetTargetProperty(animation, "ScaleY"); storyboard.Children.Add(animation); } } if (storyboard.Children.Count > 0) { storyboard.Begin(); return storyboard; } return null; } /// <summary> /// 播放一个包含透明度和移动变化的动画 /// </summary> /// <param name="uiElement">作用UI元素</param> /// <param name="timeSpan">动画时长</param> /// <param name="destOpacity">目标Opacity</param> /// <param name="changeVisibility">是否允许改变UI元素的Visibility属性,建议“是”</param> /// <param name="fromX">起始坐标X</param> /// <param name="fromY">起始坐标Y</param> /// <param name="destX">目标的坐标X</param> /// <param name="destY">目标的坐标Y</param> /// <param name="easingFunctionForFade">EasingFunction</param> /// <param name="easingFunctionForMove">EasingFunction</param> /// <returns>当成功播放动画时会返回一个Storyboard对象</returns> public static Storyboard PlayFadeMoveAnimation( this UIElement uiElement, TimeSpan timeSpan, double destOpacity, bool changeVisibility = true, double fromX = double.NaN, double fromY = double.NaN, double destX = double.NaN, double destY = double.NaN, EasingFunctionBase easingFunctionForFade = null, EasingFunctionBase easingFunctionForMove = null ) { var storyboard = new Storyboard { FillBehavior = FillBehavior.HoldEnd }; if (changeVisibility && destOpacity > DoubleEpsilon && uiElement.Visibility != Visibility.Visible) { uiElement.Opacity = 0; uiElement.Visibility = Visibility.Visible; } var fadeAnimation = new DoubleAnimation { From = uiElement.Opacity, To = destOpacity, Duration = new Duration(timeSpan) }; if (easingFunctionForFade != null) { fadeAnimation.EasingFunction = easingFunctionForFade; } Storyboard.SetTarget(fadeAnimation, uiElement); Storyboard.SetTargetProperty(fadeAnimation, "Opacity"); fadeAnimation.Completed += (sender, e) => { uiElement.Opacity = destOpacity; if (changeVisibility) { if (destOpacity < DoubleEpsilon) { uiElement.Visibility = Visibility.Collapsed; } } }; storyboard.Children.Add(fadeAnimation); if (!double.IsNaN(destX) || !double.IsNaN(destY)) { var translateTransform = uiElement.GetTranform<TranslateTransform>(); if (!double.IsNaN(destX)) { var x = double.IsNaN(fromX) ? translateTransform.X : fromX; if (Math.Abs(x - destX) > DoubleEpsilon) { var animation = new DoubleAnimation { From = x, To = destX, Duration = new Duration(timeSpan), EasingFunction = easingFunctionForMove }; Storyboard.SetTarget(animation, translateTransform); Storyboard.SetTargetProperty(animation, "X"); storyboard.Children.Add(animation); } } if (!double.IsNaN(destY)) { var y = double.IsNaN(fromY) ? translateTransform.X : fromY; if (Math.Abs(y - destY) > DoubleEpsilon) { var animation = new DoubleAnimation { From = y, To = destY, Duration = new Duration(timeSpan), EasingFunction = easingFunctionForMove }; Storyboard.SetTarget(animation, translateTransform); Storyboard.SetTargetProperty(animation, "Y"); storyboard.Children.Add(animation); } } } if (storyboard.Children.Count > 0) { storyboard.Begin(); return storyboard; } return null; } #endregion #region Transform helper /// <summary> /// 获得或创建一个新的Transform对象 /// </summary> /// <typeparam name="T">指定一个Transform类型</typeparam> /// <param name="uiElement">UI元素</param> /// <returns>一个Transform对象</returns> public static T GetTranform<T>(this UIElement uiElement) where T : Transform, new() { if (uiElement.RenderTransform == null) { var newTransformGroup = new TransformGroup(); var newTransfrom = new T(); newTransformGroup.Children.Add(newTransfrom); uiElement.RenderTransform = newTransformGroup; return newTransfrom; } var transformGroup = uiElement.RenderTransform as TransformGroup; if (transformGroup != null) { if (transformGroup is T) { return transformGroup as T; } var r = GetTranform<T>(transformGroup); if (r != null) { return r; } var newTransfrom = new T(); transformGroup.Children.Add(newTransfrom); return newTransfrom; } var transform = uiElement.RenderTransform as T; if (transform != null) { return transform; } var newTransformGroup1 = new TransformGroup(); var newTransfrom1 = new T(); //如果原来不是MatrixTransform矩阵,则加入 var matrixTransform = uiElement.RenderTransform as MatrixTransform; if (matrixTransform == null) { newTransformGroup1.Children.Add(uiElement.RenderTransform); } newTransformGroup1.Children.Add(newTransfrom1); uiElement.RenderTransform = newTransformGroup1; return newTransfrom1; } private static T GetTranform<T>(TransformGroup transformGroup) where T : Transform, new() { foreach (var child in transformGroup.Children) { if (child is T) { return (T)child; } var group1 = child as TransformGroup; if (group1 != null) { var r = GetTranform<T>(group1); if (r != null) { return r; } } } return null; } #endregion }
需要特别说一下的是:在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的附加属性。
相关文章推荐
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十二)重构 – 让代码插上翅膀自由飞翔
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十二)重构 – 让代码插上翅膀自由飞翔
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十二)重构 – 让代码插上翅膀自由飞翔
- 从Flash到Silverlight进阶教程-用代码来创建动画
- 代码创建 WPF 旋转、翻转动画(汇总)
- silverlight如何在运行时用代码动态控制(或创建)动画
- 代码创建 WPF 旋转动画
- C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十二)重构 – 让代码插上翅膀自由飞翔
- 代码创建 WPF 旋转动画
- Silverlight实用窍门系列:20.后台CS代码中创建四种常用的动画效果【附带源码实例】
- silverlight如何在运行时用代码动态控制(或创建)动画
- 代码创建 WPF 旋转动画
- Silverlight后台CS代码中创建四种常用的动画效果
- 代码创建 WPF 旋转动画
- Silverlight后台CS代码中创建四种常用的动画效果
- Silverlight 代码创建动画 原创示例
- Silverlight实用窍门系列:20.后台CS代码中创建四种常用的动画效果【附带源码实例】
- WPF/Silverlight深度解决方案:(十八)GPU硬件加速下Silverlight超性能动画实现(下)
- 只使用代码创建WPF应用程序
- WPF中创建水晶动画效果的treeview控件