在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代码创建动画的思路的更多相关文章

  1. wpf/wp/win8中的代码编写过程

    0.根据需求文档,完成前端界面显示 1.定义事件,初始化事件并定义方法. 2.定义加载数目和当先显示数目,定义方法所需要的变量. 3.编写方法所需要的接口以及接口实现. 4.在方法中引用接口. 5.实 ...

  2. wpf 创建动画三种方式

    动画类型 : 故事版,CompositionTarget,DispachTime 那么到此,三种动态创建动画的方法都已经详细介绍过了,大家可能会有种感觉,比较钟情于第一种WPF/Silverlight ...

  3. 《Programming WPF》翻译 第8章 5.创建动画过程

    原文:<Programming WPF>翻译 第8章 5.创建动画过程 所有在这章使用xaml举例说明的技术,都可以在代码中使用,正如你希望的.可是,代码可以使用动画在某种程度上不可能在x ...

  4. 示例:WPF中自定义StoryBoarService在代码中封装StoryBoard、Animation用于简化动画编写

    原文:示例:WPF中自定义StoryBoarService在代码中封装StoryBoard.Animation用于简化动画编写 一.目的:通过对StoryBoard和Animation的封装来简化动画 ...

  5. 【WPF学习】第五十七章 使用代码创建故事板

    在“[WPF学习]第五十章 故事板”中讨论了如何使用代码创建简单动画,以及如何使用XAML标记构建更复杂的故事板——具有多个动画以及播放控制功能.但有时采用更复杂的故事板例程,并在代码中实现全部复杂功 ...

  6. WPF使用后台C#代码创建Grid

    笔者刚刚接触WPF,菜鸟一枚,在做一个练手程序时遇到这样一个需求,创建一个新的Grid并将其添加至一个ListView中,要求Grid及其子元素应按一定顺序给Name属性赋值,直接使用XAML创建的话 ...

  7. [WPF]如何使用代码创建DataTemplate(或者ControlTemplate)

    1. 前言 上一篇文章([UWP]如何使用代码创建DataTemplate(或者ControlTemplate))介绍了在UWP上的情况,这篇文章再稍微介绍在WPF上如何实现. 2. 使用Framew ...

  8. SharePoint 2013 代码创建应用程序目录(App Catalog)

    众所周知,SharePoint App是2013版本的一大特色,那么,关于App的分发有几种方式呢?SharePoint给我们提供了两种方式,一种是上载到SharePoint应用商店,另一种是在本地S ...

  9. WPF,Silverlight与XAML读书笔记第四十三 - 多媒体支持之文本与文档

    说明:本系列基本上是<WPF揭秘>的读书笔记.在结构安排与文章内容上参照<WPF揭秘>的编排,对内容进行了总结并加入一些个人理解. Glyphs对象(WPF,Silverlig ...

随机推荐

  1. Google 翻译如何获取 tk 参数值?

    1.首先获取 TKK 参数,这个参数可以在 https://translate.google.com 网页获取, src:TKK=eval('((function(){var a\x3d2089517 ...

  2. 深度学习:Keras入门(一)之基础篇(转)

    转自http://www.cnblogs.com/lc1217/p/7132364.html 1.关于Keras 1)简介 Keras是由纯python编写的基于theano/tensorflow的深 ...

  3. redis的安装配置(转)

    一.下载windows版本的Redis 去官网找了很久,发现原来在官网上可以下载的windows版本的,现在官网以及没有下载地址,只能在github上下载,官网只提供linux版本的下载 官网下载地址 ...

  4. SpringMVC:学习笔记(10)——整合Ckeditor且实现图片上传

    SpringMVC:学习笔记(10)——整合Ckeditor且实现图片上传 配置CKEDITOR 精简文件 解压之后可以看到ckeditor/lang下面有很多语言的js,如果不需要那么多种语言的,可 ...

  5. Openstak(M版)计算节点安装

    #############修改hosts文件 10.0.0.11 controller10.0.0.31 compute110.0.0.32 compute210.0.0.41 block110.0. ...

  6. CSS清除浮动使父级元素展开的三个方法

    点评:一个没有设置高度的容器div内如果存在浮动元素(即使用了属性float:left或者float:right),那么该父级元素会无法展开,下面举个例子为大家详细介绍下,希望对大家有所帮助 一个没有 ...

  7. VC6.0中添加库文件和头文件

    附加头文件包含 VC6.0中: VC6.0默认include包含路径:Tools>Options>Directories>Include files. 对于特定项目的头文件包含,在“ ...

  8. sublime text C++

    几乎每一门编程语言都是从"Hello, world!"学起的, 刚学编程的时候感觉有点枯燥, 对它不够重视. 可是到后来慢慢发现, 几乎我学到的每一个知识点, 在最开始都是经过 h ...

  9. Shell编程基础及变量

    一.Shell脚本 1.Shell脚本的建立 由Linux命令.shell命令.程序结构控制语句和注释等内容组成. 脚本第一行 #!/bin/bash #!字符称为幻数,内核会根据它后面的解释器来确定 ...

  10. indy10 UDP实例

    UDP就比较简单了,放个按钮,一个TIdUDPServerTIdUDPServer绑定 0.0.0.0:3820,然后Active设置为True //发送按钮procedure TForm1.Butt ...