2009-06-18 21 views
35

¿Cómo puedo hacer un control de fundido de entrada/salida cuando se vuelve Visible.WPF Fade Animation

A continuación es mi intento fallido:

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

Respuesta

53

No sé cómo hacer las dos animaciones (fade in y fade out) en XAML puro. Pero el simple desvanecimiento se puede lograr de manera relativamente simple. Reemplace DataTriggers por Triggers y elimine ExitActions ya que no tienen sentido en el escenario Fade out. Esto es lo que tendrá:

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

Pero oye, no te rindas. Si desea admitir ambas animaciones, puedo sugerir pequeñas codificaciones detrás del XAML. Después hacemos un truco, vamos a obtener lo que desea mediante la adición de una línea de código en XAML:

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

Cada vez que cambiamos btn.Visibility de visible a colapsada botón oculto/se desvanecerá. Y cada vez que cambiemos Visibilidad, el botón se desvanecerá. Este truco funcionará con cualquier FrameworkElement (incluido ListView :)).

Este es el código de la propiedad adjunta 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; 
    } 
    } 

Lo más importante aquí es el método CoerceVisibility(). Como puede ver, no permitimos cambiar esta propiedad hasta que se complete la animación de desvanecimiento.

Este código no es seguro para hilos ni está libre de errores. Su única intención es mostrar la dirección :). Así que siéntete libre de mejorar, editar y obtener reputación;).

+0

Se puede lograr el efecto de repetición de fundido de entrada y salida mediante: a) estableciendo la propiedad AutoReverse en DoubleAnimation en True, b) configurando el R epeatBehaviour Property en Storyboard to Forever –

+0

Notaste * // NB: Pequeño problema aquí. Esto puede y probablemente frenará // vinculando a la propiedad de visibilidad. * ... ¿Alguien sabe cómo solucionar esto? Realmente me gustaría poder vincular la visibilidad a un valor de modelo. – Akku

2

Bastante viejo ahora, pero ¿no podrías simplemente encadenar las 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

Es posible que desee probar la propiedad AutoReverse ... aunque no estoy seguro si funciona de la manera que desea. Esto es lo que encontré en MSDN:

Cuando la propiedad AutoReverse de una línea de tiempo se establece en true y su propiedad RepeatBehavior hace que se repita, cada iteración directa va seguida de una iteración hacia atrás. Esto hace una repetición. Por ejemplo, una línea de tiempo con un valor de AutoReverse de verdadero con una cuenta de iteración de 2 se reproducirá hacia adelante una vez, luego hacia atrás, luego hacia delante otra vez, y luego hacia atrás otra vez.

6

Me doy cuenta de que esta Cuestión es un poco antigua, pero solo la he leído ahora y he modificado el código proporcionado por Anvaka. Admite el enlace a Visibilidad (solo cuando el modo de enlace está configurado en TwoWay). También es compatible con 2 valores de duración diferentes para FadeIn y FadeOut.

Aquí es la clase:

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

No se puede utilizar directamente la propiedad de visibilidad para un fundido de salida, porque el establecimiento de un disparador en primer lugar se oculta/Contraer el control, entonces animarlo. Entonces, básicamente, obtendrás una animación en un control contraído => nada.

Una forma "fiable" sería la introducción de una nueva propiedad de dependencia (que se adjunta o no), por ejemplo IsOpen y el establecimiento de un desencadenador de propiedad IsOpen=True en él con:

EnterAction:
  • Asegúrese de visibilidad se ha establecido a Visible
  • fundido de la opacidad de 0 a 1
ExitAction:
  • Visibilidad configurada como visible en el fotograma clave 0 y se derrumbó/Hidden en el último fotograma clave
  • Fundido a cabo la opacidad de 1 a 0.

He aquí un ejemplo:

<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

Gran solución. Como estoy trabajando con ViewModels, podría usar el DataContext como nulo/no nulo como disparador, en cuyo caso ni siquiera necesito la propiedad adjunta :) – flq

+1

1+ esto es perfecto para mis necesidades, Gracias :) –

3

he estado llegando a esto de una manera ligeramente diferente - Tengo una versión extendida de la respuesta de Ray a this question que agrega un método de extensión FadeIn() y FadeOut() a todo lo que colapsa o muestra el elemento como sea apropiado, entonces, en lugar de hacer objetos visibles, puede simplemente invocar FadeIn() y FadeOut() sobre ellos d funcionará con cualquier elemento sin ningún código de animación específico.

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

Great solution. Esto también funciona bien para desvanecimientos cruzados: llama a FadeIn() en un elemento y luego a FadeOut() en el otro que comparte la misma ubicación. Sin embargo, no veo qué sentido tienen los genéricos. Puede simplemente hacer FadeOut (este UIElement uiElement, ...), por ejemplo. – t9mike

+0

Buen punto, bien hecho. ¡Cambiado! –

3

Esto se hace mejor con un comportamiento

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

Esto específicamente se está aplicando a una frontera - No he probado un control de usuario pero espero que lo mismo se aplica.

Para utilizarlo, es necesario el espacio de nombres de mezcla Interactividad:

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

y utilizar esta marcado en el borde que desea el comportamiento en:

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

Usted tendrá que añadir en el espacio de nombres para la clase de comportamiento también ...

+1

También es necesario agregar NotifyOnTargetUpdated = True al enlace de control. – Random