在XAML中如何为网格列或网格行添加动画效果?

14
有没有一种方式可以从XAML中实现对网格列宽或网格行高进行动画处理?
6个回答

19

不妨尝试一个解决方法?为什么不在你想要动画的特定行内放置一个网格(或任何其他所需控件),将该行高度设置为“自动”,然后对控件的高度进行动画处理。这对我有用。

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="30"/>
    <RowDefinition Height="Auto"/>
  </Grid.RowDefinitions>
  <Button x:Name="ExpandCollapseBtn" Width="100" Click="ExpandCollapse_Click"/>
  <WrapPanel x:Name="ToolBox" Grid.Row="1" Height="0">
    <Button Content="1" Width="50" Height="50"/>
    <Button Content="2" Width="50" Height="50"/>
    <Button Content="3" Width="50" Height="50"/>
    <Button Content="4" Width="50" Height="50"/>
  </WrapPanel>
</Grid>

代码后台:

private bool Expanded = false;
void ExpandCollapse_Click(object sender, RoutedEventArgs e)
{
  if (Expanded)
  {
    var anim = new DoubleAnimation(0, (Duration)TimeSpan.FromSeconds(0.3));
    anim.Completed += (s, _) => Expanded = false;
    ToolBox.BeginAnimation(ContentControl.HeightProperty, anim);
  }
  else
  {
    var anim = new DoubleAnimation(100, (Duration)TimeSpan.FromSeconds(0.3));
    anim.Completed += (s, _) => Expanded = true;
    ToolBox.BeginAnimation(ContentControl.HeightProperty, anim);
  }
}

我承认这不是你要寻找的,但它是一个快速的解决方案(当然前提是你最终想要UI元素放置在通过动画设置行动的网格内),对于列宽度也可以采用类似的方法。


这绝对是最简单的方法...非常好用,代码/麻烦度最小。 - AshbyEngineer

5

我在 Nigel Shaw 提供的 AnimationHelper 类的基础上进行了改进,并将其封装在可重用的 GridAnimationBehavior 中,可以附加到 RowDefinitionColumnDefinition 元素上。

/// <summary>
/// Wraps the functionality provided by the <see cref="AnimationHelper"/> class
/// in a behavior which can be used with the <see cref="ColumnDefinition"/>
/// and <see cref="RowDefinition"/> types.
/// </summary>
public class GridAnimationBehavior : DependencyObject
{
  #region Attached IsExpanded DependencyProperty

  /// <summary>
  /// Register the "IsExpanded" attached property and the "OnIsExpanded" callback 
  /// </summary>
  public static readonly DependencyProperty IsExpandedProperty =
    DependencyProperty.RegisterAttached("IsExpanded", typeof(bool), typeof(GridAnimationBehavior),
      new FrameworkPropertyMetadata(OnIsExpandedChanged));

  public static void SetIsExpanded(DependencyObject dependencyObject, bool value)
  {
    dependencyObject.SetValue(IsExpandedProperty, value);
  }

  #endregion

  #region Attached Duration DependencyProperty

  /// <summary>
  /// Register the "Duration" attached property 
  /// </summary>
  public static readonly DependencyProperty DurationProperty =
    DependencyProperty.RegisterAttached("Duration", typeof(TimeSpan), typeof(GridAnimationBehavior),
      new FrameworkPropertyMetadata(TimeSpan.FromMilliseconds(200)));

  public static void SetDuration(DependencyObject dependencyObject, TimeSpan value)
  {
    dependencyObject.SetValue(DurationProperty, value);
  }

  private static TimeSpan GetDuration(DependencyObject dependencyObject)
  {
    return (TimeSpan)dependencyObject.GetValue(DurationProperty);
  }

  #endregion

  #region GridCellSize DependencyProperty

  /// <summary>
  /// Use a private "GridCellSize" dependency property as a temporary backing 
  /// store for the last expanded grid cell size (row height or column width).
  /// </summary>
  private static readonly DependencyProperty GridCellSizeProperty =
    DependencyProperty.Register("GridCellSize", typeof(double), typeof(GridAnimationBehavior),
      new UIPropertyMetadata(0.0));

  private static void SetGridCellSize(DependencyObject dependencyObject, double value)
  {
    dependencyObject.SetValue(GridCellSizeProperty, value);
  }

  private static double GetGridCellSize(DependencyObject dependencyObject)
  {
    return (double)dependencyObject.GetValue(GridCellSizeProperty);
  }

  #endregion

