UWP Background過渡動畫

来源:https://www.cnblogs.com/blue-fire/archive/2018/09/10/9617275.html
-Advertisement-
Play Games

首先說兩件事: 1、大爆炸我還記著呢,先欠著吧。。。 2、博客搬家啦,新地址:https://blog.ultrabluefire.cn/ 下麵是正文 前些日子看到Xaml Controls Gallery的ToggleTheme過渡非常心水,大概是這樣的: 在17134 SDK里寫法如下: 這和我 ...


首先說兩件事:

1、大爆炸我還記著呢,先欠著吧。。。

2、博客搬家啦,新地址:https://blog.ultrabluefire.cn/

==========下麵是正文==========

前些日子看到Xaml Controls Gallery的ToggleTheme過渡非常心水,大概是這樣的:

在17134 SDK里寫法如下:

1 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
2     <Grid.BackgroundTransition>
3         <BrushTransition Duration="0:0:0.4" />
4     </Grid.BackgroundTransition>
5 </Grid>

這和我原本的思路完全不同。
我原本的思路是定義一個靜態的筆刷資源,然後動畫修改他的Color,但是這樣就不能和系統的筆刷資源很好的融合了。怎麼辦呢?
前天半夢半醒間,突然靈光一現,感覺可以用一個附加屬性作為中間層,給Background賦臨時的筆刷實現過渡。
閑話不多說,開乾。
首先我們需要一個畫刷,這個畫刷要實現以下功能:

  • 擁有一個Color屬性。
  • 對Color屬性賦值時會播放動畫。
  • 動畫播放結束觸發事件。
  • 可以從外部清理事件。

這個可以使用Storyboard,CompositionAnimation手動Start或者ImplicitAnimation實現,在這裡我選擇了我最順手的Composition實現。
下麵貼代碼:

  1 public class FluentSolidColorBrush : XamlCompositionBrushBase
  2 {
  3     Compositor Compositor => Window.Current.Compositor;
  4     ColorKeyFrameAnimation ColorAnimation;
  5     bool IsConnected;
  6 
  7     //被設置到控制項屬性時觸發,例RootGrid.Background=new FluentSolidColorBrush();
  8     protected override void OnConnected()
  9     {
 10         if (CompositionBrush == null)
 11         {
 12             IsConnected = true;
 13 
 14             ColorAnimation = Compositor.CreateColorKeyFrameAnimation();
 15 
 16             //進度為0的關鍵幀,表達式為起始顏色。
 17             ColorAnimation.InsertExpressionKeyFrame(0f, "this.StartingValue");
 18 
 19             //進度為0的關鍵幀,表達式為參數名為Color的參數。
 20             ColorAnimation.InsertExpressionKeyFrame(1f, "Color");
 21 
 22             //創建顏色筆刷
 23             CompositionBrush = Compositor.CreateColorBrush(Color);
 24         }
 25     }
 26 
 27     //從屬性中移除時觸發,例RootGrid.Background=null;
 28     protected override void OnDisconnected()
 29     {
 30         if (CompositionBrush != null)
 31         {
 32             IsConnected = false;
 33 
 34             ColorAnimation.Dispose();
 35             ColorAnimation = null;
 36             CompositionBrush.Dispose();
 37             CompositionBrush = null;
 38 
 39             //清除已註冊的事件。
 40             ColorChanged = null;
 41         }
 42     }
 43 
 44     public TimeSpan Duration
 45     {
 46         get { return (TimeSpan)GetValue(DurationProperty); }
 47         set { SetValue(DurationProperty, value); }
 48     }
 49 
 50     public static readonly DependencyProperty DurationProperty =
 51         DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FluentSolidColorBrush), new PropertyMetadata(TimeSpan.FromSeconds(0.4d), (s, a) =>
 52         {
 53             if (a.NewValue != a.OldValue)
 54             {
 55                 if (s is FluentSolidColorBrush sender)
 56                 {
 57                     if (sender.ColorAnimation != null)
 58                     {
 59                         sender.ColorAnimation.Duration = (TimeSpan)a.NewValue;
 60                     }
 61                 }
 62             }
 63         }));
 64 
 65     public Color Color
 66     {
 67         get { return (Color)GetValue(ColorProperty); }
 68         set { SetValue(ColorProperty, value); }
 69     }
 70 
 71     public static readonly DependencyProperty ColorProperty =
 72         DependencyProperty.Register("Color", typeof(Color), typeof(FluentSolidColorBrush), new PropertyMetadata(default(Color), (s, a) =>
 73         {
 74             if (a.NewValue != a.OldValue)
 75             {
 76                 if (s is FluentSolidColorBrush sender)
 77                 {
 78                     if (sender.IsConnected)
 79                     {
 80                         //給ColorAnimation,進度為1的幀的參數Color賦值
 81                         sender.ColorAnimation.SetColorParameter("Color", (Color)a.NewValue);
 82 
 83                         //創建一個動畫批,CompositionAnimation使用批控制動畫完成。
 84                         var batch = sender.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
 85 
 86                         //批內所有動畫完成事件,完成時如果畫刷沒有Disconnected,則觸發ColorChanged
 87                         batch.Completed += (s1, a1) =>
 88                         {
 89                             if (sender.IsConnected)
 90                             {
 91                                 sender.OnColorChanged((Color)a.OldValue, (Color)a.NewValue);
 92                             }
 93                         };
 94                         sender.CompositionBrush.StartAnimation("Color", sender.ColorAnimation);
 95                         batch.End();
 96                     }
 97                 }
 98             }
 99         }));
