Advanced XAML Animation effects. Pulse, Marching a

2019-01-09 11:49发布

问题:

I have a list of alarms. When alarms are activated it has been requested to make them more noticeable. When an alarms status changes I wanted to create a pulsing Outer Glow around the item for a few seconds and then have it disappear.

The problem I am having is that I can't seem to make the DropShadowEffect appear only when I need it. I tried setting the opacity to 0 and the color to Transparent but then it seems to disable the animation. I considered adding the effect in with a Style Trigger but then I'm not sure how you would remove it when the animation is done?

Any advice on how to accomplish this?

<Rectangle Grid.Column="1" Grid.Row="0">    
    <Rectangle.Effect>
        <DropShadowEffect ShadowDepth="0" BlurRadius="0" Opacity="0" Color="White"    /> 
    </Rectangle.Effect>   
<Rectangle.Style>
<Style>
    <Style.Triggers>
        <DataTrigger Binding="{Binding Value, Converter={StaticResource AlarmConverter}, IsAsync=True}" Value="true">
            <DataTrigger.EnterActions>
               <BeginStoryboard>
                   <Storyboard FillBehavior="Stop" >
                       <DoubleAnimation Storyboard.TargetProperty="Effect.Opacity" To="1" FillBehavior="Stop" />
                        <ColorAnimation Storyboard.TargetProperty="Effect.Color" To="White" FillBehavior="Stop" />
                        <DoubleAnimation Storyboard.TargetProperty="Effect.BlurRadius" From="0" To="20" RepeatBehavior="3x" FillBehavior="Stop" />                                                            
                   </Storyboard>
              </BeginStoryboard>
          </DataTrigger.EnterActions>
      </DataTrigger>

Update Here's an example of what a section of the screen looks like. I had to remove the text and other aspects from the screenshot due to security concerns, my apologies. I replaced most of them generic text to still provide context. Another thing to note is that in general this isn't ran on normal monitors. It usually displayed along side other screens on a very long, very large video wall.

The Alarm with the words Binding Limits (the first word was removed, Text doesn't normally sit over like that) has a reasonable animated rolling gradient that replaced a simple flashing. Additionally the alarm status in the case causes the shape spin.

The feedback was that when an alarm status changes for the first time (from green to yellow or orange to red etc) that they would like some additional indication. I had a few different ideas. My first was to try animating the text to cause it to expand a little almost pulsing. When I tried however it expands very obviously to the right and down only and didn't really give that "Swelling" effect I had hoped for.

Another idea for which I asked here was to possibly animate a glowing border around the alarm item. Something that would get Bright and then Dim 3 or 4 times when the status changed. I am open to other ideas however. I really like the animation abilities in WPF but I am finding difficulty in using it to articulate my ideas. (Something I think will come with time)

Update Tried animating the Stroke and StrokeThickness as well as the effect and while it helps the Glow stand out the border is way too hard and noticible, especially when the alarm color is red.

<BeginStoryboard>
     <Storyboard FillBehavior="Stop">
         <DoubleAnimation Storyboard.TargetProperty="StrokeThickness" To="2"   />
         <ColorAnimation Storyboard.TargetProperty="Stroke.Color" To="White"  />
         <DoubleAnimation Storyboard.TargetProperty="Effect.Opacity" To="30"   />
         <ColorAnimation Storyboard.TargetProperty="Effect.Color" To="White"  />
         <DoubleAnimation Storyboard.TargetProperty="Effect.BlurRadius" From="0" To="100" AutoReverse="True"  Duration="00:00:02" RepeatBehavior="3x" />                                                            
     </Storyboard>
</BeginStoryboard>

Update Demo'd an outer glow for the alarm as a whole and it was deemed too subtle. I'm entertaining the idea of a dancing 7up dot at this point... (not really)

回答1:

Ok amigo, so I took about 15-20mins this morning to go ahead and throw together a few random examples of styles I've used in the past for notification type stuff. I'm guessing the mock you show is just a real rough example, so without knowing more precisely what it looks like it's difficult to match the style like a designer would want to.

However, I imagine you could use these examples to get the creative juices flowing on how/what route to go, and just a taste of some of the things you can do real quick and easily. If you want a prettier/more precise example you'll have to share the real screens etc. The animations and stuff could apply to other objects like the icon and stuff but for this example I just threw them at some boxes.

These are from my own bag of tricks, use them freely, I have tons more different techniques I could show you if ya want also but if we get too involved, well this sort of in depth advice is how I make a living, so may have to at least charge ya a case of beer or something ;)

