在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. 001-Spring在代码中获取bean的几种方式

    一.概述 方法一:在初始化时保存ApplicationContext对象 方法二:通过Spring提供的utils类获取ApplicationContext对象 方法三:继承自抽象类Applicati ...

  2. 爬虫四 selenium模块详细参数

    selenium元素定位方法 一.访问页面并获取网页html from selenium import webdriver browser = webdriver.Chrome() browser.g ...

  3. mysql只能连接localhost解决

    grant all privileges on *.* to 'root'@'%' identified by 'root';flush privileges;

  4. FTP主动连接与被动连接

    FTP(File Transfer Protocol, FTP)是TCP/IP网络上两台计算机传送文件的协议,应用层的协议,它基于传输层, FTP是一个8位的客户端-服务器协议,能操作任何类型的文件而 ...

  5. 联合文件系统 unionfs

  6. Python操作SQLAlchemy

    Mysql环境: MySQL 一.概述什么是数据库 ? 答:数据的仓库,如:在ATM的示例中我们创建了一个 db 目录,称其为数据库 什么是 MySQL.Oracle.SQLite.Access.MS ...

  7. Java 中的会话管理—— HttpServlet,Cookies,URL Rewriting(转)

    索引 1.什么是 Session? 2.Java 中的会话管理—— Cookie 3.Java Servlet 中的 Session —— HttpSession 理解 JSESSIONID Cook ...

  8. Go bufio库

    bufio.Scanner bufio包使处理输入和输出方便又高效.Scanner类型是该包最有用的特性之一,它读取输入并将其拆成行或单词:通常是处理行形式的输入最简单的方法.该变量从程序的标准输入中 ...

  9. CCNA 课程 五

    VLSM (可变长子网掩码)也就是子网的划分过程 子网掩码和ip地址相与得到的是IP地址的网络地址(0&1 == 0 : 1&1 == 1) 简单来说就是 IP地址 和 子网掩码 上下 ...

  10. 通用Mapper(Mybatis)

    1.Mapper的简单介绍 2.Mapper的作用 通用Mapper可以通过Mybatis的拦截器原理,动态的帮我们实现单表的增删改查功能,大大降低了我们的开发成本,减少了我们的工作量. 3.Mapp ...