代码之家  ›  专栏  ›  技术社区  ›  Adam Tegen

WPF淡入淡出动画

  •  39
  • Adam Tegen  · 技术社区  · 17 年前

    <Window x:Class="WadFileTester.Form1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Name="MyWindow" Title="WAD File SI Checker" Height="386" Width="563" WindowStyle="SingleBorderWindow" DragEnter="Window_DragEnter" DragLeave="Window_DragLeave" DragOver="Window_DragOver" Drop="Window_Drop" AllowDrop="True">
        <Window.Resources>
            <Style TargetType="ListView" x:Key="animatedList">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Visibility}" Value="Visible">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation
                                        Storyboard.TargetProperty="Opacity"
                                        From="0.0" To="1.0" Duration="0:0:5"
                                        />
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
                        <DataTrigger.ExitActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation
                                        Storyboard.TargetProperty="Opacity"
                                        From="1.0" To="0.0" Duration="0:0:5"
                                        />
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.ExitActions>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Window.Resources>
        <Grid>
            <ListView Name="listView1" Style="{StaticResource animatedList}" TabIndex="1" Margin="12,41,12,12" Visibility="Hidden">
            </ListView>
        </Grid>
    </Window>
    
    8 回复  |  直到 17 年前
        1
  •  53
  •   Tim Cooper    13 年前

    我不知道如何在纯XAML中同时制作动画(淡入淡出)。但简单的淡出可以相对简单地实现。将DataTriggers替换为触发器,并删除ExitActions,因为它们在淡出场景中没有意义。这就是你将拥有的:

     <Style TargetType="FrameworkElement" x:Key="animatedList">
      <Setter Property="Visibility" Value="Hidden"/>
      <Style.Triggers>
        <Trigger Property="Visibility" Value="Visible">
          <Trigger.EnterActions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation Storyboard.TargetProperty="Opacity"
                                 From="0.0" To="1.0" Duration="0:0:0.2"/>
              </Storyboard>
            </BeginStoryboard>
          </Trigger.EnterActions>
        </Trigger>
      </Style.Triggers>
    </Style>
    

    但是,嘿,不要放弃。如果你想同时支持这两种动画,我可以建议在XAML后面进行小代码编写。在我们完成一个技巧后,我们将通过在XAML中添加一行代码来获得您想要的东西:

    <Button Content="Fading button"
            x:Name="btn"
            loc:VisibilityAnimation.IsActive="True"/>
    

    每次我们换btn。“从可见到隐藏/折叠”按钮的可见性将逐渐消失。每次我们将可见性更改回原来时,按钮都会淡入。此技巧适用于任何FrameworkElement(包括ListView:)。

      public class VisibilityAnimation : DependencyObject
      {
        private const int DURATION_MS = 200;
    
        private static readonly Hashtable _hookedElements = new Hashtable();
    
        public static readonly DependencyProperty IsActiveProperty =
          DependencyProperty.RegisterAttached("IsActive", 
          typeof(bool), 
          typeof(VisibilityAnimation),
          new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
    
        public static bool GetIsActive(UIElement element)
        {
          if (element == null)
          {
            throw new ArgumentNullException("element");
          }
    
          return (bool)element.GetValue(IsActiveProperty);
        }
    
        public static void SetIsActive(UIElement element, bool value)
        {
          if (element == null)
          {
            throw new ArgumentNullException("element");
          }
          element.SetValue(IsActiveProperty, value);
        }
    
        static VisibilityAnimation()
        {
          UIElement.VisibilityProperty.AddOwner(typeof(FrameworkElement),
                                                new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(VisibilityChanged), CoerceVisibility));
        }
    
        private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
          // So what? Ignore.
        }
    
        private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
          var fe = d as FrameworkElement;
          if (fe == null)
          {
            return;
          }
          if (GetIsActive(fe))
          {
            HookVisibilityChanges(fe);
          }
          else
          {
            UnHookVisibilityChanges(fe);
          }
        }
    
        private static void UnHookVisibilityChanges(FrameworkElement fe)
        {
          if (_hookedElements.Contains(fe))
          {
            _hookedElements.Remove(fe);
          } 
        }
    
        private static void HookVisibilityChanges(FrameworkElement fe)
        {
          _hookedElements.Add(fe, false);
        }
    
        private static object CoerceVisibility(DependencyObject d, object baseValue)
        {
          var fe = d as FrameworkElement;
          if (fe == null)
          {
            return baseValue;
          }
    
          if (CheckAndUpdateAnimationStartedFlag(fe))
          {
            return baseValue;
          }
          // If we get here, it means we have to start fade in or fade out
          // animation. In any case return value of this method will be
          // Visibility.Visible. 
    
          var visibility = (Visibility)baseValue;
    
          var da = new DoubleAnimation
          {
            Duration = new Duration(TimeSpan.FromMilliseconds(DURATION_MS))
          };
    
          da.Completed += (o, e) =>
                            {
                              // This will trigger value coercion again
                              // but CheckAndUpdateAnimationStartedFlag() function will reture true
                              // this time, and animation will not be triggered.
                              fe.Visibility = visibility;
                              // NB: Small problem here. This may and probably will brake 
                              // binding to visibility property.
                            };
    
          if (visibility == Visibility.Collapsed || visibility == Visibility.Hidden)
          {
            da.From = 1.0;
            da.To = 0.0;
          }
          else
          {
            da.From = 0.0;
            da.To = 1.0;
          }
    
          fe.BeginAnimation(UIElement.OpacityProperty, da);
          return Visibility.Visible;
        }
    
        private static bool CheckAndUpdateAnimationStartedFlag(FrameworkElement fe)
        {
          var hookedElement = _hookedElements.Contains(fe);
          if (!hookedElement)
          {
            return true; // don't need to animate unhooked elements.
          }
    
          var animationStarted = (bool) _hookedElements[fe];
          _hookedElements[fe] = !animationStarted;
    
          return animationStarted;
        }
      }
    

    这里最重要的是CoerceVisibility()方法。如您所见,在淡入淡出动画完成之前,我们不允许更改此属性。

    这段代码既不是线程安全的,也不是没有bug的。它的唯一目的是指明方向:)。所以,请随时改进、编辑并获得声誉;).

        2
  •  19
  •   Nock    14 年前

    您不能直接将Visibility属性用于淡入淡出,因为在其上设置触发器将首先隐藏/折叠控件,然后为其设置动画。因此,基本上您将在折叠的控件上获得动画=>没有什么。

    IsOpen 并设置属性触发器 IsOpen=True 上面写着:

    进入行动:

    • 确保“可见性”设置为“可见”
    • 不透明度从0变为1

    退出操作:

    • 可见性设置为在关键帧0处可见,在最后一个关键帧处折叠/隐藏
    • 将“不透明度”从1渐变为0。

    这里有一个例子:

    <Style TargetType="{x:Type local:TCMenu}">
        <Style.Resources>
            <Storyboard x:Key="FadeInMenu">
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
                    <EasingDoubleKeyFrame KeyTime="0" Value="0"/>
                    <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1"/>
                </DoubleAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
                    <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
            <Storyboard x:Key="FadeOutMenu">
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
                    <EasingDoubleKeyFrame KeyTime="0" Value="1"/>
                    <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0"/>
                </DoubleAnimationUsingKeyFrames>
                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="{x:Null}">
                    <DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{x:Static Visibility.Visible}"/>
                    <DiscreteObjectKeyFrame KeyTime="0:0:0.2" Value="{x:Static Visibility.Collapsed}"/>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </Style.Resources>
        <Style.Triggers>
            <Trigger Property="IsOpen" Value="true">
                <Trigger.EnterActions>
                    <BeginStoryboard Storyboard="{StaticResource FadeInMenu}"/>
                </Trigger.EnterActions>
                <Trigger.ExitActions>
                    <BeginStoryboard Storyboard="{StaticResource FadeOutMenu}"/>
                </Trigger.ExitActions>
            </Trigger>
        </Style.Triggers>
        <Setter Property="Visibility" Value="Collapsed"/>
    </Style>
    
        3
  •  6
  •   Xtr    14 年前

    我一直在用一种稍微不同的方式来处理这个问题——我有一个雷的答案的扩展版本 this question 它为所有折叠或显示元素的内容添加了FadeIn()和FadeOut()扩展方法,然后我可以对它们调用FadeIn和FadeOut-它可以在没有任何特定动画代码的情况下处理任何元素,而不是使对象可见。

        public static T FadeFromTo(this UIElement uiElement, double fromOpacity,
            double toOpacity, int durationInMilliseconds, bool loopAnimation,
            bool showOnStart, bool collapseOnFinish)
        {
            var timeSpan = TimeSpan.FromMilliseconds(durationInMilliseconds);
            var doubleAnimation =
                  new DoubleAnimation(fromOpacity, toOpacity,
                                      new Duration(timeSpan));
                if (loopAnimation)
                    doubleAnimation.RepeatBehavior = RepeatBehavior.Forever;
                uiElement.BeginAnimation(UIElement.OpacityProperty, doubleAnimation);
                if (showOnStart)
                {
                    uiElement.ApplyAnimationClock(UIElement.VisibilityProperty, null);
                    uiElement.Visibility = Visibility.Visible;
                }
                if (collapseOnFinish)
                {
                    var keyAnimation = new ObjectAnimationUsingKeyFrames{Duration = new Duration(timeSpan) };
                    keyAnimation.KeyFrames.Add(new DiscreteObjectKeyFrame(Visibility.Collapsed, KeyTime.FromTimeSpan(timeSpan)));
                    uiElement.BeginAnimation(UIElement.VisibilityProperty, keyAnimation);
                }
                return uiElement;
        }
    
        public static T FadeIn(this UIElement uiElement, int durationInMilliseconds)
        {
            return uiElement.FadeFromTo(0, 1, durationInMilliseconds, false, true, false);
        }
    
        public static T FadeOut(this UIElement uiElement, int durationInMilliseconds)
        {
            return uiElement.FadeFromTo(1, 0, durationInMilliseconds, false, false, true);
        }
    
        4
  •  4
  •   Community Mohan Dere    7 年前

    我意识到这个问题有点老了,但我现在才读过,我已经调整了Anvaka给出的代码。它支持绑定到可见性(仅当绑定模式设置为双向时)。它还支持淡入淡出两种不同的持续时间值。

      public class VisibilityAnimation : DependencyObject
      {
        #region Private Variables
    
        private static HashSet<UIElement> HookedElements = new HashSet<UIElement>();
        private static DoubleAnimation FadeAnimation = new DoubleAnimation();
        private static bool SurpressEvent;
        private static bool Running;
    
        #endregion
    
        #region Attached Dependencies
    
        public static readonly DependencyProperty IsActiveProperty = DependencyProperty.RegisterAttached("IsActive", typeof(bool), typeof(VisibilityAnimation), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsActivePropertyChanged)));
        public static bool GetIsActive(UIElement element)
        {
          if (element == null) throw new ArgumentNullException("element");
          return (bool)element.GetValue(IsActiveProperty);
        }
        public static void SetIsActive(UIElement element, bool value)
        {
          if (element == null) throw new ArgumentNullException("element");
          element.SetValue(IsActiveProperty, value);
        }
    
        public static readonly DependencyProperty FadeInDurationProperty = DependencyProperty.RegisterAttached("FadeInDuration", typeof(double), typeof(VisibilityAnimation), new PropertyMetadata(0.5));
        public static double GetFadeInDuration(UIElement e)
        {
          if (e == null) throw new ArgumentNullException("element");
          return (double)e.GetValue(FadeInDurationProperty);
        }
        public static void SetFadeInDuration(UIElement e, double value)
        {
          if (e == null) throw new ArgumentNullException("element");
          e.SetValue(FadeInDurationProperty, value);
        }
    
        public static readonly DependencyProperty FadeOutDurationProperty = DependencyProperty.RegisterAttached("FadeOutDuration", typeof(double), typeof(VisibilityAnimation), new PropertyMetadata(1.0));
        public static double GetFadeOutDuration(UIElement e)
        {
          if (e == null) throw new ArgumentNullException("element");
          return (double)e.GetValue(FadeOutDurationProperty);
        }
        public static void SetFadeOutDuration(UIElement e, double value)
        {
          if (e == null) throw new ArgumentNullException("element");
          e.SetValue(FadeOutDurationProperty, value);
        }
    
        #endregion
    
        #region Callbacks
    
        private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
          // So what? Ignore.
          // We only specified a property changed call-back to be able to set a coercion call-back
        }
    
        private static void OnIsActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
          // Get the framework element and leave if it is null
          var fe = d as FrameworkElement;
          if (fe == null) return;
    
          // Hook the element if IsActive is true and unhook the element if it is false
          if (GetIsActive(fe)) HookedElements.Add(fe);
          else HookedElements.Remove(fe);
        }
    
        private static object CoerceVisibility(DependencyObject d, object baseValue)
        {
          if (SurpressEvent) return baseValue;  // Ignore coercion if we set the SurpressEvent flag
    
          var FE = d as FrameworkElement;
          if (FE == null || !HookedElements.Contains(FE)) return baseValue;  // Leave if the element is null or does not belong to our list of hooked elements
    
          Running = true;  // Set the running flag so that an animation does not change the visibility if another animation was started (Changing Visibility before the 1st animation completed)
    
          // If we get here, it means we have to start fade in or fade out animation
          // In any case return value of this method will be Visibility.Visible
    
          Visibility NewValue = (Visibility)baseValue;  // Get the new value
    
          if (NewValue == Visibility.Visible) FadeAnimation.Duration = new Duration(TimeSpan.FromSeconds((double)d.GetValue(FadeInDurationProperty)));  // Get the duration that was set for fade in
          else FadeAnimation.Duration = new Duration(TimeSpan.FromSeconds((double)d.GetValue(FadeOutDurationProperty)));  // Get the duration that was set for fade out
    
          // Use an anonymous method to set the Visibility to the new value after the animation completed
          FadeAnimation.Completed += (obj, args) =>
          {
            if (FE.Visibility != NewValue && !Running)
            {
              SurpressEvent = true;  // SuppressEvent flag to skip coercion
              FE.Visibility = NewValue;
              SurpressEvent = false;
              Running = false;  // Animation and Visibility change is now complete
            }
          };
    
          FadeAnimation.To = (NewValue == Visibility.Collapsed || NewValue == Visibility.Hidden) ? 0 : 1;  // Set the to value based on Visibility
    
          FE.BeginAnimation(UIElement.OpacityProperty, FadeAnimation);  // Start the animation (it will only start after we leave the coercion method)
    
          return Visibility.Visible;  // We need to return Visible in order to see the fading take place, otherwise it just sets it to Collapsed/Hidden without showing the animation
        }
    
        #endregion
    
        static VisibilityAnimation()
        {
          // Listen for visibility changes on all elements
          UIElement.VisibilityProperty.AddOwner(typeof(FrameworkElement), new FrameworkPropertyMetadata(Visibility.Visible, new PropertyChangedCallback(VisibilityChanged), CoerceVisibility));
        }    
      }
    
        5
  •  3
  •   Craig    12 年前

    这最好使用行为来完成

    class AnimatedVisibilityFadeBehavior : Behavior<Border>
       {
          public Duration AnimationDuration { get; set; }
          public Visibility InitialState { get; set; }
    
          DoubleAnimation m_animationOut;
          DoubleAnimation m_animationIn;
    
          protected override void OnAttached()
          {
             base.OnAttached();
    
             m_animationIn = new DoubleAnimation(1, AnimationDuration, FillBehavior.HoldEnd);
             m_animationOut = new DoubleAnimation(0, AnimationDuration, FillBehavior.HoldEnd);
             m_animationOut.Completed += (sender, args) =>
                {
                   AssociatedObject.SetCurrentValue(Border.VisibilityProperty, Visibility.Collapsed);
                };
    
             AssociatedObject.SetCurrentValue(Border.VisibilityProperty,
                                              InitialState == Visibility.Collapsed
                                                 ? Visibility.Collapsed
                                                 : Visibility.Visible);
    
             Binding.AddTargetUpdatedHandler(AssociatedObject, Updated);
          }
    
          private void Updated(object sender, DataTransferEventArgs e)
          {
             var value = (Visibility)AssociatedObject.GetValue(Border.VisibilityProperty);
             switch (value)
             {
                case Visibility.Collapsed:
                   AssociatedObject.SetCurrentValue(Border.VisibilityProperty, Visibility.Visible);
                   AssociatedObject.BeginAnimation(Border.OpacityProperty, m_animationOut);
                   break;
                case Visibility.Visible:
                   AssociatedObject.BeginAnimation(Border.OpacityProperty, m_animationIn);
                   break;
             }
          }
       }
    

    这是专门应用于边框的——我还没有尝试过用户控件,但我希望同样适用。

    要使用它,您需要Blend Interactivity命名空间:

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    

    <i:Interaction.Behaviors>
                    <Interactivity:AnimatedVisibilityFadeBehavior AnimationDuration="0:0:0.3" InitialState="Collapsed" />
    </i:Interaction.Behaviors>
    

    您还需要为行为类添加命名空间。。

        6
  •  2
  •   Geoff    15 年前

    现在很老了,但你能不能把DoubleAnimations连起来?

    <DataTrigger.EnterActions>
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation
                    Storyboard.TargetProperty="Opacity"
                    From="0.0" To="1.0" Duration="0:0:5"
                    />
                <DoubleAnimation
                    Storyboard.TargetProperty="Opacity"
                    From="1.0" To="0.0" Duration="0:0:5"
                    />
            </Storyboard>
        </BeginStoryboard>
    </DataTrigger.EnterActions>
    
        7
  •  2
  •   illusion    14 年前

    您可能希望尝试使用AutoReverse属性。..虽然我不确定它是否按你想要的方式工作。 这是我在MSDN上发现的:

    当时间线的AutoReverse属性设置为true并且其RepeatBehavior属性导致其重复时,每次向前迭代后都会进行向后迭代。这就重复了一次。例如,自动反转值为true、迭代计数为2的时间线将向前播放一次,然后向后播放,然后再次向前播放,然后再向后播放。