Anyhow, throw this in a window, I just did it with a fresh quick wpf proj. so you'll just paste them in and run it, they're set to start on load.

Main thing I've found to avoid, is messing heavily with gradients and the various pixel shader stuff like that in mass animations. However stuff like this seems to do fine.

OUTPUT (In choppy .gif animated style anyway for visual example.):

AND THE MAGIC:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" x:Class="MainWindow"
    Title="MainWindow" Height="450" Width="250">
    <Window.Resources>

        <!-- Marching Ants -->
        <Storyboard x:Key="MarchingAnts">
                <DoubleAnimation BeginTime="00:00:00"
                                Storyboard.TargetName="AlertBox"
                                Storyboard.TargetProperty="StrokeThickness"
                                To="4"
                                Duration="0:0:0.25" />
                           <!-- If you want to run counter-clockwise, just swap the 'From' and 'To' values. -->
                <DoubleAnimation BeginTime="00:00:00" RepeatBehavior="Forever" Storyboard.TargetName="AlertBox" Storyboard.TargetProperty="StrokeDashOffset" 
                                Duration="0:3:0" From="1000" To="0"/>
        </Storyboard>

        <!-- Pulse -->
        <Storyboard x:Key="Pulse" RepeatBehavior="Forever">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="PulseBox">
                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1.15"/>
                <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="PulseBox">
                <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1.15"/>
                <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>

        <!-- Flipper -->
        <Storyboard x:Key="Flipper" RepeatBehavior="Forever">
            <PointAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransformOrigin)" Storyboard.TargetName="FlipperBox">
                <EasingPointKeyFrame KeyTime="0:0:1" Value="0.5,0.5"/>
                <EasingPointKeyFrame KeyTime="0:0:2" Value="0.5,0.5"/>
            </PointAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="FlipperBox">
                <EasingDoubleKeyFrame KeyTime="0:0:1" Value="-1"/>
                <EasingDoubleKeyFrame KeyTime="0:0:2" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>


        <!-- Elasic Lines -->
        <Storyboard x:Key="ElasticLines" RepeatBehavior="Forever" AutoReverse="True">
            <PointAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(LinearGradientBrush.EndPoint)" Storyboard.TargetName="ElasticBox">
                <EasingPointKeyFrame KeyTime="0:0:4" Value="12,8"/>
            </PointAnimationUsingKeyFrames>
        </Storyboard>

        <!-- Knight Rider -->
        <Storyboard x:Key="KnightRider" RepeatBehavior="Forever" AutoReverse="True">
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="KRBox">
                <EasingDoubleKeyFrame KeyTime="0:0:1" Value="-50"/>
                <EasingDoubleKeyFrame KeyTime="0:0:2" Value="50"/>
                <EasingDoubleKeyFrame KeyTime="0:0:3" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>

    </Window.Resources>
    <Window.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Storyboard="{StaticResource Pulse}"/>
            <BeginStoryboard Storyboard="{StaticResource MarchingAnts}"/>
            <BeginStoryboard Storyboard="{StaticResource Flipper}"/>
            <BeginStoryboard Storyboard="{StaticResource ElasticLines}"/>
            <BeginStoryboard Storyboard="{StaticResource KnightRider}"/>
        </EventTrigger>
    </Window.Triggers>

    <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
        <Grid.Resources>
            <Style TargetType="{x:Type TextBlock}">
                <Setter Property="Foreground" Value="White"/>
                <Setter Property="FontWeight" Value="Bold"/>
                <Setter Property="FontSize" Value="35"/>
                <Setter Property="HorizontalAlignment" Value="Center"/>
                <Setter Property="VerticalAlignment" Value="Center"/>
                <Setter Property="Text" Value="ALERT"/>
            </Style>
            <Style TargetType="{x:Type Grid}">
                <Setter Property="Margin" Value="0,10"/>                
            </Style>
            <Style TargetType="{x:Type Rectangle}">
                <Setter Property="Height" Value="50"/>
                <Setter Property="Width" Value="150"/>
            </Style>
        </Grid.Resources>

        <StackPanel>

        <!-- Marching Ants -->
        <Grid>

            <Rectangle x:Name="AlertBox"
                      Stroke="Red" 
                       StrokeDashOffset="2" StrokeDashArray="5" Margin="5">
                <Rectangle.Fill>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="6,4" MappingMode="Absolute" SpreadMethod="Repeat">
                        <GradientStop Color="Red" Offset="0.25"/>
                        <GradientStop Color="#00000000" Offset="0.15"/>
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle>

            <TextBlock/>

        </Grid>
        <!-- End Marching Ants -->


        <!-- Pulse : Will not skew other elements location like width/height animations would. -->
        <Grid>
            <Border x:Name="PulseBox"
                        Background="Red" RenderTransformOrigin="0.5,0.5">
                <Border.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform/>
                        <TranslateTransform/>
                    </TransformGroup>
                </Border.RenderTransform>

                <TextBlock/>

            </Border>
        </Grid>
        <!-- End Pulse -->


        <!-- Flipper -->
        <Grid>
            <Border x:Name="FlipperBox"
                        Background="Red">
                <Border.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform/>
                        <TranslateTransform/>
                    </TransformGroup>
                </Border.RenderTransform>

                <TextBlock/>

            </Border>
        </Grid>
        <!-- End Flipper -->


        <!-- Elastic Lines -->
        <Grid>
            <Rectangle x:Name="ElasticBox"
                      Stroke="Red" StrokeThickness="5" Margin="5">
                <Rectangle.Fill>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="6,4" MappingMode="Absolute" SpreadMethod="Repeat">
                        <GradientStop Color="Red" Offset="0.25"/>
                        <GradientStop Color="#00000000" Offset="0.15"/>
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle>

            <TextBlock/>

        </Grid>
        <!-- End Elastic Box -->


        <!-- Knight Rider -->
        <Grid>
            <Rectangle Fill="Red"/>
            <Rectangle x:Name="KRBox" Width="50" Fill="White" RenderTransformOrigin="0.5,0.5">
                <Rectangle.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform/>
                        <TranslateTransform/>
                    </TransformGroup>
                </Rectangle.RenderTransform>
            </Rectangle>

            <TextBlock Foreground="Red"/>

        </Grid>
        <!-- End Knight Rider -->

        </StackPanel>

    </Grid>
