使用行为设置扩展器的动画

本文关键字:扩展器 动画 设置 | 更新日期: 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中设置ExpandSiteVisibility时,它优先,并且忽略默认Style中的Trigger.Setter。因此,我们可以在这两种情况下管理Visibility

你确实有一个替代整个过程的人。不要使用Behavior<...>,只需为Expander提供一个自定义Style,并在ControlTemplate中相应地指定Trigger.Enter/ExitActions,即可为ExpandSiteVisibility和您的Content设置动画。

更新:

示例下载:链接

在您的原始代码中也存在重新调整大小的问题。这与我发布的答案无关,因为我们添加的只是切换VisibilityExpandSite。该问题是由于动画冻结了ContentHeight属性,从而不允许出现任何未来的更改,除非通过以下动画。

这个^^样本也应该有相应的修正。