2009-06-18 67 views
35

如何让控件淡入/淡出时变为可见。WPF Fade动画

下面是我的失败尝试:

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

回答

53

我不知道如何在纯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.Visibility从可见光到隐藏/折叠按钮时会淡出。每次我们改变Visibility时,按钮都会淡入。这个技巧可以用于任何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; 
    } 
    } 

这里最重要的是CoerceVisibility()方法。正如你所看到的,我们不允许改变这个属性,直到淡入淡出的动画完成。

此代码既不是线程安全的也没有缺陷。它唯一的意图是显示方向:)。所以请随时改进,编辑并获得声望;)。

+0

可以通过以下方式实现淡入和淡出重复效果:a)将DoubleAnimation上的AutoReverse属性设置为True,b)将R epeatBehaviour物业在故事板永远 – 2010-06-17 02:36:08

+0

你注意到*// /注:小问题在这里。这可能会或可能会制动 //绑定到知名度属性。* ...有谁知道如何解决这个问题?我真的希望能够将可见性绑定到模型值。 – Akku 2012-01-10 15:59:24

2

现在已经很久了,但是你能不能连锁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> 
2

您可能想尝试AutoReverse属性......但我不确定它是否按照您希望的方式工作。 这是我在MSDN上找到的:

当时间轴的AutoReverse属性设置为true并且其RepeatBehavior属性导致它重复时,每个前向迭代后面跟着一个后向迭代。这使得重复一次。例如,一个带有AutoReverse值为true并且迭代计数为2的时间线将向前播放一次,然后向后播放,然后再向前播放,然后再向后播放。

6

我意识到这个问题有点旧,但我现在只读过它,并且我调整了Anvaka给出的代码。它支持绑定到可见性(仅当绑定模式设置为双向时)。它还支持FadeIn和FadeOut的2个不同的持续时间值。

这里是类:

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

不能直接使用Visibility属性的淡出,因为设置一个触发上会先隐藏/关闭的控制,那么它的动画。所以基本上你会得到一个动画崩溃的控制=>什么都没有。

一个“可靠”的办法是引入新的依赖项属性(连接或没有),说IsOpen,并在其上设置属性触发IsOpen=True

EnterAction:
  • 确保可见性设置至可见
  • 淡入不透明度从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> 
+0

伟大的解决方案。因为我正在使用ViewModels,所以我可以使用DataContext为null/not null作为触发器,在这种情况下,我甚至不需要附加属性:) – flq 2012-07-27 15:19:20

+1

1+这对我的需求是完美的,谢谢:) – 2013-05-15 12:13:11

3

我一直以一种稍微不同的方式 - 我有一个扩展版本的Ray的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); 
    } 
3

这是通过使用行为

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> 

你需要添加行为类的命名空间也是如此。