Reputation: 53
I am using UWP and working with the Composition API to programmatically slide items around on the screen and I have found that when the application first starts that the initial slide for many of the elements are not performing correctly. Upon further inspection I have found that the ElementVisual's Offset properties that I use for computing and setting both the initial and destination positions of the animation are sometimes coming up with all zero values for the X,Y,Z values on the very first animation. Best I can tell all animations of that element from that point forward are working correctly as long as the app keeps running.
The app I am working on is more complex and interesting, but I have created a simplified test application to demonstrate the problem I am encountering.
From a new blank UWP project I add the following GridView and Button to the root grid of the page:
<GridView x:Name="TestGridView" HorizontalAlignment="Center" VerticalAlignment="Center">
<GridViewItem >
<Border Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 1" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 2" />
</Grid>
</Border>
</GridViewItem>
<GridViewItem>
<Border Width="125" Height="125">
<Grid>
<TextBlock Text="Content String 3"/>
</Grid>
</Border>
</GridViewItem>
</GridView>
<Button Height="125" Width="125" Click="Button_Click"/>
In the codebehind file I add the following additional using statements, a variable declaration and this event handler:
using System.Numerics;
using Windows.UI.Composition;
using Windows.UI.Xaml.Hosting;
using System.Diagnostics;
Compositor compositor;
private void Button_Click(object sender, RoutedEventArgs e)
{
foreach (var element in TestGridView.ItemsPanelRoot.Children)
{
var slideVisual = ElementCompositionPreview.GetElementVisual(element);
Debug.WriteLine("Element Offset: " + slideVisual.Offset);
var slideAnimation = compositor.CreateVector3KeyFrameAnimation();
slideAnimation.InsertKeyFrame(0f, slideVisual.Offset);
slideAnimation.InsertKeyFrame(1f, new Vector3(slideVisual.Offset.X, slideVisual.Offset.Y + 20f, 0f));
slideAnimation.Duration = TimeSpan.FromMilliseconds(800);
slideVisual.StartAnimation(nameof(slideVisual.Offset), slideAnimation);
}
}
I also initialize the compositor in the MainPage() constructor:
public MainPage()
{
this.InitializeComponent();
compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
}
I then run the project in Debug mode from Visual Studio. Pressing the button produces the following output in my Immediate Window (separator lines added in editing for clarity)
Element Offset: <0, 0, 0>
Element Offset: <0, 0, 0>
Element Offset: <0, 0, 0>
Element Offset: <0, 20, 0>
Element Offset: <129, 0, 0>
Element Offset: <258, 0, 0>
Element Offset: <0, 40, 0>
Element Offset: <129, 20, 0>
Element Offset: <258, 20, 0>
Element Offset: <0, 60, 0>
Element Offset: <129, 40, 0>
Element Offset: <258, 40, 0>
Note that the first of the three elements does animate on the first slide, but the other two do not and that their Offset values are all zero values.
On each slide there after all three elements animate from their previous position as they should, although the side effects from the first slide animation linger on.
I have tried unsuccessfully to find a way to force the elements in our application to update their Offset values so that the initial slide animation for each element works correctly including doing things like inserting a dummy WarmUp animation just before the actual intended animation that very quickly tells the element to either move from its current position to the same position, or a very slightly modified start position to its actual current position.
var warmUpAnimation = compositor.CreateVector3KeyFrameAnimation();
warmUpAnimation.InsertKeyFrame(0.0f, new Vector3(slideVisual.Offset.X + 1, slideVisual.Offset.Y, slideVisual.Offset.Z));
warmUpAnimation.InsertKeyFrame(1f, slideVisual.Offset);
warmUpAnimation.Duration = TimeSpan.FromMilliseconds(1);
I am guessing this is a bug in the Windows Composition API but I am hoping someone can suggest a simple and effective work around that will force the ElementVisual to correctly initialize after load.
Upvotes: 1
Views: 804
Reputation: 1460
Two things:
1 - Generally, DON'T use Offset
to animate the position of Visual's that are created from a FrameworkElement
. Use the manual Translation
property instead.
This can be done by calling the following on each element, preferably in their Loaded
events
ElementCompositionPreview.SetIsTranslationEnabled(myFrameworkElement, true);
You would then change the animation to something like
var warmUpAnimation = compositor.CreateVector3KeyFrameAnimation();
warmUpAnimation.InsertExpressionKeyFrame(0.0f, "Vector3(this.Target.Translation.X + 100f, this.Target.Translation.Y, 0f)");
warmUpAnimation.InsertKeyFrame(1f, new Vector3(0f));
warmUpAnimation.Duration = TimeSpan.FromMilliseconds(1);
slideVisual.StartAnimation("Translation", warmUpAnimation);
This used to be very evident in the old Composition animation documentation, but they seem to have largely wiped away its mentions from the recent updates. In general the composition documentation isn't very good at all. I literally can't even find the page right now that explains why you should use Translation over Offset (which is to do with XAML properties overwriting Composition Properties).
Essentially Offset
is the absolute X.Y. position of an element relative to its parent set by the XAML rendering engine, and Translation
is akin to a RenderTransform
on top of that - the different is XAML will overwrite Composition's Offset
with its values with no care as to what you put in at the Composition level, but it has no "knowledge" of the Translation
property so it never overwrites it. Translation
always applies as an additive to the current Offset. It also only works for Visuals created from FrameworkElements - it's unneeded for any other type of CompositionVisual as their offsets don't get changed by XAML.
2 - It may help to call ElementCompositionPreview.GetElementVisual()
on each target element you know you're going to animate at some point in their loaded events - note it has to be directly on the element that is going to be animated. This ensures the handoff visual is created and fully ready by the time you need it. (Created is asynchronous due to the nature of how Composition APIs works with the system compositor).
Upvotes: 1
Reputation: 53
I keep running into this same problem when using the Composition api whenever I use it in UWP applications. For whatever reason initial calls to GetElementVisual for UIElements often do not provide correct return results.
I have resorted to running a test in a loop before using visuals for the first time.
As a simple example, if you add the following function to the above sample:
private bool IsCompositionVisualReady(UIElement element)
{
var visual = ElementCompositionPreview.GetElementVisual(element);
if (visual.Size.X == 0 && visual.Size.Y == 0)
{
return false;
}
return true;
}
And then insert the following while loop at the beginning of the foreach segment in the Button_Click routine and add "async" to the click handler:
private async void Button_Click(object sender, RoutedEventArgs e)
{
foreach (var element in TestGridView.ItemsPanelRoot.Children)
{
while (!IsCompositionVisualReady(element))
{
await Task.Delay(TimeSpan.FromMilliseconds(1));
}
var slideVisual = ElementCompositionPreview.GetElementVisual(element);
The visuals animate more or less as they should. However, there does still seem to be a slight delay on the last two elements on the first pass of animations.
Upvotes: 0