在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. Delphi重定义的消息结构

    // 除去DDE和MDI消息,一共159个消息,其中部分消息仅仅的转定义 // 普通消息,有两个参数和结果 PMessage = ^TMessage; TMessage = packed record ...

  2. mapper文件提示:No data sources are configured to run this sql

    mapper文件发出黄色警告. 输入数据库用户名和密码等等. 自动同步ok 就会发现代码变绿了,ok

  3. Open SQL和Native SQL到底有什么本质的区别

    1.個人愚見:它們只是在实现的方式上,执行效率上不同,有的书上还说native sql存在一定风险 *& 20170521 171300 1.Open sql 是由创建数据库数据的ABAP命令 ...

  4. C语言预处理命令的使用

    cppreference.com -> 预处理命令 -> 详细说明 预处理命令 #,## # 和 ## 操作符是和#define宏使用的. 使用# 使在#后的首个参数返回为一个带引号的字符 ...

  5. 异常:Retrieving the COM class factory for component with CLSID {00024500-0000-0000-C000-000000000046} failed due to the following error: 80070005.

    异常:Retrieving the COM class factory for component with CLSID {00024500-0000-0000-C000-000000000046} ...

  6. offsetHeight+scrollHeight+clientHeight

    ch 窗口可见区域高度 :ch = padding + height(height不是所有内容的高度,是样式定义的高度) oh border以内的内容高度: oh = border + padding ...

  7. range基础

    collapse这个方法是把结束位置抛弃掉,并不是简单的设置到开始位置. 结束位置被抛弃掉以后,只要没有给它重新设置位置,它就一直都会等 于开始位置.即使你修改了开始位置,结束位置还是会在修改后的开始 ...

  8. 预防SQL注入攻击

    /** * 预防SQL注入攻击 * @param string $value * @return string */ function check_input($value) { // 去除斜杠 if ...

  9. Loadrunner脚本篇——从文件中读取内容并参数化

    直接代码展示: char* testfn() { int count, total = 0; char * buffer = NULL; int filelenth = 0; long file_st ...

  10. 活用:after 让图片垂直居中

    现在莫名虽然更喜欢 background 但大多时候还是选择用 img,这其中的利弊争议不在本文中赘述. 那么在布局中常会遇到定高容器中图片居中的需求,这时就有很多方法了呀: line-height ...