使用行为动画扩展器 [英] Animating an Expander using a Behavior

查看:24
本文介绍了使用行为动画扩展器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

尝试在 Expanded 和 Collapsed 事件期间使用 Behavior 为 Expander 设置动画时,它在展开时有效,但在折叠时无效.在花了很长时间试图找出原因(可见性 == 折叠)后,我无法在折叠时设置动画.

抓取初始内容大小有一种技巧,如果内容发生变化,动画肯定是不正确的,但是如果内容发生变化,则没有 ContentChanged 类型的事件可以钩住并抓取新的大小.

>

行为:

public class AnimatedExpanderBehavior : Behavior{公共持续时间持续时间{得到;放;}私有大小 ContentSize { 获取;放;}受保护的覆盖 void OnAttached(){base.OnAttached();AssociatedObject.Collapsed += AssociatedObject_Collapsed;AssociatedObject.Expanded += AssociatedObject_Expanded;}受保护的覆盖 void OnDetaching(){base.OnDetaching();AssociatedObject.Collapsed -= AssociatedObject_Collapsed;AssociatedObject.Expanded -= AssociatedObject_Expanded;}private void AssociatedObject_Collapsed(对象发送者,RoutedEventArgs e){var expander = sender as Expander;如果(扩展器!= null){var name = expander.Content as FrameworkElement;如果(名称!= null){//不会发生,而是立即崩溃var animation = new DoubleAnimation(name.ActualHeight, 0, Duration);name.BeginAnimation(FrameworkElement.HeightProperty, 动画);}}}私有无效 AssociatedObject_Expanded(对象发送者,RoutedEventArgs e){var expander = sender as Expander;如果(扩展器!= null){var name = expander.Content as UIElement;如果(名称!= null){//获取初始内容大小如果(ContentSize.Width <= 0 && ContentSize.Height <= 0){name.Measure(新尺寸(9999, 9999));ContentSize = name.DesiredSize;}var animation = new DoubleAnimation(0, ContentSize.Height, Duration);name.BeginAnimation(FrameworkElement.HeightProperty, 动画);}}}}

用法:

<i:Interaction.Behaviors><behaviors:AnimatedExpanderBehavior Duration="0:0:0.2"/></i:Interaction.Behaviors><矩形高度=100"填充=红色"/></扩展器>

有趣的是,我一直在研究 Windows UI 如何做到这一点,我绝对确定它是双向的,而实际上它只在扩展期间这样做.

是否有任何限制会阻止在折叠时实现此类动画?

编辑

新代码,但是它不会在内容更改时进行调整,而原始扩展器会这样做:

 private void AssociatedObject_Expanded(object sender, RoutedEventArgs e){var expander = sender as Expander;如果(扩展器!= null){var name = expander.Content as FrameworkElement;如果(名称!= null){_expandSite.Visibility = Visibility.Visible;双高;如果(_firstExpansion){name.Measure(新尺寸(9999, 9999));高度 = name.DesiredSize.Height;_firstExpansion = false;}别的{高度 = name.RenderSize.Height;}var animation = new DoubleAnimation(0, height, new Duration(TimeSpan.FromSeconds(0.5d)));name.BeginAnimation(FrameworkElement.HeightProperty, 动画);}}}

解决方案

你的问题是 Expander.ControlTemplate 持有一个 ContentPresenterVisibility> 设置为 Collapsed 一旦 IsExpanded 变为 false

因此,即使您的动画实际运行,您也永远看不到它,因为它的父级是不可见的.这个 ContentPresenter 被称为 ExpandSite(来自默认模板),我们可以用类似的东西在行为中控制它

私有 UIElement _expandSite;受保护的覆盖无效 OnAttached() {base.OnAttached();AssociatedObject.Collapsed += AssociatedObject_Collapsed;AssociatedObject.Expanded += AssociatedObject_Expanded;AssociatedObject.Loaded += (sender, args) =>{_expandSite = AssociatedObject.Template.FindName("ExpandSite", AssociatedObject) as UIElement;如果(_expandSite == null)抛出新的 InvalidOperationException();};}...私有无效 AssociatedObject_Collapsed(对象发送者,RoutedEventArgs e){var expander = sender as Expander;如果(扩展器 == 空)返回;var name = expander.Content as FrameworkElement;如果(名称 == 空)返回;_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, 动画);}私有无效 AssociatedObject_Expanded(对象发送者,RoutedEventArgs e){var expander = sender as Expander;如果(扩展器 == 空)返回;var name = expander.Content as FrameworkElement;如果(名称 == 空)返回;if (name.DesiredSize.Width <= 0 && name.DesiredSize.Height <= 0)name.Measure(新尺寸(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, 动画);}

我们在设置ExpandSiteVisibility时,在展开动画之前还要设置_expandSite.Visibility = Visibility.Visible;的原因是cos> 从行为中,它具有优先权,并且会忽略默认 Style 中的 Trigger.Setter.因此,我们必须在两种情况下管理 Visibility.

您确实有整个过程的替代方法.不要使用 Behavior<...>,而只是为 Expander 提供自定义 Style 并指定 Trigger.在 ControlTemplate 中相应地输入/退出操作 以动画 ExpandSiteVisibility 和您的 Content.>

更新:

示例下载:链接

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

这个 ^^ 样本也应该有修复.

Trying to animate an Expander with a Behavior during Expanded and Collapsed events, it does work when expanding but not when collapsing. After spending quite some time to try figure out the cause (Visibility == Collapsed) I couldn't make it animate when collapsing.

There's a sort of hack on grabbing initial content size, the animation would certainly be incorrect in case of content changing but there is no event of kind ContentChanged to hook onto and grab the new size in case the content is changed.

Behavior:

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);
            }
        }
    }
}

Usage :

<Expander>
    <i:Interaction.Behaviors>
        <behaviors:AnimatedExpanderBehavior Duration="0:0:0.2" />
    </i:Interaction.Behaviors>
    <Rectangle Height="100" Fill="Red" />
</Expander>

Interestingly I've been looking at how Windows UI does it, I was absolutely certain that it was doing it both ways while in fact it does it only during expansion.

Is there any sort of limitation that would prevent achieving such animation when collapsing ?

Edit

New code, however it does not adjust when content changes while the original expander does:

    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);
            }
        }
    }

解决方案

Your problem here is the Expander.ControlTemplate holds a ContentPresenter whose Visibility is set to Collapsed as soon as IsExpanded becomes false

Thus even if your animation actually runs you never get to see it since it's parent is invisible. This ContentPresenter is called ExpandSite(from default template) and we can get a hold of it in the behavior with something like

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);
}

The reason why we also set _expandSite.Visibility = Visibility.Visible; before the expanding animation is cos when we set the Visibility of ExpandSite from the Behavior, it takes precedence and the Trigger.Setter from the default Style is ignored. Thus we got to manage Visibility in both cases.

You do have an alternate to this entire process. Don't use a Behavior<...>, instead just provide a custom Style for the Expander and specify Trigger.Enter/ExitActions accordingly in the ControlTemplate to animate Visibility of ExpandSite and your Content.

Update:

Sample Download: Link

The problem with re-sizing was there in your original code as well. It has nothing to do with the answer I posted as all we added was toggling Visibility of ExpandSite. That issue is due to the animation freezing the Height property of the Content thereby not allowing any future changes to appear unless via a following animation.

This ^^ sample should have the fix for that as well.

这篇关于使用行为动画扩展器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