Вопрос по xaml – WPF Fade Animation

36

Как сделать так, чтобы элемент управления появлялся / исчезал, когда он становился видимым?

Ниже моя неудачная попытка:

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

Ваш Ответ

7   ответов
6

что этот Вопрос немного устарел, но я только сейчас прочитал его и настроил код, предоставленный Anvaka. Он поддерживает привязку к видимости (только если режим привязки установлен на TwoWay). Он также поддерживает 2 разных значения длительности для 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));
    }    
  }
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, кнопка Видимый на Скрытый / Свернутый исчезнет. И каждый раз, когда мы меняем видимость обратно, кнопка постепенно исчезает. Этот прием будет работать с любым 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 (). Как видите, мы не разрешаем изменять это свойство до тех пор, пока анимация не исчезнет.

Этот код не является ни потокобезопасным, ни свободным от ошибок. Его единственное намерение - показать направление :). Так что смело улучшайте, редактируйте и получайте репутацию;).

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded// NB: Small problem here. This may and probably will brake // binding to visibility property.Error: User Rate Limit Exceeded
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>
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;
         }
      }
   }

Это конкретно применяется к границе - я не пробовал пользовательский элемент управления, но я ожидаю, что то же самое применимо.

Чтобы использовать его, вам нужно пространство имен 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>

Вам также нужно добавить в пространство имен класс поведения.

Error: User Rate Limit Exceeded
19

потому что установка на него триггера сначала спрячет / свернет элемент управления, а затем оживит его. Таким образом, вы получите анимацию на свернутом элементе управления = & gt; ничего такого.

Один "надежный" можно было бы ввести новое свойство зависимости (прикрепленное или нет), скажем,IsOpen и установка триггера свойстваIsOpen=True на это с:

EnterAction: Make sure Visibility is set to Visible Fade in the Opacity from 0 to 1 ExitAction: Visibility set to Visible at keyframe 0 and Collapsed/Hidden at the last Keyframe Fade out the Opacity from 1 to 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>
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
2

вы захотите попробовать свойство AutoReverse ... хотя я не уверен, что оно работает так, как вы хотите. Вот что я нашел в MSDN:

Когда свойство AutoReverse временной шкалы установлено в значение true, а его свойство RepeatBehavior вызывает его повторение, за каждой прямой итерацией следует обратная итерация. Это делает одно повторение. Например, временная шкала со значением AutoReverse, равным true, с числом итераций, равным 2, будет воспроизводиться вперед один раз, затем назад, затем снова вперед и затем снова назад.

5

твета Рэя наэтот вопрос который добавляет метод расширения 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);
    }
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded

Похожие вопросы