</Window>


回答2:

I am adding a test scenario using TextBox

<Grid>
    <Grid.Resources>
        <SolidColorBrush x:Key="BlackColor" Color="Black" />
        <SolidColorBrush x:Key="WhiteColor" Color="White" />
    </Grid.Resources>
    <TextBox Text="{Binding Test}" Width="150" Height="25">
        <TextBox.Effect>
            <DropShadowEffect ShadowDepth="0" BlurRadius="0" Opacity="0" Color="White"    />
        </TextBox.Effect>
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Style.Triggers>
                    <EventTrigger RoutedEvent="TextChanged">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <ThicknessAnimation Storyboard.TargetProperty="BorderThickness" From="1" To="2" Duration="0:0:2" AutoReverse="True"/>
                                    <DoubleAnimation Storyboard.TargetProperty="Effect.Opacity" To="1" />
                                    <ColorAnimation Storyboard.TargetProperty="Effect.Color" From="Red" To="Purple" Duration="0:0:2" RepeatBehavior="2x" AutoReverse="True" />
                                    <DoubleAnimation Storyboard.TargetProperty="Effect.ShadowDepth" From="0" To="1" Duration="0:0:2" RepeatBehavior="2x" AutoReverse="True"/>
                                    <DoubleAnimation Storyboard.TargetProperty="Effect.BlurRadius" From="0" To="30" Duration="0:0:2" AutoReverse="True" RepeatBehavior="2x" />
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{StaticResource BlackColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:1" Value="{StaticResource WhiteColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:2" Value="{StaticResource BlackColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:3" Value="{StaticResource WhiteColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:4" Value="{StaticResource BlackColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:5" Value="{StaticResource WhiteColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:6" Value="{StaticResource BlackColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:7" Value="{StaticResource WhiteColor}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{StaticResource WhiteColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:1" Value="{StaticResource BlackColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:2" Value="{StaticResource WhiteColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:3" Value="{StaticResource BlackColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:4" Value="{StaticResource WhiteColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:5" Value="{StaticResource BlackColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:6" Value="{StaticResource WhiteColor}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:7" Value="{StaticResource BlackColor}" />
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
</Grid>

Added some more animation for the Background and Foreground