100 
101     public event ColorChangedEventHandler ColorChanged;
102     private void OnColorChanged(Color oldColor, Color newColor)
103     {
104         ColorChanged?.Invoke(this, new ColorChangedEventArgs()
105         {
106             OldColor = oldColor,
107             NewColor = newColor
108         });
109     }
110 }
111 
112 public delegate void ColorChangedEventHandler(object sender, ColorChangedEventArgs args);
113 public class ColorChangedEventArgs : EventArgs
114 {
115     public Color OldColor { get; internal set; }
116     public Color NewColor { get; internal set; }
117 }
View Code

這樣這個筆刷在每次修改Color的時候就能自動觸發動畫了,這完成了我思路的第一步,接下來我們需要一個Background屬性設置時的中間層,用來給兩個顏色之間添加過渡,這個使用附加屬性和Behavior都可以實現。

我開始選擇了Behavior,優點是可以在VisualState的Storyboard節點中賦值,而且由於每個Behavior都是獨立的屬性,可以存儲更多的非公共屬性、狀態等;但是缺點也非常明顯,使用Behavior要引入"Microsoft.Xaml.Behaviors.Uwp.Managed"這個包,使用的時候也要使用至少三行代碼。
而附加屬性呢,優點是原生和短,缺點是不能存儲過多狀態,也不能在Storyboard里使用,只能用Setter控制。
不過對於我們的需求呢,只需要Background和Duration兩個屬性,綜上所述,最終我選擇了附加屬性實現。
閑話不多說,繼續貼代碼:

 1 public class TransitionsHelper : DependencyObject
 2 {
 3     public static Brush GetBackground(FrameworkElement obj)
 4     {
 5         return (Brush)obj.GetValue(BackgroundProperty);
 6     }
 7 
 8     public static void SetBackground(FrameworkElement obj, Brush value)
 9     {
10         obj.SetValue(BackgroundProperty, value);
11     }
12 
13     public static TimeSpan GetDuration(FrameworkElement obj)
14     {
15         return (TimeSpan)obj.GetValue(DurationProperty);
16     }
17 
18     public static void SetDuration(FrameworkElement obj, TimeSpan value)
19     {
20         obj.SetValue(DurationProperty, value);
21     }
22 
23     public static readonly DependencyProperty BackgroundProperty =
24         DependencyProperty.RegisterAttached("Background", typeof(Brush), typeof(TransitionsHelper), new PropertyMetadata(null, BackgroundPropertyChanged));
25 
26     public static readonly DependencyProperty DurationProperty =
27         DependencyProperty.RegisterAttached("Duration", typeof(TimeSpan), typeof(TransitionsHelper), new PropertyMetadata(TimeSpan.FromSeconds(0.6d)));
28 
29     private static void BackgroundPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
30     {
31         if (e.NewValue != e.OldValue)
32         {
33             if (d is FrameworkElement sender)
34             {
35                 //拿到New和Old的Brush,因為Brush可能不是SolidColorBrush,這裡不能使用強制類型轉換。
36                 var NewBrush = e.NewValue as SolidColorBrush;
37                 var OldBrush = e.OldValue as SolidColorBrush;
38 
39                 //下麵分別獲取不同控制項的Background依賴屬性。
40                 DependencyProperty BackgroundProperty = null;
41                 if (sender is Panel)
42                 {
43                     BackgroundProperty = Panel.BackgroundProperty;
44                 }
45                 else if (sender is Control)
46                 {
47                     BackgroundProperty = Control.BackgroundProperty;
48                 }
49                 else if (sender is Shape)
50                 {
51                     BackgroundProperty = Shape.FillProperty;
52                 }
53 
54                 if (BackgroundProperty == null) return;
55 
56                 //如果當前筆刷是FluentSolidColorBrush,就將當前筆刷設置成舊筆刷,觸發FluentSolidColorBrush的OnDisconnected,
57                 //OnDisconnected中會清理掉ColorChanged上註冊的事件,防止筆刷在卸載之後,動畫完成時觸發事件,導致運行不正常。
58                 if (sender.GetValue(BackgroundProperty) is FluentSolidColorBrush tmp_fluent)
59                 {
60                     sender.SetValue(BackgroundProperty, OldBrush);
61                 }
62 
63                 //如果OldBrush或者NewBrush中有一個為空,就不播放動畫,直接賦值
64                 if (OldBrush == null || NewBrush == null)
65                 {
66                     sender.SetValue(BackgroundProperty, NewBrush);
67                     return;
68                 }
69 
70                 var FluentBrush = new FluentSolidColorBrush()
71                 {
72                     Duration = GetDuration(sender),
73                     Color = OldBrush.Color,
74                 };
75                 FluentBrush.ColorChanged += (s, a) =>
76                 {
77                     sender.SetValue(BackgroundProperty, NewBrush);
78                 };
79                 sender.SetValue(BackgroundProperty, FluentBrush);
80                 FluentBrush.Color = NewBrush.Color;
81             }
82         }
83     }
84 }
View Code

