Clean-up and simplify Shadow Animations, add support for animating all shadows
This commit is contained in:
Родитель
864791961c
Коммит
ca6f772a99
|
@ -13,30 +13,63 @@
|
|||
|
||||
<Page.Resources>
|
||||
<media:AttachedCardShadow x:Key="CommonShadow" Offset="4" CornerRadius="0"/>
|
||||
|
||||
<ani:AnimationSet x:Key="ShadowEnterAnimation">
|
||||
<ani:OffsetDropShadowAnimation To="12"/>
|
||||
</ani:AnimationSet>
|
||||
|
||||
<ani:AnimationSet x:Key="ShadowExitAnimation">
|
||||
<ani:OffsetDropShadowAnimation To="4"/>
|
||||
</ani:AnimationSet>
|
||||
|
||||
<ani:AnimationSet x:Key="ShadowPopAnimation" IsSequential="True">
|
||||
<ani:TranslationAnimation To="-8" Duration="0:0:1"/>
|
||||
<ani:OffsetDropShadowAnimation To="16" Duration="0:0:2" Target="{StaticResource CommonShadow}"/>
|
||||
<ani:OffsetDropShadowAnimation To="4" Delay="0:0:0.5" Duration="0:0:2" Target="{StaticResource CommonShadow}"/>
|
||||
<ani:TranslationAnimation To="0" Duration="0:0:1"/>
|
||||
</ani:AnimationSet>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Image x:Name="ImageWithShadow"
|
||||
ui:Effects.Shadow="{StaticResource CommonShadow}"
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image ui:Effects.Shadow="{StaticResource CommonShadow}"
|
||||
Height="100" Width="100"
|
||||
Source="ms-appx:///Assets/Photos/Owl.jpg">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactions:EventTriggerBehavior EventName="PointerEntered">
|
||||
<behaviors:StartAnimationAction Animation="{Binding ElementName=ShadowEnterAnimation}"/>
|
||||
<behaviors:StartAnimationAction Animation="{StaticResource ShadowEnterAnimation}"/>
|
||||
</interactions:EventTriggerBehavior>
|
||||
<interactions:EventTriggerBehavior EventName="PointerExited">
|
||||
<behaviors:StartAnimationAction Animation="{Binding ElementName=ShadowExitAnimation}"/>
|
||||
<behaviors:StartAnimationAction Animation="{StaticResource ShadowExitAnimation}"/>
|
||||
</interactions:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<ani:Explicit.Animations>
|
||||
<ani:AnimationSet x:Name="ShadowEnterAnimation">
|
||||
<ani:OffsetDropShadowAnimation From="4" To="12" Target="{Binding ElementName=ImageWithShadow}"/>
|
||||
</ani:AnimationSet>
|
||||
|
||||
<ani:AnimationSet x:Name="ShadowExitAnimation">
|
||||
<ani:OffsetDropShadowAnimation From="12" To="4" Target="{Binding ElementName=ImageWithShadow}"/>
|
||||
</ani:AnimationSet>
|
||||
</ani:Explicit.Animations>
|
||||
</Image>
|
||||
<Image ui:Effects.Shadow="{StaticResource CommonShadow}"
|
||||
Height="100" Width="100"
|
||||
Grid.Column="1"
|
||||
Source="ms-appx:///Assets/Photos/Owl.jpg">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactions:EventTriggerBehavior EventName="PointerEntered">
|
||||
<behaviors:StartAnimationAction Animation="{StaticResource ShadowEnterAnimation}"/>
|
||||
</interactions:EventTriggerBehavior>
|
||||
<interactions:EventTriggerBehavior EventName="PointerExited">
|
||||
<behaviors:StartAnimationAction Animation="{StaticResource ShadowExitAnimation}"/>
|
||||
</interactions:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Image>
|
||||
<Button Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Top" Content="Click Me">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactions:EventTriggerBehavior EventName="Click">
|
||||
<behaviors:StartAnimationAction Animation="{StaticResource ShadowPopAnimation}"/>
|
||||
</interactions:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
|
|
@ -4,4 +4,5 @@
|
|||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Media")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Behaviors")]
|
||||
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Media")]
|
||||
|
|
|
@ -21,8 +21,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
|
|||
/// properties. This can differ from <typeparamref name="TKeyFrame"/> to facilitate XAML parsing.
|
||||
/// </typeparam>
|
||||
/// <typeparam name="TKeyFrame">The actual type of keyframe values in use.</typeparam>
|
||||
public abstract class ShadowAnimation<TShadow, TValue, TKeyFrame> : Animation<TValue, TKeyFrame>
|
||||
where TShadow : FrameworkElement
|
||||
public abstract class ShadowAnimation<TShadow, TValue, TKeyFrame> : Animation<TValue, TKeyFrame>, IAttachedTimeline
|
||||
where TShadow : AttachedShadowBase
|
||||
where TKeyFrame : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -46,21 +46,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
|
|||
/// <inheritdoc/>
|
||||
public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint)
|
||||
{
|
||||
if (Target is not TShadow target)
|
||||
{
|
||||
static AnimationBuilder ThrowTargetNullException() => throw new ArgumentNullException("The target element is null, make sure to set the Target property");
|
||||
|
||||
return ThrowTargetNullException();
|
||||
}
|
||||
|
||||
var shadowBase = Effects.GetShadow(Target);
|
||||
if (shadowBase == null)
|
||||
{
|
||||
static AnimationBuilder ThrowArgumentNullException() => throw new ArgumentNullException("The target's shadow is null, make sure to set the Target property to an element with a Shadow");
|
||||
|
||||
return ThrowArgumentNullException();
|
||||
}
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AnimationBuilder AppendToBuilder(AnimationBuilder builder, UIElement parent, TimeSpan? delayHint = null, TimeSpan? durationHint = null, EasingType? easingTypeHint = null, EasingMode? easingModeHint = null)
|
||||
{
|
||||
if (ExplicitTarget is not string explicitTarget)
|
||||
{
|
||||
static AnimationBuilder ThrowArgumentNullException()
|
||||
|
@ -72,20 +63,52 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
|
|||
return ThrowArgumentNullException();
|
||||
}
|
||||
|
||||
var shadow = shadowBase.GetElementContext(Target).Shadow;
|
||||
if (Target is TShadow allShadows)
|
||||
{
|
||||
// in this case we'll animate all the shadows being used.
|
||||
foreach (var context in allShadows.GetElementContextEnumerable()) //// TODO: Find better way!!!
|
||||
{
|
||||
NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
|
||||
explicitTarget,
|
||||
Delay ?? delayHint ?? DefaultDelay,
|
||||
Duration ?? durationHint ?? DefaultDuration,
|
||||
Repeat,
|
||||
DelayBehavior);
|
||||
|
||||
NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
|
||||
explicitTarget,
|
||||
Delay ?? delayHint ?? DefaultDelay,
|
||||
Duration ?? durationHint ?? DefaultDuration,
|
||||
Repeat,
|
||||
DelayBehavior);
|
||||
AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
|
||||
|
||||
AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
|
||||
CompositionAnimation animation = keyFrameBuilder.GetAnimation(context.Shadow, out _);
|
||||
|
||||
CompositionAnimation animation = keyFrameBuilder.GetAnimation(shadow, out _);
|
||||
builder.ExternalAnimation(context.Shadow, animation);
|
||||
}
|
||||
|
||||
return builder.ExternalAnimation(shadow, animation);
|
||||
return builder;
|
||||
}
|
||||
else
|
||||
{
|
||||
var shadowBase = Effects.GetShadow(parent as FrameworkElement);
|
||||
if (shadowBase == null)
|
||||
{
|
||||
static AnimationBuilder ThrowArgumentNullException() => throw new ArgumentNullException("The target's shadow is null, make sure to set the Target property to an element with a Shadow");
|
||||
|
||||
return ThrowArgumentNullException();
|
||||
}
|
||||
|
||||
var shadow = shadowBase.GetElementContext((FrameworkElement)parent).Shadow;
|
||||
|
||||
NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
|
||||
explicitTarget,
|
||||
Delay ?? delayHint ?? DefaultDelay,
|
||||
Duration ?? durationHint ?? DefaultDuration,
|
||||
Repeat,
|
||||
DelayBehavior);
|
||||
|
||||
AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
|
||||
|
||||
CompositionAnimation animation = keyFrameBuilder.GetAnimation(shadow, out _);
|
||||
|
||||
return builder.ExternalAnimation(shadow, animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -119,7 +119,15 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
|
|||
{
|
||||
foreach (object node in this)
|
||||
{
|
||||
if (node is ITimeline timeline)
|
||||
if (node is IAttachedTimeline attachedTimeline)
|
||||
{
|
||||
var builder = AnimationBuilder.Create();
|
||||
|
||||
attachedTimeline.AppendToBuilder(builder, element);
|
||||
|
||||
await builder.StartAsync(element, token);
|
||||
}
|
||||
else if (node is ITimeline timeline)
|
||||
{
|
||||
var builder = AnimationBuilder.Create();
|
||||
|
||||
|
@ -166,6 +174,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
|
|||
{
|
||||
switch (node)
|
||||
{
|
||||
case IAttachedTimeline attachedTimeline:
|
||||
builder = attachedTimeline.AppendToBuilder(builder, element);
|
||||
break;
|
||||
case ITimeline timeline:
|
||||
builder = timeline.AppendToBuilder(builder);
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
|
||||
namespace Microsoft.Toolkit.Uwp.UI.Animations
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface representing a XAML model for a custom animation that requires a specific parent <see cref="UIElement"/> context.
|
||||
/// </summary>
|
||||
public interface IAttachedTimeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Appends the current animation to a target <see cref="AnimationBuilder"/> instance.
|
||||
/// This method is used when the current <see cref="ITimeline"/> instance is explicitly triggered.
|
||||
/// </summary>
|
||||
/// <param name="builder">The target <see cref="AnimationBuilder"/> instance to schedule the animation on.</param>
|
||||
/// <param name="parent">The parent <see cref="UIElement"/> this animation will be started on.</param>
|
||||
/// <param name="delayHint">A hint for the animation delay, if present.</param>
|
||||
/// <param name="durationHint">A hint for the animation duration, if present.</param>
|
||||
/// <param name="easingTypeHint">A hint for the easing type, if present.</param>
|
||||
/// <param name="easingModeHint">A hint for the easing mode, if present.</param>
|
||||
/// <returns>The same <see cref="AnimationBuilder"/> instance as <paramref name="builder"/>.</returns>
|
||||
AnimationBuilder AppendToBuilder(
|
||||
AnimationBuilder builder,
|
||||
UIElement parent,
|
||||
TimeSpan? delayHint = null,
|
||||
TimeSpan? durationHint = null,
|
||||
EasingType? easingTypeHint = null,
|
||||
EasingMode? easingModeHint = null);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
|
|||
public interface ITimeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Appens the current animation to a target <see cref="AnimationBuilder"/> instance.
|
||||
/// Appends the current animation to a target <see cref="AnimationBuilder"/> instance.
|
||||
/// This method is used when the current <see cref="ITimeline"/> instance is explicitly triggered.
|
||||
/// </summary>
|
||||
/// <param name="builder">The target <see cref="AnimationBuilder"/> instance to schedule the animation on.</param>
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
|
|||
/// <summary>
|
||||
/// An offset animation working on the composition layer.
|
||||
/// </summary>
|
||||
public sealed class OffsetDropShadowAnimation : ShadowAnimation<FrameworkElement, string, Vector3>
|
||||
public sealed class OffsetDropShadowAnimation : ShadowAnimation<AttachedShadowBase, string, Vector3>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override string ExplicitTarget => nameof(DropShadow.Offset);
|
||||
|
|
|
@ -58,13 +58,19 @@ namespace Microsoft.Toolkit.Uwp.UI.Behaviors
|
|||
ThrowArgumentNullException();
|
||||
}
|
||||
|
||||
UIElement parent = null;
|
||||
|
||||
if (TargetObject is not null)
|
||||
{
|
||||
Animation.Start(TargetObject);
|
||||
}
|
||||
else if (Animation.ParentReference?.TryGetTarget(out parent) == true) //// TODO: Tidy... apply same pattern to Activities?
|
||||
{
|
||||
Animation.Start(parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Animation.Start();
|
||||
Animation.Start(sender as UIElement);
|
||||
}
|
||||
|
||||
return null!;
|
||||
|
|
|
@ -58,13 +58,19 @@ namespace Microsoft.Toolkit.Uwp.UI.Behaviors
|
|||
ThrowArgumentNullException();
|
||||
}
|
||||
|
||||
UIElement parent = null;
|
||||
|
||||
if (TargetObject is not null)
|
||||
{
|
||||
Animation.Stop(TargetObject);
|
||||
}
|
||||
else if (Animation.ParentReference?.TryGetTarget(out parent) == true) //// TODO: Tidy...
|
||||
{
|
||||
Animation.Stop(parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Animation.Stop();
|
||||
Animation.Stop(sender as UIElement);
|
||||
}
|
||||
|
||||
return null!;
|
||||
|
|
|
@ -123,22 +123,22 @@ namespace Microsoft.Toolkit.Uwp.UI
|
|||
// Need to remove all old children from previous container if it's changed
|
||||
if (prevContainer != null && prevContainer != shadow._container)
|
||||
{
|
||||
foreach (var context in shadow.ShadowElementContextTable)
|
||||
foreach (var context in shadow.GetElementContextEnumerable())
|
||||
{
|
||||
if (context.Value.IsInitialized &&
|
||||
prevContainer.Children.Contains(context.Value.SpriteVisual))
|
||||
if (context.IsInitialized &&
|
||||
prevContainer.Children.Contains(context.SpriteVisual))
|
||||
{
|
||||
prevContainer.Children.Remove(context.Value.SpriteVisual);
|
||||
prevContainer.Children.Remove(context.SpriteVisual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure all child shadows are hooked into container
|
||||
foreach (var context in shadow.ShadowElementContextTable)
|
||||
foreach (var context in shadow.GetElementContextEnumerable())
|
||||
{
|
||||
if (context.Value.IsInitialized)
|
||||
if (context.IsInitialized)
|
||||
{
|
||||
shadow.SetElementChildVisual(context.Value);
|
||||
shadow.SetElementChildVisual(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,12 +154,12 @@ namespace Microsoft.Toolkit.Uwp.UI
|
|||
{
|
||||
// Don't use sender or 'e' here as related to container element not
|
||||
// element for shadow, grab values off context. (Also may be null from internal call.)
|
||||
foreach (var context in ShadowElementContextTable)
|
||||
foreach (var context in GetElementContextEnumerable())
|
||||
{
|
||||
if (context.Value.IsInitialized)
|
||||
if (context.IsInitialized)
|
||||
{
|
||||
// TODO: Should we use ActualWidth/Height instead of RenderSize?
|
||||
OnSizeChanged(context.Value, context.Value.Element.RenderSize, context.Value.Element.RenderSize);
|
||||
OnSizeChanged(context, context.Element.RenderSize, context.Element.RenderSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Windows.Foundation;
|
||||
|
@ -64,7 +65,7 @@ namespace Microsoft.Toolkit.Uwp.UI
|
|||
/// <summary>
|
||||
/// Gets or sets the collection of <see cref="AttachedShadowElementContext"/> for each element this <see cref="AttachedShadowBase"/> is connected to.
|
||||
/// </summary>
|
||||
protected ConditionalWeakTable<FrameworkElement, AttachedShadowElementContext> ShadowElementContextTable { get; set; }
|
||||
private ConditionalWeakTable<FrameworkElement, AttachedShadowElementContext> ShadowElementContextTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the blur radius of the shadow.
|
||||
|
@ -178,6 +179,18 @@ namespace Microsoft.Toolkit.Uwp.UI
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an enumerator over the current list of <see cref="AttachedShadowElementContext"/> of elements using this shared shadow definition.
|
||||
/// </summary>
|
||||
/// <returns>Enumeration of <see cref="AttachedShadowElementContext"/> objects.</returns>
|
||||
public IEnumerable<AttachedShadowElementContext> GetElementContextEnumerable()
|
||||
{
|
||||
foreach (var kvp in ShadowElementContextTable)
|
||||
{
|
||||
yield return kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="AttachedShadowElementContext.SpriteVisual"/> as a child visual on <see cref="AttachedShadowElementContext.Element"/>
|
||||
/// </summary>
|
||||
|
|
Загрузка…
Ссылка в новой задаче