9.7 模拟实现微信的彩蛋动画

大家在玩微信的时候有没有发现节日的时候发一些节日问候语句如“情人节快乐”,这时候会出现很多爱心形状从屏幕上面飘落下来,我们这小节就是要模拟实现这样的一种动画效果。可能微信里面实现的动画效果都是采用固定的小图片来最为动画的对象,但是我们这小节要对该动画效果增加一些改进,也就是要实现的彩蛋动画的针对的图形形状是动态随机生成的,所以看到从屏幕上面飘落的图形都是形状不太一样的。下面来看一下如何实现星星飘落动画。

9.7.1 实现的思路

首先,我们来分析一下星星飘落动画的实现思路。

(1)动画对象

微信的彩蛋动画可以采用固定的图片对象作为动画的对象,但是因为我们要实现的星星动画,这个星星的形状和颜色是不一样的,所以不能直接用图片做为星星的对象。要创建出不同的星星形状需要用到Path图形绘图的方式来实现,定义好画图的规则,然后用随机数来决定某些点的位置这样就可以绘制出各种不同形状的星星。颜色的随机定义就很好办了,可以先产生随机的0-255的三原色色值,然后用三原色的方式来创建颜色对象。

(2)动画的实现方式

从微信的彩蛋动画效果可以看出来,这些动画的随机型很大,轨迹运动轨迹、速度、方向都是随机,这种情况是很难使用线性插值动画或者关键帧动画去实现的,所以这个动画的实现方式很明显是要采用基于帧动画去实现。在基于帧动画里面,通过CompositionTarget.Rendering事件的处理程序来实现动画的效果,星星飘落的动画效果是星星对象从一个固定区域的最上面某个位置飘落到该区域的最底下,然后再销毁星星对象。我们可以采用Canvas面板来定义动画的区域,用Canvas.LeftProperty和Canvas.TopProperty属性作为定义星星对象位置的坐标,然后通过改变这个坐标来实现星星的下落。

(3)动画的封装

在对动画的实现方式和动画对象的封装的思路清楚之后,我们开始考虑如何来封装这样的一种动画效果。星星对象的实现需要通过一个星星工厂类(StarFactory类)来封装星星创建的逻辑。因为每个星星的运动轨迹都是不一样的,都有其自己飘落的速度和方向,所以我们需要封装一个星星的对象,在星星对象里面负责处理星星的速度,方向,运动轨迹等逻辑。最后动画可以通过附加属性的方式在Canvas面板上实现动画。

下面我们来详细地看一下星星飘落动画的编码实现。

代码清单9-18星星飘落动画(源代码:第9章\Examples_9_18)

9.7.2 星星创建工厂

首先,我们先来实现星星的创建工厂StarFactory类,StarFactory类的作用是创建动画里面的星星对象,动画的实现需要向调用StarFactory类来创建出星星对象,然后对星星进行动画处理,所以StarFactory类是一个非常单一的星星构造工厂,里面不涉及动画的操作,只涉及星星Path对象的创建。

(1)星星对象的绘图原理

星星的对象的绘图坐标模型如图9.28所示,首先,需要确定确定坐标系三个点a、b、c,然后再取三条线段ab、ac、bc的三分之一和三分之二的点坐标和两点区域之前一个随机点,经过这样一个处理之后原来的是3条线段的星星图形,变成了3*4条线段的星星图形,在递归一次就会变成3*4*4条线段的星星图形如此类推。

下面把星星图形形状的构造封装了3个方法:_RefactorPoints方法是用于取两个点线段之间的三分之一点、三分之二点和两点区域间的随机点,最后在加上原来的两个点,返回一个5个点的点集合;_RecurseSide方法是封装了两个点之间递归之后所取到的点集合;_CreatePath方法则是把这些点集合连接起来创建一个Path图形表示星星图形。三个方法的代码如下所示:

