WPF自定义IEasingFunction不继承Freezable为何触发冻结异常?
WPF自定义缓动函数继承Freezable的原因说明
问题背景
我正在尝试实现一个自定义的IEasingFunction,它本质是另一个IEasingFunction的包装器,会在动画的开头和结尾增加停顿效果。我不希望它继承自EasingFunctionBase,因为给PausingEasingFunction设置EasingMode没有实际意义:无论使用什么缓动模式,停顿效果都是一致的,被包装的缓动函数可以自行指定其缓动模式。
初始代码
PausingEasingFunction.cs
public class PausingEasingFunction : DependencyObject, IEasingFunction { public static readonly DependencyProperty WrappedEasingFunctionProperty = DependencyProperty.Register(nameof(WrappedEasingFunction), typeof(IEasingFunction), typeof(PausingEasingFunction)); public static readonly DependencyProperty StartPauseProperty = DependencyProperty.Register(nameof(StartPause), typeof(double), typeof(PausingEasingFunction)); public static readonly DependencyProperty EndPauseProperty = DependencyProperty.Register(nameof(EndPause), typeof(double), typeof(PausingEasingFunction)); public IEasingFunction WrappedEasingFunction { get => (IEasingFunction)GetValue(WrappedEasingFunctionProperty); set => SetValue(WrappedEasingFunctionProperty, value); } public double StartPause { get => (double)GetValue(StartPauseProperty); set => SetValue(StartPauseProperty, value); } public double EndPause { get => (double)GetValue(EndPauseProperty); set => SetValue(EndPauseProperty, value); } public double Ease(double normalizedTime) { if (normalizedTime < StartPause) { return 0; } else if (normalizedTime <= 1 - EndPause) { if (WrappedEasingFunction == null) { return (normalizedTime - StartPause) / (1 - StartPause - EndPause); } else { return WrappedEasingFunction.Ease((normalizedTime - StartPause) / (1 - StartPause - EndPause)); } } else { return 1; } } }
XAML使用代码(local:是指向PausingEasingFunction所在clr命名空间的xmlns前缀):
<TextBlock Text="Hello I am some animated text!"> <TextBlock.RenderTransform> <TranslateTransform X="0" x:Name="transform"/> </TextBlock.RenderTransform> <TextBlock.Triggers> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <BeginStoryboard.Storyboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="X" Storyboard.TargetName="transform" RepeatBehavior="Forever" From="0" To="100" Duration="0:0:10"> <DoubleAnimation.EasingFunction> <local:PausingEasingFunction> <local:PausingEasingFunction.WrappedEasingFunction> <SineEase EasingMode="EaseInOut"/> </local:PausingEasingFunction.WrappedEasingFunction> </local:PausingEasingFunction> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </BeginStoryboard.Storyboard> </BeginStoryboard> </EventTrigger> </TextBlock.Triggers> </TextBlock>
问题现象
运行代码会抛出InvalidOperationException,提示Freezable无法冻结。修改PausingEasingFunction.cs让其继承自Freezable而非DependencyObject,并实现CreateInstanceCore方法后异常消失,新增代码如下:
protected override Freezable CreateInstanceCore() { return new PausingEasingFunction(); }
问题解答
- 这不是WPF的Bug,属于WPF动画系统的设计特性,你的操作没有错误。
- WPF的动画相关对象(包括Storyboard、各类时间线、动画类)本身都是Freezable类型,为了优化运行性能,动画首次启动前WPF会尝试冻结整个动画对象树中的所有可冻结对象,其中就包括缓动函数属性值。
- 初始实现中你的自定义缓动函数继承自DependencyObject,不属于Freezable类型,WPF的冻结逻辑遍历到该对象时无法完成冻结操作,就会抛出对应的异常。
- 只要你将自定义类型作为动画的EasingFunction属性值使用,就必须让其继承自Freezable,这是WPF动画系统的硬性要求。你不需要继承EasingFunctionBase,直接继承Freezable实现IEasingFunction接口即可,你修改后的写法是符合WPF规范的。
内容的提问来源于stack exchange,提问作者nalka
相关产品推荐
相关产品推荐

