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文件部分代码------------------------------------------------------------------------------------------------------------------ ////// 把两个点转化成多级的点的集合 /// /// 第一个点 /// 第二个点 /// 递归的次数 ///新的点的集合 private static List_RecurseSide(Point a, Point b, int level) { // 递归完成后,返回此线段 if (level == 0) { return new List { a, b }; } else { // 首先,需要建立起第一次递归的点的列表,一直递归到级别为0 List newPoints = new List (); // 把区域分成5个点 foreach (Point point in _RefactorPoints(a, b)) { newPoints.Add(point); } List aggregatePoints = new List (); // 把每一个线段进一步分解 for (int x = 0; x < newPoints.Count; x++) { int y = x + 1 == newPoints.Count ? 0 : x + 1; aggregatePoints.AddRange(_RecurseSide(newPoints[x], newPoints[y], level - 1)); } return aggregatePoints; } } /// /// 通过输入两个点来构建一个有多个三角形组成的Star形状 /// /// 第一个点 /// 第二个点 ///一个新的几何图形的点的集合 private static IEnumerable_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; } /// /// 使用一系列的点来创建路径图形 /// /// 点的集合 ///路径图形 private static Path _CreatePath(Listpoints) { 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[0], Segments = segments }); return new Path { Data = pathGeometry }; }
(2)星星颜色的随机生成
星星颜色的产生是通过ARGB的数值来进行创建,这样更加方便用随机数进行处理。因为对星星填充的属性是需要用画刷对象来赋值的,所以需要用随机颜色来创建画刷,这里用线性渐变画刷LinearGradientBrush来填充Path图形。封装的方法_GetColor表示创建随机的颜色对象,_ColorFactory表示对Path图形填充随机的颜色画刷。代码如下所示:
StarFactory.cs文件部分代码------------------------------------------------------------------------------------------------------------------ ////// 添加颜色到路径图形 /// /// 路径图形 private static void _ColorFactory(Path input) { LinearGradientBrush brush = new LinearGradientBrush(); brush.StartPoint = new Point(0, 0); brush.EndPoint = new Point(1.0, 1.0); GradientStop start = new GradientStop(); start.Color = _GetColor(); start.Offset = 0; 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; } ////// 获取一个随机的颜色 /// ///private static Color _GetColor() { Color color = new Color(); color.A = (byte)(_random.Next(200) + 20); color.R = (byte)(_random.Next(200) + 50); color.G = (byte)(_random.Next(200) + 50); color.B = (byte)(_random.Next(200) + 50); return color; }
(3)创建星星对象
创建星星对象需要先有三个点,然后在利用这三个点根据创建星星图形的原理(3*4n,n表示星星递归的层次)来创建星星图形,然后用随机颜色画刷来填充图形。同时为了更加个性化,也对图形做了随机角度的旋转变换特效。代码如下所示:
StarFactory.cs文件部分代码------------------------------------------------------------------------------------------------------------------ const int MIN = 0; const int MAX = 2; // 随机数产生器 static readonly Random _random = new Random(); // 创建一个Star public static Path Create() { Point a = new Point(0, 0); Point b = new Point(_random.NextDouble() * 70.0 + 15.0, 0); Point c = new Point(0, b.X); int levels = _random.Next(MIN,MAX); Listpoints = new List (); 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文件代码------------------------------------------------------------------------------------------------------------------ ////// Star实体,封装Star的行为 /// public class StarEntity { // 左边距 const double LEFT = 480; // 上边距 const double TOP = 800; // 离开屏幕 const double GONE = 480; //随机近似数 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(Actioninsert) : this(insert, true) { } /// /// 星星对象构造方法 /// /// 是否从顶下落下 public StarEntity(Actioninsert, bool fromTop) { _starflake = StarFactory.Create(); //产生0到1的随机数 _affinity = _random.NextDouble(); // 设置速度,和初始化x y轴 velocity = _random.NextDouble() * 2; x = _random.NextDouble() * LEFT; y = fromTop ? 0 : _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 < 0) { x = 0; _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文件代码------------------------------------------------------------------------------------------------------------------ ////// StarBehavior类管理附加属性的行为触发批量星星的构造和动画的实现 /// public static class StarBehavior { // 屏幕上生成的星星数量 const int CAPACITY = 75; // 动画是否已经开始的标识符 private static bool beginning = false; // Star对象列表 private static List_starflakes = new List (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 > 0) { return; } // 开始动画 beginning = true; for (int x = 0; 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文件主要代码------------------------------------------------------------------------------------------------------------------
MainPage.xaml.cs文件主要代码------------------------------------------------------------------------------------------------------------------ // 按钮事件,播放动画和停止动画 private async void button_Click_1(object sender, RoutedEventArgs e) { if ((bool)myCanvas.GetValue(StarBehavior.AttachStarFlakeProperty) == false) { // 要等所有的星星都全部落下去之后才可以再次播放动画 if (myCanvas.Children.Count > 0) { await new MessageDialog("星星动画未完全结束").ShowAsync(); return; } myCanvas.SetValue(StarBehavior.AttachStarFlakeProperty, true); button.Content = "停止新增星星"; } else { myCanvas.SetValue(StarBehavior.AttachStarFlakeProperty, false); button.Content = "开始星星飘落"; } }
本文来源于
源代码下载:
目录:
欢迎关注我的微博 微信公众号:wp开发(号:wpkaifa)
Windows10/WP技术交流群:284783431