  /// <summary>
  /// Called when the attached <c>IsExpanded</c> property changed.
  /// </summary>
  private static void OnIsExpandedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
  {
    var duration = GetDuration(dependencyObject);
    var rowDefinition = dependencyObject as RowDefinition;
    if (rowDefinition != null)
    {
      // The IsExpanded attached property of a RowDefinition changed
      if ((bool)e.NewValue)
      {
        var expandedHeight = GetGridCellSize(rowDefinition);
        if (expandedHeight > 0)
        {
          // Animate row height back to saved expanded height.
          AnimationHelper.AnimateGridRowExpandCollapse(rowDefinition, true, expandedHeight, rowDefinition.ActualHeight, 0, duration);
        }
      }
      else
      {
        // Save expanded height and animate row height down to zero.
        SetGridCellSize(rowDefinition, rowDefinition.ActualHeight);
        AnimationHelper.AnimateGridRowExpandCollapse(rowDefinition, false, rowDefinition.ActualHeight, 0, 0, duration);
      }
    }

    var columnDefinition = dependencyObject as ColumnDefinition;
    if (columnDefinition != null)
    {
      // The IsExpanded attached property of a ColumnDefinition changed
      if ((bool)e.NewValue)
      {
        var expandedWidth = GetGridCellSize(columnDefinition);
        if (expandedWidth > 0)
        {
          // Animate column width back to saved expanded width.
          AnimationHelper.AnimateGridColumnExpandCollapse(columnDefinition, true, expandedWidth, columnDefinition.ActualWidth, 0, duration);
        }
      }
      else
      {
        // Save expanded width and animate column width down to zero.
        SetGridCellSize(columnDefinition, columnDefinition.ActualWidth);
        AnimationHelper.AnimateGridColumnExpandCollapse(columnDefinition, false, columnDefinition.ActualWidth, 0, 0, duration);
      }
    }
  }
}

请注意,我稍微修改了尼格尔的代码,使用TimeSpan类型的参数来代替单独的秒和毫秒参数来控制动画持续时间。
这种行为使得网格行/列的动画更加符合MVVM的要求(仅限于XAML,无需代码后台)。例如:
<Grid.RowDefinitions>
  <RowDefinition Height="*" Behaviors:GridAnimationBehavior.IsExpanded="{Binding IsUpperPaneVisible}" />
  <RowDefinition Height="*" />
  <RowDefinition Height="*" Behaviors:GridAnimationBehavior.IsExpanded="{Binding IsLowerPaneVisible}" />
</Grid.RowDefinitions>

我添加了这个答案,因为原贴作者要求一个纯XAML的解决方案。


5

我前一段时间厌倦了不得不调整XAML来动画化网格的行和列,因此我写了几个方法完全从代码中进行操作。

使用这些方法,您可以通过一行代码从代码中扩展/缩小列和行:

Animation.AnimationHelper.AnimateGridColumnExpandCollapse(LeftColumn, true, expandedHeight, currentWidth, LeftColumn.MinWidth, 0, 200);

需要注意的一件重要事情是在动画完成时将其设置为null。如果不这样做,当动画完成后,网格仍然受到动画的控制。如果网格没有分割器,那么这可能没问题,但如果网格有分割器并且您希望在动画完成后能够手动调整其大小,则必须在动画完成后将其设置为null。