StarFactory.cs文件部分代码
------------------------------------------------------------------------------------------------------------------
/// <summary>
/// 把两个点转化成多级的点的集合
/// </summary>
/// <param name="a">第一个点</param>
/// <param name="b">第二个点</param>
/// <param name="level">递归的次数</param>
/// <returns>新的点的集合</returns>
private static List<Point> _RecurseSide(Point a, Point b, int level)
{
// 递归完成后,返回此线段
if (level == )
{
return new List<Point> { a, b };
}
else
{
// 首先,需要建立起第一次递归的点的列表,一直递归到级别为0
List<Point> newPoints = new List<Point>();
// 把区域分成5个点
foreach (Point point in _RefactorPoints(a, b))
{
newPoints.Add(point);
}
List<Point> aggregatePoints = new List<Point>();
// 把每一个线段进一步分解
for (int x = ; x < newPoints.Count; x++)
{
int y = x + == newPoints.Count ? : x + ;
aggregatePoints.AddRange(_RecurseSide(newPoints[x], newPoints[y], level - ));
}
return aggregatePoints;
}
}
/// <summary>
/// 通过输入两个点来构建一个有多个三角形组成的Star形状
/// </summary>
/// <param name="a">第一个点</param>
/// <param name="b">第二个点</param>
/// <returns>一个新的几何图形的点的集合</returns>
private static IEnumerable<Point> _RefactorPoints(Point a, Point b)
{
// 第一个点
yield return a;
double dX = b.X - a.X;
double dY = b.Y - a.Y;
// 第一个点到第二个点1/3处的一个点
yield return new Point(a.X + dX / 3.0, a.Y + dY / 3.0);
double factor = _random.NextDouble() - 0.5;
double vX = (a.X + b.X) / (2.0 + factor) + Math.Sqrt(3.0 + factor) * (b.Y - a.Y) / (6.0 + factor * 2.0);
double vY = (a.Y + b.Y) / (2.0 + factor) + Math.Sqrt(3.0 + factor) * (a.X - b.X) / (6.0 + factor * 2.0);
// 中间的三角形的顶点
yield return new Point(vX, vY);
//第二个点到第一个点1/3处的一个点
yield return new Point(b.X - dX / 3.0, b.Y - dY / 3.0);
//第二个点
yield return b;
}
/// <summary>
/// 使用一系列的点来创建路径图形
/// </summary>
/// <param name="points">点的集合</param>
/// <returns>路径图形</returns>
private static Path _CreatePath(List<Point> points)
{
PathSegmentCollection segments = new PathSegmentCollection();
bool first = true;
// 把点添加到线段里面
foreach (Point point in points)
{
if (first)
{
first = false;
}
else
{
segments.Add(
new LineSegment
{
Point = point
});
}
}
PathGeometry pathGeometry = new PathGeometry();
//通过线段构建几何图形
pathGeometry.Figures.Add(
new PathFigure
{
IsClosed = true,
StartPoint = points[],
Segments = segments
});
return new Path { Data = pathGeometry };
}

(2)星星颜色的随机生成

星星颜色的产生是通过ARGB的数值来进行创建,这样更加方便用随机数进行处理。因为对星星填充的属性是需要用画刷对象来赋值的,所以需要用随机颜色来创建画刷,这里用线性渐变画刷LinearGradientBrush来填充Path图形。封装的方法_GetColor表示创建随机的颜色对象,_ColorFactory表示对Path图形填充随机的颜色画刷。代码如下所示:

StarFactory.cs文件部分代码
------------------------------------------------------------------------------------------------------------------
/// <summary>
/// 添加颜色到路径图形
/// </summary>
/// <param name="input">路径图形</param>
private static void _ColorFactory(Path input)
{
LinearGradientBrush brush = new LinearGradientBrush();
brush.StartPoint = new Point(, );
brush.EndPoint = new Point(1.0, 1.0);
GradientStop start = new GradientStop();
start.Color = _GetColor();
start.Offset = ;
GradientStop middle = new GradientStop();
middle.Color = _GetColor();
middle.Offset = _random.NextDouble();
GradientStop end = new GradientStop();
end.Color = _GetColor();
end.Offset = 1.0;
brush.GradientStops.Add(start);
brush.GradientStops.Add(middle);
brush.GradientStops.Add(end);
input.Fill = brush;
}
/// <summary>
/// 获取一个随机的颜色
/// </summary>
/// <returns></returns>
private static Color _GetColor()
{
Color color = new Color();
color.A = (byte)(_random.Next() + );
color.R = (byte)(_random.Next() + );
color.G = (byte)(_random.Next() + );
color.B = (byte)(_random.Next() + );
return color;
}

