XAML WPF淡出动画

r6vfmomb  于 2022-12-07  发布在  其他
关注(0)|答案(8)|浏览(210)

当控件变为可见时,如何使其淡入/淡出?
以下是我失败的尝试:

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

zbwhf8kr1#

我不知道如何在纯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从Visible更改为Hidden/Collapsed按钮时,该按钮将淡出。而每次我们将Visibility更改为Back时,该按钮将淡入。此技巧适用于任何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()方法。正如你所看到的,我们不允许改变这个属性,直到淡入淡出动画完成。
这段代码既不是线程安全的,也不是没有错误的。它的唯一目的是显示方向:)。所以请随意改进,编辑和获得声誉;).

yizd12fk

yizd12fk2#

你不能直接使用Visibility属性来淡出,因为在它上面设置一个触发器会首先隐藏/折叠控件,然后动画化它。所以基本上你会在一个折叠的控件上得到一个动画=〉什么都没有。
一种“可靠”的方法是引入一个新的依赖属性(附加的或未附加的),比如IsOpen,并在其上设置一个属性触发器IsOpen=True,其中:

输入操作:
  • 确保“可见性”设置为“可见
  • 将不透明度从0淡入到1
退出操作:
  • “可见性”设置为“在关键帧0可见”和“在最后一个关键帧折叠/隐藏”
  • 将“不透明度”(Opacity)从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>
5ssjco0h

5ssjco0h3#

我用了一种稍微不同的方法--我得到了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);
    }
ogsagwnx

ogsagwnx4#

我知道这个问题有点老了,但我现在才读过它,我已经调整了Anvaka给出的代码。它支持绑定到可见性(仅当绑定模式设置为双向时)。它还支持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));
    }    
  }
46qrfjad

46qrfjad5#

最好使用行为来完成此操作

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"

并在您要在其上执行行为的Border上使用此标记:

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

您还需要为行为类添加命名空间。

vwhgwdsa

vwhgwdsa6#

现在已经很老了,但是你能不能不把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>
8qgya5xd

8qgya5xd7#

您可能想尝试AutoReverse属性...虽然我不确定它是否按您希望的方式工作。这是我在MSDN上找到的内容:
当时刻表的AutoReverse属性设定为true,且其RepeatBehavior属性导致它重复时,每个向前反覆运算后面都会跟着一个向后反覆运算。这会重复一次。例如,AutoReverse值为true且反覆运算计数为2的时刻表会向前播放一次,然后向后播放,再向前播放,再向后播放。

unhi4e5o

unhi4e5o8#

我更喜欢Nock's XAML-only solution。结合Mike Nakis的评论,它给了我一个完美的解决方案。我自己也在DataTrigger上挣扎。这就是为什么我想在这里分享它:

<TextBlock x:Name="Text1" Text="Hello World!" FontSize="30" d:Visibility="Visible">
    <TextBlock.Style>
        <Style>
            <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>
                <DataTrigger Binding="{Binding IsOpen}" Value="true">
                    <DataTrigger.EnterActions>
                        <BeginStoryboard Storyboard="{StaticResource FadeInMenu}"/>
                    </DataTrigger.EnterActions>
                    <DataTrigger.ExitActions>
                        <BeginStoryboard Storyboard="{StaticResource FadeOutMenu}"/>
                    </DataTrigger.ExitActions>
                </DataTrigger>
            </Style.Triggers>
            <Setter Property="TextBlock.Visibility" Value="Collapsed" />
        </Style>
    </TextBlock.Style>
</TextBlock>

另外,不要忘记在代码后面为PropertyIsOpen 实现INotifyPropertyChanged

相关问题