調用的時候就不能直接設置Background了:

1 <Grid helper:TransitionsHelper.Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
2     <Button x:Name="ToggleTheme" Click="ToggleTheme_Click">ToggleTheme</Button>
3 </Grid>

在Style里調用方法也類似:

 

 1 <!-- Element中 -->
 2 <Grid x:Name="RootGrid" helper:TransitionsHelper.Background="{TemplateBinding Background}">
 3     ...
 4 </Grid>
 5 
 6 <!-- VisualState中 -->
 7 <VisualState x:Name="TestState">
 8     <VisualState.Setter>
 9         <Setter Target="RootGrid.(helper:TransitionsHelper.Background)" Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=SecondBackground}" />
10     </VisualState.Setter>
11 </VisualState>

這裡還有個點要註意,在VisualState中,不管是Storyboard還是Setter,如果要修改模板綁定,直接寫Value="{TemplateBinding XXX}"會報錯,正確的寫法是Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=SecondBackground}"。


最後附一張效果圖:

原文地址:https://blog.ultrabluefire.cn/archives/13.html

 


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

-Advertisement-
Play Games
更多相關文章
  • 本篇文章將與大家分享bootstrap-table插件,藉助於它實現基本的增刪改查,導入導出,分頁,父子表等。 至於其他技術,如凍結表頭,列排列,行拖動,列拖動等,會在後續文章中與大家分享。 一 效果圖 (一)頁面初始化 下圖是頁面首次載入結束後的效果,主要完成以下功能: 1.日期部分,開始時間:當 ...
  • ASCII值控制字元ASCII值控制字元ASCII值控制字元ASCII值控制字元 0 NUT 32 (space) 64 @ 96 、 1 SOH 33 ! 65 A 97 a 2 STX 34 " 66 B 98 b 3 ETX 35 # 67 C 99 c 4 EOT 36 $ 68 D 100 ...
  • 調用代理,就是負責將客戶端對服務的調用進行包裝,然後傳遞到伺服器端。當客戶端調用方法時,只是像正常的方法調用一樣,傳入方法參數,取得返回結果。客戶端不需要知道這個調用被傳遞到何處,業務邏輯在哪裡執行。例如: ServiceFactory 使用了 System.Runtime.Remoting.Pro ...
  • 1、Global.asax中添加對Session的支持,重新Init方法: 2、WebConfig中添加跨域支持: 3、寫Session的Controller 4、讀Session的Controller 5、前端jQuery調用時加上參數crossDomain: true 和 xhrFields: ...
  • 1、功能及功能許可權 一般地,用戶擁有一項功能,則應體現在主視窗的功能菜單樹上,功能還應包括業務 View 內部的功能按鈕。框架會根據許可權設定情況使各功能按鈕可用。為了構建導航樹,某些功能還有可能是用來定義導航關係的,而不包含具體的功能含義,舉例說明: 1)用來定義導航關係的功能:如圖所示,【功能】、 ...
  • 初識ASP.NET Core的小伙伴一定會發現,其幾乎所有的項目依賴都是通過依賴註入方式進行鏈式串通的。這是因為其使用了依賴註入 (DI) 的軟體設計模式,代碼的設計是遵循著“高內聚、低耦合”的原則,使得各個類與類之間的關係依賴於介面,這樣做的目的是能更有利於項目代碼的維護與擴展。 ...
  • # [翻譯] 如何在 ASP.Net Core 中使用 Consul 來存儲配置 原文: [USING CONSUL FOR STORING THE CONFIGURATION IN ASP.NET CORE](https://www.natmarchand.fr/consul-configura... ...
  • 1.瀏覽aws 開發人員指南 https://docs.aws.amazon.com/zh_cn/sns/latest/dg/sms_publish-to-phone.html 2.安裝 aws sms net api : AWSSDK.SimpleNotificationService 3.調用  ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...