(3)创建星星对象

创建星星对象需要先有三个点,然后在利用这三个点根据创建星星图形的原理(3*4n,n表示星星递归的层次)来创建星星图形,然后用随机颜色画刷来填充图形。同时为了更加个性化,也对图形做了随机角度的旋转变换特效。代码如下所示:

StarFactory.cs文件部分代码
------------------------------------------------------------------------------------------------------------------
const int MIN = ;
const int MAX = ;
// 随机数产生器
static readonly Random _random = new Random();
// 创建一个Star
public static Path Create()
{
Point a = new Point(, );
Point b = new Point(_random.NextDouble() * 70.0 + 15.0, );
Point c = new Point(, b.X);
int levels = _random.Next(MIN,MAX);
List<Point> points = new List<Point>();
points.AddRange(_RecurseSide(a, b, levels));
points.AddRange(_RecurseSide(b, c, levels));
points.AddRange(_RecurseSide(c, a, levels));
// 画边
Path retVal = _CreatePath(points);
// 添加颜色
_ColorFactory(retVal);
// 建立一个旋转的角度
RotateTransform rotate = new RotateTransform();
rotate.CenterX = 0.5;
rotate.CenterY = 0.5;
rotate.Angle = _random.NextDouble() * 360.0;
retVal.SetValue(Path.RenderTransformProperty, rotate);
return retVal;
}

9.7.3 实现单个星星的动画轨迹

星星对象构造工厂实现之后,接下来就需要实现对星星实体(StarEntity类)的封装了,在StarEntity类里面要实现基于帧动画,在帧刷新事件处理程序里面实现星星飘落的动画逻辑。首先需要处理的是确定星星在区域最顶部的随机位置,下落的随机速度和方向,然后在动画的过程中需要去判断星星是否碰撞到了区域的左边距或者右边距,碰撞之后则需要往反弹回来往另外一边运动。最后还需要判断星星是否已经落到了对底下,如果落到了区域最底下,则需要移除CompositionTarget.Rendering事件和从画布上移除星星图形,还要触发StarflakeDied事件来告知调用方星星已经销毁掉了。StarEntity类的代码如下所示:

