使用行为设置扩展器的动画
本文关键字:扩展器 动画 设置 | 更新日期: 2023-09-27 18:19:37
试图在展开和折叠事件期间用行为来动画扩展器,它在展开时有效,但在折叠时无效。在花了相当长的时间试图找出原因(Visibility==Collapsed)之后,我无法在崩溃时使其动画化。
有一种获取初始内容大小的方法,如果内容发生变化,动画肯定是不正确的,但如果内容发生了变化,就没有ContentChanged这样的事件可以抓住并获取新的大小。
行为:
public class AnimatedExpanderBehavior : Behavior<Expander>
{
public Duration Duration { get; set; }
private Size ContentSize { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Collapsed += AssociatedObject_Collapsed;
AssociatedObject.Expanded += AssociatedObject_Expanded;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Collapsed -= AssociatedObject_Collapsed;
AssociatedObject.Expanded -= AssociatedObject_Expanded;
}
private void AssociatedObject_Collapsed(object sender, RoutedEventArgs e)
{
var expander = sender as Expander;
if (expander != null)
{
var name = expander.Content as FrameworkElement;
if (name != null)
{
// Does not happen, collapses instantly instead
var animation = new DoubleAnimation(name.ActualHeight, 0, Duration);
name.BeginAnimation(FrameworkElement.HeightProperty, animation);
}
}
}
private void AssociatedObject_Expanded(object sender, RoutedEventArgs e)
{
var expander = sender as Expander;
if (expander != null)
{
var name = expander.Content as UIElement;
if (name != null)
{
// Grabbing initial content size
if (ContentSize.Width <= 0 && ContentSize.Height <= 0)
{
name.Measure(new Size(9999, 9999));
ContentSize = name.DesiredSize;
}
var animation = new DoubleAnimation(0, ContentSize.Height, Duration);
name.BeginAnimation(FrameworkElement.HeightProperty, animation);
}
}
}
}
用法:
<Expander>
<i:Interaction.Behaviors>
<behaviors:AnimatedExpanderBehavior Duration="0:0:0.2" />
</i:Interaction.Behaviors>
<Rectangle Height="100" Fill="Red" />
</Expander>
有趣的是,我一直在研究Windows UI是如何做到这一点的,我绝对确信它是双向的,而事实上它只是在扩展过程中做到的。
有没有什么限制可以阻止在崩溃时实现这样的动画?
编辑
新代码,但当内容发生变化时不会调整,而原始扩展器会调整:
private void AssociatedObject_Expanded(object sender, RoutedEventArgs e)
{
var expander = sender as Expander;
if (expander != null)
{
var name = expander.Content as FrameworkElement;
if (name != null)
{
_expandSite.Visibility = Visibility.Visible;
double height;
if (_firstExpansion)
{
name.Measure(new Size(9999, 9999));
height = name.DesiredSize.Height;
_firstExpansion = false;
}
else
{
height = name.RenderSize.Height;
}
var animation = new DoubleAnimation(0, height, new Duration(TimeSpan.FromSeconds(0.5d)));
name.BeginAnimation(FrameworkElement.HeightProperty, animation);
}
}
}
这里的问题是Expander.ControlTemplate
持有一个ContentPresenter
,一旦IsExpanded
变成false
,其Visibility
就会设置为Collapsed
因此,即使动画实际运行,也永远看不到它,因为它的父对象是不可见的。这个ContentPresenter
被称为ExpandSite
(来自默认模板),我们可以在行为中使用类似的东西来控制它
private UIElement _expandSite;
protected override void OnAttached() {
base.OnAttached();
AssociatedObject.Collapsed += AssociatedObject_Collapsed;
AssociatedObject.Expanded += AssociatedObject_Expanded;
AssociatedObject.Loaded += (sender, args) => {
_expandSite = AssociatedObject.Template.FindName("ExpandSite", AssociatedObject) as UIElement;
if (_expandSite == null)
throw new InvalidOperationException();
};
}
...
private void AssociatedObject_Collapsed(object sender, RoutedEventArgs e) {
var expander = sender as Expander;
if (expander == null)
return;
var name = expander.Content as FrameworkElement;
if (name == null)
return;
_expandSite.Visibility = Visibility.Visible;
var animation = new DoubleAnimation(name.ActualHeight, 0, Duration);
animation.Completed += (o, args) => {
_expandSite.Visibility = Visibility.Collapsed;
name.BeginAnimation(FrameworkElement.HeightProperty, null);
};
name.BeginAnimation(FrameworkElement.HeightProperty, animation);
}
private void AssociatedObject_Expanded(object sender, RoutedEventArgs e) {
var expander = sender as Expander;
if (expander == null)
return;
var name = expander.Content as FrameworkElement;
if (name == null)
return;
if (name.DesiredSize.Width <= 0 && name.DesiredSize.Height <= 0)
name.Measure(new Size(9999, 9999));
_expandSite.Visibility = Visibility.Visible;
var animation = new DoubleAnimation(0, name.DesiredSize.Height, Duration);
animation.Completed += (o, args) => name.BeginAnimation(FrameworkElement.HeightProperty, null);
name.BeginAnimation(FrameworkElement.HeightProperty, animation);
}
我们在展开动画之前也设置_expandSite.Visibility = Visibility.Visible;
的原因是,当我们从Behavior中设置ExpandSite
的Visibility
时,它优先,并且忽略默认Style
中的Trigger.Setter
。因此,我们可以在这两种情况下管理Visibility
。
你确实有一个替代整个过程的人。不要使用Behavior<...>
,只需为Expander
提供一个自定义Style
,并在ControlTemplate
中相应地指定Trigger.Enter/ExitActions
,即可为ExpandSite
的Visibility
和您的Content
设置动画。
更新:
示例下载:链接
在您的原始代码中也存在重新调整大小的问题。这与我发布的答案无关,因为我们添加的只是切换Visibility
和ExpandSite
。该问题是由于动画冻结了Content
的Height
属性,从而不允许出现任何未来的更改,除非通过以下动画。
这个^^样本也应该有相应的修正。