以下是方法:

    /// <summary>
    /// Animate expand/collapse of a grid column. 
    /// </summary>
    /// <param name="gridColumn">The grid column to expand/collapse.</param>
    /// <param name="expandedWidth">The expanded width.</param>
    /// <param name="milliseconds">The milliseconds component of the duration.</param>
    /// <param name="collapsedWidth">The width when collapsed.</param>
    /// <param name="minWidth">The minimum width of the column.</param>
    /// <param name="seconds">The seconds component of the duration.</param>
    /// <param name="expand">If true, expand, otherwise collapse.</param>
    public static void AnimateGridColumnExpandCollapse(ColumnDefinition gridColumn, bool expand, double expandedWidth, double collapsedWidth, 
        double minWidth, int seconds, int milliseconds)
    {
        if( expand && gridColumn.ActualWidth >= expandedWidth)
            // It's as wide as it needs to be.
            return;

        if (!expand && gridColumn.ActualWidth == collapsedWidth)
            // It's already collapsed.
            return;

        Storyboard storyBoard = new Storyboard();

        GridLengthAnimation animation = new GridLengthAnimation();
        animation.From = new GridLength(gridColumn.ActualWidth);
        animation.To = new GridLength(expand ? expandedWidth : collapsedWidth);
        animation.Duration = new TimeSpan(0, 0, 0, seconds, milliseconds);

        // Set delegate that will fire on completion.
        animation.Completed += delegate
        {
            // Set the animation to null on completion. This allows the grid to be resized manually
            gridColumn.BeginAnimation(ColumnDefinition.WidthProperty, null);

            // Set the final value manually.
            gridColumn.Width = new GridLength(expand ? expandedWidth : collapsedWidth);

            // Set the minimum width.
            gridColumn.MinWidth = minWidth;
        };

        storyBoard.Children.Add(animation);

        Storyboard.SetTarget(animation, gridColumn);
        Storyboard.SetTargetProperty(animation, new PropertyPath(ColumnDefinition.WidthProperty));
        storyBoard.Children.Add(animation);

        // Begin the animation.
        storyBoard.Begin();
    }

    /// <summary>
    /// Animate expand/collapse of a grid row. 
    /// </summary>
    /// <param name="gridRow">The grid row to expand/collapse.</param>
    /// <param name="expandedHeight">The expanded height.</param>
    /// <param name="collapsedHeight">The collapesed height.</param>
    /// <param name="minHeight">The minimum height.</param>
    /// <param name="milliseconds">The milliseconds component of the duration.</param>
    /// <param name="seconds">The seconds component of the duration.</param>
    /// <param name="expand">If true, expand, otherwise collapse.</param>
    public static void AnimateGridRowExpandCollapse(RowDefinition gridRow, bool expand, double expandedHeight, double collapsedHeight, double minHeight, int seconds, int milliseconds)
    {
        if (expand && gridRow.ActualHeight >= expandedHeight)
            // It's as high as it needs to be.
            return;

        if (!expand && gridRow.ActualHeight == collapsedHeight)
            // It's already collapsed.
            return;

        Storyboard storyBoard = new Storyboard();

        GridLengthAnimation animation = new GridLengthAnimation();
        animation.From = new GridLength(gridRow.ActualHeight);
        animation.To = new GridLength(expand ? expandedHeight : collapsedHeight);
        animation.Duration = new TimeSpan(0, 0, 0, seconds, milliseconds);

        // Set delegate that will fire on completioon.
        animation.Completed += delegate
        {
            // Set the animation to null on completion. This allows the grid to be resized manually
            gridRow.BeginAnimation(RowDefinition.HeightProperty, null);

            // Set the final height.
            gridRow.Height = new GridLength(expand ? expandedHeight : collapsedHeight);

            // Set the minimum height.
            gridRow.MinHeight = minHeight;
        };

        storyBoard.Children.Add(animation);

        Storyboard.SetTarget(animation, gridRow);
        Storyboard.SetTargetProperty(animation, new PropertyPath(RowDefinition.HeightProperty));
        storyBoard.Children.Add(animation);

        // Begin the animation.
        storyBoard.Begin();
    }

5
你使用的GridLengthAnimation类是什么? - koenmetsu
这是来自于这个回复 https://dev59.com/tHA75IYBdhLWcg3wqKx0#3181521 - 最后一个链接 - 只是稍作修改。 - stambikk

5
ColumnDefinition.WidthRowDefinition.Height属性的类型是GridLength,而且没有内置的动画效果。如果您想要实现这个功能,可能需要创建自己的GridLengthAnimation类。如果以DoubleAnimation为例,这可能并不太难,但也不容易...编辑:实际上,在Google上搜索“GridLength animation”会得到几个有趣的结果...以下是一些链接:http://windowsclient.net/learn/video.aspx?v=70654http://marlongrech.wordpress.com/2007/08/20/gridlength-animation/http://www.codeproject.com/KB/WPF/GridLengthAnimation.aspx

4
“windowsclient.net”链接已经失效。看起来微软正在拆除任何Windows 8之前的东西,或者按照汉隆剃刀原理,根本不关心它。 - codekaizen
1
第一个链接被Wayback Machine保存了。[http://web.archive.org/web/20111201124746/http://windowsclient.net/learn/video.aspx?v=70654] - fernio
在周围寻找并发现了这个答案,是针对另一个问题的。也许它会帮助其他人 https://dev59.com/wnVC5IYBdhLWcg3wvT1a#12629636 - kenny

2

MahApps.Metro 库内置了一个控件来实现这个功能。源代码可以在 这里 找到。

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="48" x:Name="HamburgerMenuColumn" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>

    <Grid.Resources>
        <Storyboard x:Key="CloseMenu" Storyboard.TargetName="HamburgerMenuColumn" Storyboard.TargetProperty="(ColumnDefinition.Width)">
            <metro:GridLengthAnimation To="48" Duration="00:00:00"></metro:GridLengthAnimation>
        </Storyboard>
     </Grid.Resources>
</Grid>

0

这对我有用

XAML中:

<Grid >
    <Grid.RenderTransform>
        <TranslateTransform  />
    </Grid.RenderTransform>

    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition x:Name="SecondRow"  Height="100"/>
    </Grid.RowDefinitions>
</Grid >

代码后台:

if (SecondRow.Height == new GridLength(0))
{

    Task.Run(() => {
        for(int i = 0; i <= 20; i++)
        {
            this.Dispatcher.Invoke((Action)delegate { SecondRow.Height = new GridLength(5*i); });
            Task.Delay(1).Wait();
        }
    });
}
else
{
    Task.Run(() => {
        for (int i = 20; i >= 0; i--)
        {
            this.Dispatcher.Invoke((Action)delegate { SecondRow.Height = new GridLength(5 * i); });
            Task.Delay(1).Wait();
        }
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接