StarEntity.cs文件代码
------------------------------------------------------------------------------------------------------------------
/// <summary>
/// Star实体,封装Star的行为
/// </summary>
public class StarEntity
{
// 左边距
const double LEFT = ;
// 上边距
const double TOP = ;
// 离开屏幕
const double GONE = ;
//随机近似数
private double _affinity;
// Star实体的唯一id
private Guid _identifier = Guid.NewGuid();
// 随机数产生器
private static Random _random = new Random();
// Star所在的画布
private Canvas _surface;
// 获取Star所在的画布
public Canvas Surface
{
get { return _surface; }
}
// X,Y坐标和相对速度
private double x, y, velocity;
// Star的路径图形
private Path _starflake;
// 获取Star实体的唯一id
public Guid Identifier
{
get { return _identifier; }
}
// 默认的构造器
public StarEntity(Action<Path> insert)
: this(insert, true)
{
}
/// <summary>
/// 星星对象构造方法
/// </summary>
/// <param name="fromTop">是否从顶下落下</param>
public StarEntity(Action<Path> insert, bool fromTop)
{
_starflake = StarFactory.Create();
//产生0到1的随机数
_affinity = _random.NextDouble();
// 设置速度,和初始化x y轴
velocity = _random.NextDouble() * ;
x = _random.NextDouble() * LEFT;
y = fromTop ? : _random.NextDouble() * TOP;
// 设置Star在画布的位置
_starflake.SetValue(Canvas.LeftProperty, x);
_starflake.SetValue(Canvas.TopProperty, y);
// 添加到画布上
insert(_starflake);
// 记录下Star的画布
_surface = _starflake.Parent as Canvas;
// 订阅基于帧动画事件
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
// 基于帧动画事件处理
void CompositionTarget_Rendering(object sender, object e)
{
_Frame();
}
// Star下落的每一帧的处理
private void _Frame()
{
// 下降的y轴的大小
y = y + velocity + 3.0 * _random.NextDouble() - 1.0;
// 判断是否离开了屏幕
if (y > GONE)
{
CompositionTarget.Rendering -= CompositionTarget_Rendering;
_surface.Children.Remove(_starflake);
// 通知外部,Star已经被清除
EventHandler handler = StarflakeDied;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
else
{
// 水平轻推
double xFactor = 10.0 * _affinity;
if (_affinity < 0.5) xFactor *= -1.0;//小于0.5向左边移动 大于0.5向右边移动 等于0.5垂直下降
x = x + _random.NextDouble() * xFactor;
// 左边的边缘
if (x < )
{
x = ;
_affinity = 1.0 - _affinity;
}
// 右边的边缘
if (x > LEFT)
{
x = LEFT;
_affinity = 1.0 - _affinity;
}
_starflake.SetValue(Canvas.LeftProperty, x);
_starflake.SetValue(Canvas.TopProperty, y);
}
// 转动
RotateTransform rotate = (RotateTransform)_starflake.GetValue(Path.RenderTransformProperty);
rotate.Angle += _random.NextDouble() * 4.0 * _affinity;
}
// 当Star飘落到底下的时候的回收Star事件
public event EventHandler StarflakeDied;
// 重载获取唯一的对象码GetHashCode方法
public override int GetHashCode()
{
return Identifier.GetHashCode();
}
// 重载实现判断对象是否一样的Equals方法
public override bool Equals(object obj)
{
return obj is StarEntity && ((StarEntity)obj).Identifier.Equals(Identifier);
}
}

9.7.4 封装批量星星飘落的逻辑

StarEntity类实现了一个星星的动画逻辑的封装,下面要实现一个StarBehavior类用附加属性的方式在Canvas上添加批量的星星飘落的动画。StarBehavior类里面通过AttachStarFlake属性表示是否在该Canvas面板上添加星星飘落动画,当设置为true的时候表示触发动画的开始,false则表示停止添加星星,知道星星全部飘落到底下的时候动画停止。在开始播放动画的时候会初始化多个StarEntity对象,并运行其飘落的动画效果,当飘落到底下StarEntity对象被销毁的时候,会触发StarflakeDied事件,在StarflakeDied事件里面继续初始化新的StarEntity对象,如果动画要被停止了beginning = false,则不再创建新的StarEntity对象。StarBehavior类的代码如下所示:

StarBehavior.cs文件代码
------------------------------------------------------------------------------------------------------------------
/// <summary>
/// StarBehavior类管理附加属性的行为触发批量星星的构造和动画的实现
/// </summary>
public static class StarBehavior
{
// 屏幕上生成的星星数量
const int CAPACITY = ;
// 动画是否已经开始的标识符
private static bool beginning = false;
// Star对象列表
private static List<StarEntity> _starflakes = new List<StarEntity>(CAPACITY);
// 添加动画效果的属性
public static DependencyProperty AttachStarFlakeProperty = DependencyProperty.RegisterAttached(
"AttachStar",
typeof(bool),
typeof(StarBehavior),
new PropertyMetadata(false, new PropertyChangedCallback(_Attach)));
// 获取属性方法
public static bool GetAttachStarFlake(DependencyObject obj)
{
return (bool)obj.GetValue(AttachStarFlakeProperty);
}
// 设置属性方法
public static void SetAttachStarFlake(DependencyObject obj, bool value)
{
obj.SetValue(AttachStarFlakeProperty, value);
}
// 附加属性属性改变事件处理方法
public static void _Attach(object sender, DependencyPropertyChangedEventArgs args)
{
Canvas canvas = sender as Canvas;
if (canvas != null && args.NewValue != null && args.NewValue.GetType().Equals(typeof(bool)))
{
if ((bool)args.NewValue)
{
// 画布上还有子元素证明星星还没全部飘落下去
if (canvas.Children.Count > )
{
return;
}
// 开始动画
beginning = true;
for (int x = ; x < _starflakes.Capacity; x++)
{
StarEntity starflake = new StarEntity((o) => canvas.Children.Add(o));
starflake.StarflakeDied += new EventHandler(Starflake_StarflakeDied);
_starflakes.Add(starflake);
}
}
else
{
// 结束动画
beginning = false;
}
}
}
// 回收Star的事件
static void Starflake_StarflakeDied(object sender, EventArgs e)
{
StarEntity starflake = sender as StarEntity;
// 获取Star的面板,用来添加一个新的Star
Canvas canvas = starflake.Surface;
_starflakes.Remove(starflake);
if (beginning)
{
// 如果动画还在继续运行一个Star消失之后再创建一个新的Star
StarEntity newFlake = new StarEntity((o) => canvas.Children.Add(o), true);
newFlake.StarflakeDied += Starflake_StarflakeDied;
_starflakes.Add(newFlake);
}
}
}

9.7.5 星星飘落动画演示

上面对星星飘落动画的逻辑都已经封装好了,下面通过一个Windows 10的例子来使用星星飘落动画。

MainPage.xaml文件主要代码
------------------------------------------------------------------------------------------------------------------
<Canvas Grid.Row="1" x:Name="myCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ></Canvas>
<Button Grid.Row="1" x:Name="button" VerticalAlignment="Bottom" Content="开始星星飘落" Click="button_Click_1"></Button>
MainPage.xaml.cs文件主要代码
------------------------------------------------------------------------------------------------------------------
// 按钮事件,播放动画和停止动画
private async void button_Click_1(object sender, RoutedEventArgs e)
{
if ((bool)myCanvas.GetValue(StarBehavior.AttachStarFlakeProperty) == false)
{
// 要等所有的星星都全部落下去之后才可以再次播放动画
if (myCanvas.Children.Count > )
{
await new MessageDialog("星星动画未完全结束").ShowAsync();
return;
}
myCanvas.SetValue(StarBehavior.AttachStarFlakeProperty, true);
button.Content = "停止新增星星";
}
else
{
myCanvas.SetValue(StarBehavior.AttachStarFlakeProperty, false);
button.Content = "开始星星飘落";
}
}

本文来源于《深入浅出Windows 10通用应用开发》

源代码下载:http://vdisk.weibo.com/u/2186322691

目录:http://www.cnblogs.com/linzheng/p/5021428.html

欢迎关注我的微博@WP林政   微信公众号:wp开发(号:wpkaifa)

Windows10/WP技术交流群:284783431

[深入浅出Windows 10]模拟实现微信的彩蛋动画的更多相关文章

  1. 《深入浅出Windows 10通用应用开发》

        <深入浅出Windows 10通用应用开发>采用Windows 10的SDK进行重新改版,整合了<深入浅出Windows Phone 8.1应用开发>和<深入解析 ...

  2. [深入浅出Windows 10]布局原理

    5.2 布局原理 很多时候在编写程序界面的时候都会忽略了应用布局的重要性,仅仅只是把布局看作是对UI元素的排列,只要能实现布局的效果就可以了,但是在实际的产品开发中这是远远不够的,你可能面临要实现的布 ...

  3. [深入浅出Windows 10]QuickCharts图表控件库解析

    13.4 QuickCharts图表控件库解析     QuickCharts图表控件是Amcharts公司提供的一个开源的图表控件库,这个控件库支持WPF.Silverlight.和Windows等 ...

  4. [深入浅出Windows 10]实现饼图控件

    13.2 实现饼图控件 上一小节讲解了动态生成折线图和区域图,对于简单的图形这样通过C#代码来生成的方式是很方便的,但是当我们的图表要实现更加复杂的逻辑的时候,这种动态生成的方式就显得力不从心了,那就 ...

  5. [深入浅出Windows 10]不同平台设备的适配

    2.3 不同平台设备的适配 Windows 10通用应用程序针对特定的平台还会有一个子API的集合,当我们要使用到某个平台的特定API的时候(比如手机相机硬件按钮触发事件),这时候就需要调用特定平台的 ...

  6. [深入浅出Windows 10]分屏控件(SplitView)

    4.18 分屏控件(SplitView) 分屏控件(SplitView)是Windows 10新增的控件类型,也是Windows 10通用应用程序主推的交互控件,通常和一个汉堡按钮搭配作为一种抽屉式菜 ...

  7. [深入浅出Windows 10]应用实战:Bing在线壁纸

    本章介绍一个使用Bing搜索引擎背景图接口实现的一个应用——Bing在线壁纸,讲解如何使用网络的接口来实现一个壁纸下载,壁纸列表展示和网络请求封装的内容.通过该例子我们可以学习到如何使用网络编程的知识 ...

  8. 【Windows 10 IoT - 2】LED闪烁及动画绘制(树莓派 Pi2)

    在上一篇博文<Windows 10 IoT系统安装>中,我们实现了在树莓派2平台上运行Window 10 IoT,本篇文章将介绍在该平台上的程序开发. 在最初获得的资讯中,以为Window ...

  9. curl 模拟登录微信公众平台带验证码

    这段时间一直写个项目, 从切图到前端到后台都要搞定,真tm累. 今天下午手残,不停用错误的密码去模拟登录微信公众平台,结果后来出现验证码,瞬间悲剧(菜鸟从来没搞过带验证码的). 研究了一下,发现其实很 ...

随机推荐

  1. Python 与 C# lambda表达式比较

    Python里到lambda表达式非常简约, lam =lambda a: a*2 --> lam(3) 6 在某些情况下确实挺好用到.但是相比C#到lambda表达式,还是不够强大(我不是在黑 ...

  2. Delphi之DLL知识学习2---静态链接和动态链接

    静态连接 静态链接是指Delphi 编译器把要调用的函数和过程编译成可执行代码.函数的代码可存留在应用程序的 .dpr文件或一单元中.当链接用户的应用程序时,这些函数与过程便成为最终的可执行文件的一部 ...

  3. 【JAVA IO流之字节流】

    字节流部分和字符流部分的体系架构很相似,有四个基本流:InputStream.OutputStream.BufferedInputStream.BufferedOutputStream,其中,Inpu ...

  4. java Integer和int的拆箱与装箱

    官网:http://docs.oracle.com/javase/tutorial/java/data/autoboxing.html 1.赋值: a. 把int类型赋值给Integer类型:JVM会 ...

  5. 深入解析结构化异常处理(SEH)

    jpg 改 rar

  6. codeforces724-A. Checking the Calendar 日期题

    首先有这样一个显然的事实,那就是每个月的第一天可以是星期x,x可以取遍1~7 因为日期一直在往后退,总有一年能轮到分割线那天,因为本来其实压根就没有月份的划分,月份划分是人为的 而且我们也不知道开始的 ...

  7. 多态、类库、委托、is和as运算符、泛型集合

    多态:简而言之就是龙生九子,各有不同 有了继承,才有了多态 1.虚方法 virtual 重写 override 父类中的方法,在子类中并不适用,那么子类需要自主更改继承的方法或者是属性,那父类中加了v ...

  8. mybatis 加载配置文件的两种方式

    package com.atguigu.day03_mybaits.test; import java.io.IOException;import java.io.InputStream;import ...

  9. hdu 1087 Super Jumping! Jumping! Jumping! 简单的dp

    Super Jumping! Jumping! Jumping! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 ...

  10. 《锋利的jQruery》读书笔记

    由于是一边看书一边练习,所以干把笔记写在html文档中.想看的同学可以复制到一个html文档中,结合浏览器查看.不得不说<锋利的jQuery>是本好书,建议好好看看.尊重知识产权,请购买正 ...