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(), destOpacity: , fromX: , destX: ,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 ==
|| timeSpanValues.Length != opacityValues.Length)
{
return null;
} var animation = new DoubleAnimationUsingKeyFrames();
for (int i = ; 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 = ;
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 = ;
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 > )
{
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 = ;
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 > )
{
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的附加属性。
WPF/WP/Silverlight/Metro App代码创建动画的思路的更多相关文章
- wpf/wp/win8中的代码编写过程
0.根据需求文档,完成前端界面显示 1.定义事件,初始化事件并定义方法. 2.定义加载数目和当先显示数目,定义方法所需要的变量. 3.编写方法所需要的接口以及接口实现. 4.在方法中引用接口. 5.实 ...
- wpf 创建动画三种方式
动画类型 : 故事版,CompositionTarget,DispachTime 那么到此,三种动态创建动画的方法都已经详细介绍过了,大家可能会有种感觉,比较钟情于第一种WPF/Silverlight ...
- 《Programming WPF》翻译 第8章 5.创建动画过程
原文:<Programming WPF>翻译 第8章 5.创建动画过程 所有在这章使用xaml举例说明的技术,都可以在代码中使用,正如你希望的.可是,代码可以使用动画在某种程度上不可能在x ...
- 示例:WPF中自定义StoryBoarService在代码中封装StoryBoard、Animation用于简化动画编写
原文:示例:WPF中自定义StoryBoarService在代码中封装StoryBoard.Animation用于简化动画编写 一.目的:通过对StoryBoard和Animation的封装来简化动画 ...
- 【WPF学习】第五十七章 使用代码创建故事板
在“[WPF学习]第五十章 故事板”中讨论了如何使用代码创建简单动画,以及如何使用XAML标记构建更复杂的故事板——具有多个动画以及播放控制功能.但有时采用更复杂的故事板例程,并在代码中实现全部复杂功 ...
- WPF使用后台C#代码创建Grid
笔者刚刚接触WPF,菜鸟一枚,在做一个练手程序时遇到这样一个需求,创建一个新的Grid并将其添加至一个ListView中,要求Grid及其子元素应按一定顺序给Name属性赋值,直接使用XAML创建的话 ...
- [WPF]如何使用代码创建DataTemplate(或者ControlTemplate)
1. 前言 上一篇文章([UWP]如何使用代码创建DataTemplate(或者ControlTemplate))介绍了在UWP上的情况,这篇文章再稍微介绍在WPF上如何实现. 2. 使用Framew ...
- SharePoint 2013 代码创建应用程序目录(App Catalog)
众所周知,SharePoint App是2013版本的一大特色,那么,关于App的分发有几种方式呢?SharePoint给我们提供了两种方式,一种是上载到SharePoint应用商店,另一种是在本地S ...
- WPF,Silverlight与XAML读书笔记第四十三 - 多媒体支持之文本与文档
说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. Glyphs对象(WPF,Silverlig ...
随机推荐
- B-Tree vs LSM-tree
什么是B-树 一.已排序文件的查找时间 对一个有N笔记录的已排序表进行二叉查找,可以在O(log2N)比较级完成.如果表有1,000,000笔记录,那么定位其中一笔记录,将在20 ( log21,00 ...
- T-SQL利用笛卡尔积累计、累加
T-SQL利用笛卡尔积累计.累加 笛卡尔积 --原始数据 select templateid,needitem1Count from db_tank..TS_CardMain --累计数据 selec ...
- New Moto X 2014 全版本RSD&Fastboot刷官方底包教程
本来我是不想写教程的,因为这样的教程实在是太多了,基本上大家也都会了,为什么还要多次一举,发来发去的呢?实在没什么意义!但是我觉得吧,别人的教程写的都太过简单,太过明了了,有时候我们很难理解,这到底是 ...
- loadrunder之脚本篇——集合点设置
1 作用 通过让多用户在同一时间点上进行并发操作来测试系统的并发处理的能力 2 实现 通过集合点函数来实现. 注意:集合点经常和事务结合起来使用.集合点只能插入到Action部分,vuser_in ...
- Oracle索引表
索引组织表(Index-Organized Table)是按B-树的结构来组织和存储数据的.与标准表中的数据时无序存放的不同,索引表中数据按主键值有序存储. 叶子节点中存放的是表的主键值与所有非主键值 ...
- python 课堂笔记-if语句
# Author:zyl _username = 'zyl' _password = 'zyl123' username = input("username:") password ...
- Github结合Eclipse出现的问题
半年前因为学习Git花费了很长时间,半年过去了,因为不使用,基本全部忘记了,最近在公司需要使用Eclipse开发相关项目,用到前期的测试数据挖掘的小算法,又重拾Git,不过这次不再是命令行模式,而是结 ...
- 配置NFS作为HDFS高可用的共享存储系统
所有命令或步骤: 首先,在各个节点上安装nfs服务 yum install -y nfs service rpcbind start service nfs start 配置开机自启动服务 chkco ...
- SOA 面向服务架构 阅读笔记(一)
Service Oriented Architecture 面向服务架构 学习笔记(一) 1.业务自由 1.1 在很多企业中,业务和IT技术是各自独立的,无法使用通用的统一语言进行管理. 1.2 ...
- 关于Kinect音频开发的探究
1.笔者在<Kinect体感程序设计入门>(王森著)的这本书中看到可以使用powershell和COM对象无缝整合,轻松的使用windows系统自带的语音合成功能. 步骤:•打开进入pow ...