首先说两件事:

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

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

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

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

在17134 SDK里写法如下:

 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.BackgroundTransition>
<BrushTransition Duration="0:0:0.4" />
</Grid.BackgroundTransition>
</Grid>

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

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

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

 public class FluentSolidColorBrush : XamlCompositionBrushBase
{
Compositor Compositor => Window.Current.Compositor;
ColorKeyFrameAnimation ColorAnimation;
bool IsConnected; //被设置到控件属性时触发,例RootGrid.Background=new FluentSolidColorBrush();
protected override void OnConnected()
{
if (CompositionBrush == null)
{
IsConnected = true; ColorAnimation = Compositor.CreateColorKeyFrameAnimation(); //进度为0的关键帧,表达式为起始颜色。
ColorAnimation.InsertExpressionKeyFrame(0f, "this.StartingValue"); //进度为0的关键帧,表达式为参数名为Color的参数。
ColorAnimation.InsertExpressionKeyFrame(1f, "Color"); //创建颜色笔刷
CompositionBrush = Compositor.CreateColorBrush(Color);
}
} //从属性中移除时触发,例RootGrid.Background=null;
protected override void OnDisconnected()
{
if (CompositionBrush != null)
{
IsConnected = false; ColorAnimation.Dispose();
ColorAnimation = null;
CompositionBrush.Dispose();
CompositionBrush = null; //清除已注册的事件。
ColorChanged = null;
}
} public TimeSpan Duration
{
get { return (TimeSpan)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
} public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FluentSolidColorBrush), new PropertyMetadata(TimeSpan.FromSeconds(0.4d), (s, a) =>
{
if (a.NewValue != a.OldValue)
{
if (s is FluentSolidColorBrush sender)
{
if (sender.ColorAnimation != null)
{
sender.ColorAnimation.Duration = (TimeSpan)a.NewValue;
}
}
}
})); public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
} public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register("Color", typeof(Color), typeof(FluentSolidColorBrush), new PropertyMetadata(default(Color), (s, a) =>
{
if (a.NewValue != a.OldValue)
{
if (s is FluentSolidColorBrush sender)
{
if (sender.IsConnected)
{
//给ColorAnimation,进度为1的帧的参数Color赋值
sender.ColorAnimation.SetColorParameter("Color", (Color)a.NewValue); //创建一个动画批,CompositionAnimation使用批控制动画完成。
var batch = sender.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); //批内所有动画完成事件,完成时如果画刷没有Disconnected,则触发ColorChanged
batch.Completed += (s1, a1) =>
{
if (sender.IsConnected)
{
sender.OnColorChanged((Color)a.OldValue, (Color)a.NewValue);
}
};
sender.CompositionBrush.StartAnimation("Color", sender.ColorAnimation);
batch.End();
}
}
}
})); public event ColorChangedEventHandler ColorChanged;
private void OnColorChanged(Color oldColor, Color newColor)
{
ColorChanged?.Invoke(this, new ColorChangedEventArgs()
{
OldColor = oldColor,
NewColor = newColor
});
}
} public delegate void ColorChangedEventHandler(object sender, ColorChangedEventArgs args);
public class ColorChangedEventArgs : EventArgs
{
public Color OldColor { get; internal set; }
public Color NewColor { get; internal set; }
}

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

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

 public class TransitionsHelper : DependencyObject
{
public static Brush GetBackground(FrameworkElement obj)
{
return (Brush)obj.GetValue(BackgroundProperty);
} public static void SetBackground(FrameworkElement obj, Brush value)
{
obj.SetValue(BackgroundProperty, value);
} public static TimeSpan GetDuration(FrameworkElement obj)
{
return (TimeSpan)obj.GetValue(DurationProperty);
} public static void SetDuration(FrameworkElement obj, TimeSpan value)
{
obj.SetValue(DurationProperty, value);
} public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.RegisterAttached("Background", typeof(Brush), typeof(TransitionsHelper), new PropertyMetadata(null, BackgroundPropertyChanged)); public static readonly DependencyProperty DurationProperty =
DependencyProperty.RegisterAttached("Duration", typeof(TimeSpan), typeof(TransitionsHelper), new PropertyMetadata(TimeSpan.FromSeconds(0.6d))); private static void BackgroundPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
if (d is FrameworkElement sender)
{
//拿到New和Old的Brush,因为Brush可能不是SolidColorBrush,这里不能使用强制类型转换。
var NewBrush = e.NewValue as SolidColorBrush;
var OldBrush = e.OldValue as SolidColorBrush; //下面分别获取不同控件的Background依赖属性。
DependencyProperty BackgroundProperty = null;
if (sender is Panel)
{
BackgroundProperty = Panel.BackgroundProperty;
}
else if (sender is Control)
{
BackgroundProperty = Control.BackgroundProperty;
}
else if (sender is Shape)
{
BackgroundProperty = Shape.FillProperty;
} if (BackgroundProperty == null) return; //如果当前笔刷是FluentSolidColorBrush,就将当前笔刷设置成旧笔刷,触发FluentSolidColorBrush的OnDisconnected,
//OnDisconnected中会清理掉ColorChanged上注册的事件,防止笔刷在卸载之后,动画完成时触发事件,导致运行不正常。
if (sender.GetValue(BackgroundProperty) is FluentSolidColorBrush tmp_fluent)
{
sender.SetValue(BackgroundProperty, OldBrush);
} //如果OldBrush或者NewBrush中有一个为空,就不播放动画,直接赋值
if (OldBrush == null || NewBrush == null)
{
sender.SetValue(BackgroundProperty, NewBrush);
return;
} var FluentBrush = new FluentSolidColorBrush()
{
Duration = GetDuration(sender),
Color = OldBrush.Color,
};
FluentBrush.ColorChanged += (s, a) =>
{
sender.SetValue(BackgroundProperty, NewBrush);
};
sender.SetValue(BackgroundProperty, FluentBrush);
FluentBrush.Color = NewBrush.Color;
}
}
}
}

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

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

