【WPF學習】第五十六章 基於幀的動畫

来源:https://www.cnblogs.com/Peter-Luo/archive/2020/03/06/12431549.html
-Advertisement-
Play Games

除基於屬性的動畫系統外,WPF提供了一種創建基於幀的動畫的方法,這種方法只使用代碼。需要做的全部工作是響應靜態的CompositionTarge.Rendering事件,觸發該事件是為了給每幀獲取內容。這是一種非常低級的方法,除非使用標準的基於屬性的動畫模型不能滿足需要(例如,構建簡單的側邊滾動游戲 ...


  除基於屬性的動畫系統外,WPF提供了一種創建基於幀的動畫的方法,這種方法只使用代碼。需要做的全部工作是響應靜態的CompositionTarge.Rendering事件,觸發該事件是為了給每幀獲取內容。這是一種非常低級的方法,除非使用標準的基於屬性的動畫模型不能滿足需要(例如,構建簡單的側邊滾動游戲、創建基於物理的動畫式構建粒子效果模型(如火焰、雪花以及氣泡)),否則不會希望使用這種方法。

  構建基於幀的動畫的基本技術很容易。只需要為靜態的CompositionTarget.Rendering事件關聯事件處理程式。一旦關聯事件處理程式,WPF就開始不斷地調用這個事件處理程式(只要渲染代碼的執行速度足夠快,WPF每秒將調用60次)。在渲染事件處理程式中,需要在視窗中相應地創建或調整元素。換句話說,需要自行管理全部工作。當動畫結束時,分離事件處理程式。

  下圖顯示了一個簡單示例。在此,隨機數量的圓從Canvas面板的頂部向底部下落。它們(根據隨機生成的開始速度)以不同速度下降,但一相同的速率加速。當所有的圓到達底部時,動畫結束。

  在這個示例中,每個下落的圓由Ellipse元素表示。使用自定義的EllipseInfo類保存橢圓的引用,並跟蹤對於物理模型而言十分重要的一些細節。在這個示例中,只有如下信息很重要——橢圓沿X軸的移動速度(可很容易地擴張這個類,使其包含沿著Y軸運動的速度、額外的加速信息等)。

public class EllipseInfo
    {
        public Ellipse Ellipse
        {
            get;
            set;
        }

        public double VelocityY
        {
            get;
            set;
        }

        public EllipseInfo(Ellipse ellipse, double velocityY)
        {
            VelocityY = velocityY;
            Ellipse = ellipse;
        }
    }

  應用程式使用集合跟蹤每個橢圓的EllipseInfo對象。還有幾個視窗級別的欄位,它們記錄計算橢圓下落時使用的各種細節。可很容易地使這些細節變成可配置的。

private List<EllipseInfo> ellipses = new List<EllipseInfo>();

private double accelerationY = 0.1;
private int minStartingSpeed = 1;
private int maxStartingSpeed = 50;
private double speedRatio = 0.1;
private int minEllipses = 20;
private int maxEllipses = 100;
private int ellipseRadius = 10;

  當單擊其中某個按鈕時,清空集合,並將事件處理程式關聯到CompositionTarget.Rendering事件:

        private bool rendering = false;
        private void cmdStart_Clicked(object sender, RoutedEventArgs e)
        {
            if (!rendering)
            {
                ellipses.Clear();
                canvas.Children.Clear();

                CompositionTarget.Rendering += RenderFrame;
                rendering = true;
            }
        }
        private void cmdStop_Clicked(object sender, RoutedEventArgs e)
        {
            StopRendering();
        }

        private void StopRendering()
        {
            CompositionTarget.Rendering -= RenderFrame;
            rendering = false;
        }    

  如果橢圓不存在,渲染代碼會自動創建它們。渲染代碼創建隨機數量的橢圓(當前為20到100個),並使他們具有相同的尺寸和顏色。橢圓被放在Canvas面板的頂部,但他們沿著X軸隨機移動:

 private void RenderFrame(object sender, EventArgs e)
        {
            if (ellipses.Count == 0)
            {
                // Animation just started. Create the ellipses.
                int halfCanvasWidth = (int)canvas.ActualWidth / 2;

                Random rand = new Random();
                int ellipseCount = rand.Next(minEllipses, maxEllipses + 1);
                for (int i = 0; i < ellipseCount; i++)
                {
                    Ellipse ellipse = new Ellipse();
                    ellipse.Fill = Brushes.LimeGreen;
                    ellipse.Width = ellipseRadius;
                    ellipse.Height = ellipseRadius;
                    Canvas.SetLeft(ellipse, halfCanvasWidth + rand.Next(-halfCanvasWidth, halfCanvasWidth));
                    Canvas.SetTop(ellipse, 0);
                    canvas.Children.Add(ellipse);

                    EllipseInfo info = new EllipseInfo(ellipse, speedRatio * rand.Next(minStartingSpeed, maxStartingSpeed));
                    ellipses.Add(info);
                }
            }
        }    

  如果橢圓已經存在,代碼處理更有趣的工作,以便進行動態顯示。使用Canvas.SetTop()方法緩慢移動每個橢圓。移動距離取決於指定的速度。

            else
            {
                for (int i = ellipses.Count - 1; i >= 0; i--)
                {
                    EllipseInfo info = ellipses[i];
                    double top = Canvas.GetTop(info.Ellipse);
                    Canvas.SetTop(info.Ellipse, top + 1 * info.VelocityY);
            }

  為提高性能,一旦橢圓到達Canvas面板的底部,就從跟蹤集合中刪除橢圓。這樣,就不需要再處理它們。當遍歷集合時,為了能夠工作而不會導致丟失位置,需要向後迭代,從集合的末尾向起始位置迭代。

  如果橢圓尚未到達Canvas面板的底部,代碼會提高速度(此外,為獲得磁鐵吸引效果,還可以根據橢圓與Canvas面板底部的距離來設置速度):

                    if (top >= (canvas.ActualHeight - ellipseRadius * 2 - 10))
                    {
                        // This circle has reached the bottom.
                        // Stop animating it.
                        ellipses.Remove(info);
                    }
                    else
                    {
                        // Increase the velocity.
                        info.VelocityY += accelerationY;
                    }    

  最後,如果所有橢圓都已從集合中刪除,就移除事件處理程式,然後結束動畫:

                    if (ellipses.Count == 0)
                    {
                        // End the animation.
                        // There's no reason to keep calling this method
                        // if it has no work to do.
                        StopRendering();
                    }        

  示例完整XAML標記如下所示:

<Window x:Class="Animation.FrameBasedAnimation"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="FrameBasedAnimation" Height="396" Width="463.2">
    <Grid Margin="3">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal">
            <Button Margin="3" Padding="3" Click="cmdStart_Clicked">Start</Button>
            <Button Margin="3" Padding="3" Click="cmdStop_Clicked">Stop</Button>
        </StackPanel>
        <Canvas Name="canvas" Grid.Row="1" Margin="3"></Canvas>
    </Grid>
</Window>
FrameBasedAnimation.xaml
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Animation
{
    /// <summary>
    /// FrameBasedAnimation.xaml 的交互邏輯
    /// </summary>
    public partial class FrameBasedAnimation : Window
    {
        public FrameBasedAnimation()
        {
            InitializeComponent();
        }

        private bool rendering = false;
        private void cmdStart_Clicked(object sender, RoutedEventArgs e)
        {
            if (!rendering)
            {
                ellipses.Clear();
                canvas.Children.Clear();

                CompositionTarget.Rendering += RenderFrame;
                rendering = true;
            }
        }
        private void cmdStop_Clicked(object sender, RoutedEventArgs e)
        {
            StopRendering();
        }

        private void StopRendering()
        {
            CompositionTarget.Rendering -= RenderFrame;
            rendering = false;
        }

        private List<EllipseInfo> ellipses = new List<EllipseInfo>();

        private double accelerationY = 0.1;
        private int minStartingSpeed = 1;
        private int maxStartingSpeed = 50;
        private double speedRatio = 0.1;
        private int minEllipses = 20;
        private int maxEllipses = 100;
        private int ellipseRadius = 10;

        private void RenderFrame(object sender, EventArgs e)
        {
            if (ellipses.Count == 0)
            {
                // Animation just started. Create the ellipses.
                int halfCanvasWidth = (int)canvas.ActualWidth / 2;

                Random rand = new Random();
                int ellipseCount = rand.Next(minEllipses, maxEllipses + 1);
                for (int i = 0; i < ellipseCount; i++)
                {
                    Ellipse ellipse = new Ellipse();
                    ellipse.Fill = Brushes.LimeGreen;
                    ellipse.Width = ellipseRadius;
                    ellipse.Height = ellipseRadius;
                    Canvas.SetLeft(ellipse, halfCanvasWidth + rand.Next(-halfCanvasWidth, halfCanvasWidth));
                    Canvas.SetTop(ellipse, 0);
                    canvas.Children.Add(ellipse);

                    EllipseInfo info = new EllipseInfo(ellipse, speedRatio * rand.Next(minStartingSpeed, maxStartingSpeed));
                    ellipses.Add(info);
                }
            }
            else
            {
                for (int i = ellipses.Count - 1; i >= 0; i--)
                {
                    EllipseInfo info = ellipses[i];
                    double top = Canvas.GetTop(info.Ellipse);
                    Canvas.SetTop(info.Ellipse, top + 1 * info.VelocityY);

                    if (top >= (canvas.ActualHeight - ellipseRadius * 2 - 10))
                    {
                        // This circle has reached the bottom.
                        // Stop animating it.
                        ellipses.Remove(info);
                    }
                    else
                    {
                        // Increase the velocity.
                        info.VelocityY += accelerationY;
                    }

                    if (ellipses.Count == 0)
                    {
                        // End the animation.
                        // There's no reason to keep calling this method
                        // if it has no work to do.
                        StopRendering();
                    }
                }
            }
        }
    }
    public class EllipseInfo
    {
        public Ellipse Ellipse
        {
            get;
            set;
        }

        public double VelocityY
        {
            get;
            set;
        }

        public EllipseInfo(Ellipse ellipse, double velocityY)
        {
            VelocityY = velocityY;
            Ellipse = ellipse;
        }
    }
}
FrameBasedAnimation.xaml.cs

  顯然,可擴展的這個動畫以使圓跳躍和分散等。使用的技術是相同的——只需要使用更複雜的公式計算速度。

  當構建基於幀的動畫時需要註意如下問題:它們不依賴與時間。換句話說,動畫可能在性能好的電腦上運動更快,因為幀率會增加,會更頻繁地調用CompositionTarget.Rendering事件。為補償這種效果,需要編寫考慮當前時間的代碼。

  開始學習基於幀的動畫的最好方式是查看WPF SDK提供的每一幀動畫都非常詳細的示例。該例演示了幾種粒子系統效果,並且使用自定義的TimeTracker類實現了依賴與時間的基於幀的動畫。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分散式等教程,一共30G,需要自己領取。傳送門:https://mp.weixin.qq.com/s/osB-BOl6W-ZLTSttTkqMPQ 前 ...
  • 隨著微信的普及,掃碼登錄方式越來越被現在的應用所使用。它因為不用去記住密碼,只要有微信號即可方便快捷登錄。微信的開放平臺原生就有支持掃碼登錄的功能,不過大部分人還是在用公眾平臺,所以掃碼登錄只能自行實現。這裡基於微信公眾平臺的帶參數臨時二維碼,並且結合 Swoole 的 WebSocket 服務實現 ...
  • 最近國內疫情已經有所好轉,但是國外的情況不容樂觀,那麼怎樣用python去製作動態圖表來看全球疫情變化趨勢呢?比如下麵的國內外疫情發展趨勢 ​ 下麵是全球疫情發展趨勢 其實用python實現並不難,簡單來說就分為三步: 獲取數據(requests) 數據清洗(pandas) 數據可視化(pyecha ...
  • 一、函數的幾種形式 1. 函數根據有沒有參數和 返回值,可以組合成下麵的四組 無參,無值 無參,有值 有參,有值 有參,無值 2.那麼我們到底給那種呢? 我給出瞭如下的總結: 2.1 無參數,無返回值 此類函數,不接收參數,也沒有返回值,應用場景如下: 1. 只是單純地做一件事情 ,例如 顯示菜單 ...
  • 1,被 synchronized 修飾的方法,鎖的對象是方法的調用者(實例對象) 2,被 static 修飾的方法,鎖的對象就是 Class模板對象,這個則全局唯一 問題7: 一個普通同步方法,一個靜態同步方法,只有一個手機,請問先執行sendEmail 還是 sendSMS public clas ...
  • Java自身UI界面太醜怎麼辦?通過以下代碼就可以將Java應用程式GUI設置成Windows風格: 設置前: 設置後: ...
  • 本文章為本人原創,適合於剛入坑C語言,對於指針的定義和用法模糊不清的同學,如有不正,請各位指出。 從根本來說,指針變數也是變數,只是int變成了int *,以此類推。只不過指針變數裡面放的內容是普通變數在存儲空間的地址(某種奇怪的16進位地址格式,感興趣可自行百度) 定義指針變數的格式:int/do ...
  • 記錄 編碼約定 學習過程。 命名空間約定 如果沒有使用using指令,項目也沒有預設導入合適的命名空間,訪問這些命名空間或者類型時,則需要“完全限定名稱”。 namespace ConsoleApp4 { class Program { static void Main(string[] args) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...