[深刻淺出Windows 10]模擬實現微信的彩蛋動畫

9.7 模擬實現微信的彩蛋動畫

    你們在玩微信的時候有沒有發現節日的時候發一些節日問候語句如「情人節快樂」,這時候會出現不少愛心形狀從屏幕上面飄落下來,咱們這小節就是要模擬實現這樣的一種動畫效果。可能微信裏面實現的動畫效果都是採用固定的小圖片來最爲動畫的對象,可是咱們這小節要對該動畫效果增長一些改進,也就是要實現的彩蛋動畫的針對的圖形形狀是動態隨機生成的,因此看到從屏幕上面飄落的圖形都是形狀不太同樣的。下面來看一下如何實現星星飄落動畫。html

9.7.1 實現的思路

    首先,咱們來分析一下星星飄落動畫的實現思路。canvas

    (1)動畫對象微信

    微信的彩蛋動畫能夠採用固定的圖片對象做爲動畫的對象,可是由於咱們要實現的星星動畫,這個星星的形狀和顏色是不同的,因此不能直接用圖片作爲星星的對象。要建立出不一樣的星星形狀須要用到Path圖形繪圖的方式來實現,定義好畫圖的規則,而後用隨機數來決定某些點的位置這樣就能夠繪製出各類不一樣形狀的星星。顏色的隨機定義就很好辦了,能夠先產生隨機的0-255的三原色色值,而後用三原色的方式來建立顏色對象。dom

    (2)動畫的實現方式async

    從微信的彩蛋動畫效果能夠看出來,這些動畫的隨機型很大,軌跡運動軌跡、速度、方向都是隨機,這種狀況是很難使用線性插值動畫或者關鍵幀動畫去實現的,因此這個動畫的實現方式很明顯是要採用基於幀動畫去實現。在基於幀動畫裏面,經過CompositionTarget.Rendering事件的處理程序來實現動畫的效果,星星飄落的動畫效果是星星對象從一個固定區域的最上面某個位置飄落到該區域的最底下,而後再銷燬星星對象。咱們能夠採用Canvas面板來定義動畫的區域,用Canvas.LeftProperty和Canvas.TopProperty屬性做爲定義星星對象位置的座標,而後經過改變這個座標來實現星星的下落。ide

    (3)動畫的封裝動畫

    在對動畫的實現方式和動畫對象的封裝的思路清楚以後,咱們開始考慮如何來封裝這樣的一種動畫效果。星星對象的實現須要經過一個星星工廠類(StarFactory類)來封裝星星建立的邏輯。由於每一個星星的運動軌跡都是不同的,都有其本身飄落的速度和方向,因此咱們須要封裝一個星星的對象,在星星對象裏面負責處理星星的速度,方向,運動軌跡等邏輯。最後動畫能夠經過附加屬性的方式在Canvas面板上實現動畫。ui

    下面咱們來詳細地看一下星星飄落動畫的編碼實現。this

    代碼清單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 == 0)
        {
            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 = 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;
        }
    }
    /// <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[0],
                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(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;
    }
    /// <summary>
    /// 獲取一個隨機的顏色
    /// </summary>
    /// <returns></returns>
    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);
        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 = 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(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() * 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文件代碼
------------------------------------------------------------------------------------------------------------------
    /// <summary>
    ///  StarBehavior類管理附加屬性的行爲觸發批量星星的構造和動畫的實現
    /// </summary>
    public static class StarBehavior
    {
        // 屏幕上生成的星星數量
        const int CAPACITY = 75;
        // 動畫是否已經開始的標識符
        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 > 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文件主要代碼
------------------------------------------------------------------------------------------------------------------
    <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 > 0)
            {
                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

相關文章
相關標籤/搜索