经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
UWP Background过渡动画
来源:cnblogs  作者:叫我蓝火火  时间:2018/9/25 20:39:46  对本文有异议

首先说两件事:

1、大爆炸我还记着呢,先欠着吧。。。

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

==========下面是正文==========

前些日子看到Xaml Controls Gallery的ToggleTheme过渡非常心水,大概是这样的:

在17134 SDK里写法如下:

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

这和我原本的思路完全不同。
我原本的思路是定义一个静态的笔刷资源,然后动画修改他的Color,但是这样就不能和系统的笔刷资源很好的融合了。怎么办呢?
前天半梦半醒间,突然灵光一现,感觉可以用一个附加属性作为中间层,给Background赋临时的笔刷实现过渡。
闲话不多说,开干。
首先我们需要一个画刷,这个画刷要实现以下功能:

  • 拥有一个Color属性。
  • 对Color属性赋值时会播放动画。
  • 动画播放结束触发事件。
  • 可以从外部清理事件。

这个可以使用Storyboard,CompositionAnimation手动Start或者ImplicitAnimation实现,在这里我选择了我最顺手的Composition实现。
下面贴代码:

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

这样这个笔刷在每次修改Color的时候就能自动触发动画了,这完成了我思路的第一步,接下来我们需要一个Background属性设置时的中间层,用来给两个颜色之间添加过渡,这个使用附加属性和Behavior都可以实现。

我开始选择了Behavior,优点是可以在VisualState的Storyboard节点中赋值,而且由于每个Behavior都是独立的属性,可以存储更多的非公共属性、状态等;但是缺点也非常明显,使用Behavior要引入"Microsoft.Xaml.Behaviors.Uwp.Managed"这个包,使用的时候也要使用至少三行代码。
而附加属性呢,优点是原生和短,缺点是不能存储过多状态,也不能在Storyboard里使用,只能用Setter控制。
不过对于我们的需求呢,只需要Background和Duration两个属性,综上所述,最终我选择了附加属性实现。
闲话不多说,继续贴代码:

  1. 1 public class TransitionsHelper : DependencyObject
  2. 2 {
  3. 3 public static Brush GetBackground(FrameworkElement obj)
  4. 4 {
  5. 5 return (Brush)obj.GetValue(BackgroundProperty);
  6. 6 }
  7. 7
  8. 8 public static void SetBackground(FrameworkElement obj, Brush value)
  9. 9 {
  10. 10 obj.SetValue(BackgroundProperty, value);
  11. 11 }
  12. 12
  13. 13 public static TimeSpan GetDuration(FrameworkElement obj)
  14. 14 {
  15. 15 return (TimeSpan)obj.GetValue(DurationProperty);
  16. 16 }
  17. 17
  18. 18 public static void SetDuration(FrameworkElement obj, TimeSpan value)
  19. 19 {
  20. 20 obj.SetValue(DurationProperty, value);
  21. 21 }
  22. 22
  23. 23 public static readonly DependencyProperty BackgroundProperty =
  24. 24 DependencyProperty.RegisterAttached("Background", typeof(Brush), typeof(TransitionsHelper), new PropertyMetadata(null, BackgroundPropertyChanged));
  25. 25
  26. 26 public static readonly DependencyProperty DurationProperty =
  27. 27 DependencyProperty.RegisterAttached("Duration", typeof(TimeSpan), typeof(TransitionsHelper), new PropertyMetadata(TimeSpan.FromSeconds(0.6d)));
  28. 28
  29. 29 private static void BackgroundPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  30. 30 {
  31. 31 if (e.NewValue != e.OldValue)
  32. 32 {
  33. 33 if (d is FrameworkElement sender)
  34. 34 {
  35. 35 //拿到New和Old的Brush,因为Brush可能不是SolidColorBrush,这里不能使用强制类型转换。
  36. 36 var NewBrush = e.NewValue as SolidColorBrush;
  37. 37 var OldBrush = e.OldValue as SolidColorBrush;
  38. 38
  39. 39 //下面分别获取不同控件的Background依赖属性。
  40. 40 DependencyProperty BackgroundProperty = null;
  41. 41 if (sender is Panel)
  42. 42 {
  43. 43 BackgroundProperty = Panel.BackgroundProperty;
  44. 44 }
  45. 45 else if (sender is Control)
  46. 46 {
  47. 47 BackgroundProperty = Control.BackgroundProperty;
  48. 48 }
  49. 49 else if (sender is Shape)
  50. 50 {
  51. 51 BackgroundProperty = Shape.FillProperty;
  52. 52 }
  53. 53
  54. 54 if (BackgroundProperty == null) return;
  55. 55
  56. 56 //如果当前笔刷是FluentSolidColorBrush,就将当前笔刷设置成旧笔刷,触发FluentSolidColorBrush的OnDisconnected,
  57. 57 //OnDisconnected中会清理掉ColorChanged上注册的事件,防止笔刷在卸载之后,动画完成时触发事件,导致运行不正常。
  58. 58 if (sender.GetValue(BackgroundProperty) is FluentSolidColorBrush tmp_fluent)
  59. 59 {
  60. 60 sender.SetValue(BackgroundProperty, OldBrush);
  61. 61 }
  62. 62
  63. 63 //如果OldBrush或者NewBrush中有一个为空,就不播放动画,直接赋值
  64. 64 if (OldBrush == null || NewBrush == null)
  65. 65 {
  66. 66 sender.SetValue(BackgroundProperty, NewBrush);
  67. 67 return;
  68. 68 }
  69. 69
  70. 70 var FluentBrush = new FluentSolidColorBrush()
  71. 71 {
  72. 72 Duration = GetDuration(sender),
  73. 73 Color = OldBrush.Color,
  74. 74 };
  75. 75 FluentBrush.ColorChanged += (s, a) =>
  76. 76 {
  77. 77 sender.SetValue(BackgroundProperty, NewBrush);
  78. 78 };
  79. 79 sender.SetValue(BackgroundProperty, FluentBrush);
  80. 80 FluentBrush.Color = NewBrush.Color;
  81. 81 }
  82. 82 }
  83. 83 }
  84. 84 }
View Code

调用的时候就不能直接设置Background了:

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

在Style里调用方法也类似:

 

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

这里还有个点要注意,在VisualState中,不管是Storyboard还是Setter,如果要修改模板绑定,直接写Value="{TemplateBinding XXX}"会报错,正确的写法是Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=SecondBackground}"。


最后附一张效果图:

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

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号