在Style里调用方法也类似:

 <!-- Element中 -->
<Grid x:Name="RootGrid" helper:TransitionsHelper.Background="{TemplateBinding Background}">
...
</Grid> <!-- VisualState中 -->
<VisualState x:Name="TestState">
<VisualState.Setter>
<Setter Target="RootGrid.(helper:TransitionsHelper.Background)" Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=SecondBackground}" />
</VisualState.Setter>
</VisualState>

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

最后附一张效果图:

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

最新文章

  1. Spring之旅(2)
  2. table 相关
  3. sonarqube插件开发(一) 环境搭建
  4. poj 1286&amp;&amp;poj2409 Polya计数 颜色匹配
  5. 在 Windows上配置NativeScript CLI
  6. tensorflow 学习1 环境搭建
  7. 探秘Java虚拟机——内存管理与垃圾回收
  8. 腾讯云升级到PHP7
  9. MongoDB学习笔记04
  10. Android实战技巧之十九:android studio导出jar包(Module)并获得手机信息
  11. Redis进阶之使用Lua脚本开发
  12. Java设计模式之【工厂模式】(简单工厂模式,工厂方法模式,抽象工厂模式)
  13. CSS样式中文字的换行
  14. Hadoop Api 基本操作
  15. 清理和关闭多余的Windows 7系统服务
  16. Centos 安装python3
  17. C#实现软件授权,限定MAC运行(软件license管理,简单软件注册机制)
  18. 所见即所得:七大无需编程的DIY开发工具
  19. Learning Phrase Representations using RNN Encoder–Decoder for Statistical Machine Translation
  20. Oracle——序列、索引、同义词

热门文章

  1. MySQLdb与sqlalchemy的简单封装
  2. 算法题思路总结和leecode继续历程
  3. github 与gitlab之间的工程创建
  4. js 异步加载和同步加载
  5. 【Go】 Go 语言环境安装
  6. android c 读写文件
  7. canvas 实现时钟效果
  8. JavaScript常用定义和方法
  9. Camtasia studio8.0破解方法
  10. LINQ 语法