Animación de desvanecimiento WPF

¿Cómo haría un control de fundido de entrada / salida cuando se vuelve Visible?

Debajo está mi bash fallido:

                            

No sé cómo hacer ambas animaciones (fundido de entrada y salida) 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ás:

   

Pero oye, no te rindas. Si desea admitir ambas animaciones, puedo sugerir pequeñas codificaciones detrás del XAML. Después de hacer un truco, obtendremos lo que desea al agregar una línea de código en XAML:

  

Cada vez que cambiemos btn. La visibilidad del botón Visible a Oculto / Colapsado se desvanecerá. Y cada vez que cambiemos Visibilidad, el botón se desvanecerá. Este truco funcionará con cualquier FrameworkElement (incluido ListView :)).

Aquí está 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 subprocesos 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;).

No se puede usar directamente la propiedad Visibilidad para un fundido de salida porque al establecer un activador primero se ocultará / contraerá el control, luego se lo animará. Entonces, básicamente, obtendrás una animación en un control contraído => nada.

Una forma “confiable” sería introducir una nueva Propiedad de Dependencia (adjunta o no), decir IsOpen y establecer un disparador de propiedad IsOpen=True en él con:

EnterAction:

  • Asegúrese de que la Visibilidad esté configurada en Visible
  • Fundido en la opacidad de 0 a 1

ExitAction:

  • La visibilidad se establece en Visible en el fotogtwig clave 0 y se contrae / oculta en el último fotogtwig clave
  • Fundir la Opacidad de 1 a 0.

Aquí hay un ejemplo:

  

Me doy cuenta de que esta Cuestión es un poco vieja, pero solo la he leído ahora y he ajustado el código dado 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í está la clase:

  public class VisibilityAnimation : DependencyObject { #region Private Variables private static HashSet HookedElements = new HashSet(); 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)); } } 

He llegado a esto de una manera ligeramente diferente. Tengo una versión extendida de la respuesta de Ray a esta pregunta que agrega un método de extensión FadeIn () y FadeOut () a todo lo que colapsa o muestra el elemento como corresponde, luego para hacer visibles los objetos, puedo llamar a FadeIn () y FadeOut () sobre ellos, y 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); } 

Esto se hace mejor usando un comportamiento

 class AnimatedVisibilityFadeBehavior : Behavior { 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 se aplica específicamente a un borde: no he probado un control de usuario, pero espero que se aplique lo mismo.

Para usarlo, necesita el espacio de nombre de Blend Interactivity:

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

Y use este marcado en el borde en el que desea que aparezca el comportamiento:

    

También deberá agregar en el espacio de nombres para la clase de comportamiento …

Ya es bastante viejo, pero ¿no podrías simplemente encadenar las Anotaciones dobles?

         

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, a cada iteración directa le sigue 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.