• UWP Background过渡动画


    首先说两件事:

    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

  • 相关阅读:
    iview表格render多元素封装
    iview表单语法
    vue实例,extend methods——实例属性 vue和jQuery.js一起使用
    vue.js基础__ extend 扩展选项
    vue.js基础__ mixins 选项
    vue.js基础__ watch 选项
    vue.js基础__ methods 选项
    vue.js基础__ computed 选项
    vue.js基础__ propsData 选项
    vue-cli, webpack + vue 目录结构解读
  • 原文地址:https://www.cnblogs.com/blue-fire/p/9617275.html
Copyright © 2020-2023  润新知