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

WPF渐变动画

  •  36
  • Adam Tegen  · 技术社区  · 15 年前

    如何使控件在可见时淡入/淡出。

    以下是我失败的尝试:

    <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>
    
    7 回复  |  直到 6 年前
        1
  •  53
  •   Tim Cooper    11 年前

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

     <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:)。

    以下是VisibilityAnimation.IsActive附加属性的代码:

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

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

    此代码既不是线程安全的,也不是无缺陷的。它的唯一目的是显示方向:)。因此,请随时改进、编辑和获得声誉;)。

        2
  •  19
  •   Nock    12 年前

    不能直接对淡出使用Visibility属性,因为对其设置触发器将首先隐藏/折叠控件,然后对其进行动画设置。所以基本上你会得到一个折叠控件上的动画=>没有。

    一种“可靠”的方法是引入一个新的依赖属性(附加的或不附加的),比如 IsOpen 并设置属性触发器 IsOpen=True 与之有关:

    行动:
    • 确保可见性设置为可见
    • 将不透明度从0淡入1
    ExitAction:
    • 可见性设置为在关键帧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    12 年前

    我知道这个问题有点老了,但是我现在才读到它,我已经修改了Anvaka给出的代码。它支持绑定到可见性(仅当绑定模式设置为twoway时)。它还支持fadein和fadeout的两个不同的持续时间值。

    这是课程:

      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));
        }    
      }
    
        4
  •  4
  •   Community Erin Dees    6 年前

    我是以一种稍微不同的方式来回答这个问题的——我有一个关于雷的答案的扩展版本。 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);
        }
    
        5
  •  3
  •   Craig    10 年前

    最好用一种行为

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

    这特别适用于边框-我没有尝试过用户控件,但我希望同样适用。

    要使用它,需要混合交互命名空间:

    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    14 年前

    现在已经很老了,但你能不能把这些双重含义连起来?

    <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    13 年前

    您可能想尝试autoreverse属性…尽管我不确定它是否按你想要的方式工作。 这是我在msdn上找到的:

    当时间线的AutoReverse属性设置为true并且其RepeatBehavior属性导致它重复时,每个正向迭代之后都是反向迭代。这会重复一次。例如,一个时间线的autoreverse值为true,迭代计数为2,将向前播放一次,然后向后播放,然后再向前播放,然后再向后播放。