Laggy animation on Windows Phone - c#

Hi I have got a performance problem that I am unable to solve. Using Blend, I have created an animation showing and hiding a grid. It is invoked when the toggle switch button is checked, and it works. The problem is, that it works really laggy and invokes after a few seconds of delay. I test the application on Nokia Lumia 920. Could you please help me find out what is wrong?
Here is the code of animation, that has been created using blend:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Collapsing">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.5" />
</VisualStateGroup.Transitions>
<VisualState x:Name="Hidden">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)"
Storyboard.TargetName="CollapsingGrid">
<EasingDoubleKeyFrame KeyTime="0"
Value="95" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5"
Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)"
Storyboard.TargetName="anonymousOnLabel">
<EasingDoubleKeyFrame KeyTime="0:0:0.5"
Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:1"
Value="91" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)"
Storyboard.TargetName="SettingsSharePicTglBtn">
<EasingDoubleKeyFrame KeyTime="0"
Value="95" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5"
Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unhidden">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)"
Storyboard.TargetName="CollapsingGrid">
<EasingDoubleKeyFrame KeyTime="0:0:0.5"
Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:1"
Value="95">
<EasingDoubleKeyFrame.EasingFunction>
<CubicEase EasingMode="EaseOut" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)"
Storyboard.TargetName="anonymousOnLabel">
<EasingDoubleKeyFrame KeyTime="0"
Value="91" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5"
Value="0" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)"
Storyboard.TargetName="SettingsSharePicTglBtn">
<EasingDoubleKeyFrame KeyTime="0"
Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5"
Value="95" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
I invoke it the following way:
private void TglBtn_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
if ((bool)((ToggleSwitchButton)sender).IsChecked)
{
VisualStateManager.GoToState(this, "Unhidden", true);
}
else
{
VisualStateManager.GoToState(this, "Hidden", true);
}
}

I would recommend not animating the Width and Height properties. Each time these properties change, a full measure/arrange pass is performed on the visual tree, which is very expensive. Instead, you should try animating Scale on the grid's RenderTransform from 1.0 to 0.0.
Now, it's possible you're animating the height because you want things stacked under the grid to move up to fill the space taken up by the grid. In this case, you may need to perform some visual trickery, such as animating Translate on the things underneath the grid to move them up, then at the very end of the animation, as the last keyframe, you can reset the RenderTransforms and collapse the grid. Then, you will only suffer a single measure/arrange pass instead of one for each animation frame.
Finally, I would recommend reading up on Windows Phone performance considerations. This is a good document: http://bit.ly/15cExFz
And these two presentations are FANTASTIC. I can't recommend them enough. http://channel9.msdn.com/events/PDC/PDC10/CD03 & http://channel9.msdn.com/Events/Build/2012/3-048

I have had similar problem, but I have animated RotationX, RotationY properties of PlaneProjection using DoubleAnimation. The solution of problem have been found from this article. And it was adding of "magic" numbers ending in 0.1 – that tells the system it’s a ratio based on the width of the control, it's hack overload of a property but it gives real performance grow in some cases.
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="anonymousOnLabel">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0.1" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="91.1" />
</DoubleAnimationUsingKeyFrames>
And of course it not good idea to animate Width and Height properties directly because it will be Dependant Animation and will have performance impact.

Related

Animating background color of CommandBar in UWP crashes

I've been working at animating the background color and opacity of CommandBar over an image for it to become more opaque when the mouse cursor is moved.
The XAML code I'm using for the animation is as follows:
<Storyboard x:Name="topbagroundfadeinout">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="topcmdbar" Storyboard.TargetProperty="Background.Color">
<EasingDoubleKeyFrame KeyTime="0" Value="Transparent" />
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="Black" />
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="Black" />
<EasingDoubleKeyFrame KeyTime="0:0:6" Value="Transparent" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
And the C# event handler looks like this:
private void raiseopacity(object s, PointerRoutedEventArgs e)
{
if (topcmdbarfadeinout.GetCurrentState()!=ClockState.Active)
{
topcmdbarfadeinout.Begin();
topbagroundfadeinout.Begin();
}
}
If I include only the fade in/out of the opacity, everything works fine. However, as soon as I uncomment the XAML code for the background color animation, the program compiles but execution crashes with a weird error
Failed to create a 'Windows.Foundation.Double' from the text 'Transparent'. [Line: 0 Position: 0]
Anyone knows what's wrong? Or anyone has a better way to do this?
DoubleAnimation is meant for double data type. Instead in this case you are animating Windows.UI.Color, which has a dedicated ColorAnimation and its EasingColorKeyFrame elements.
Also, you will probably need to modify the Storyboard.TargetProperty to (CommandBar.Background).(SolidColorBrush.Color), as it is a complex property.
<Storyboard x:Name="topbagroundfadeinout">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="topcmdbar"
Storyboard.TargetProperty="(CommandBar.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="Transparent" />
<EasingColorKeyFrame KeyTime="0:0:1" Value="Black" />
<EasingColorKeyFrame KeyTime="0:0:4" Value="Black" />
<EasingColorKeyFrame KeyTime="0:0:6" Value="Transparent" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
Also note, that Transparent color is actually a white color with 0 opacity. This can prove to be a problem, as the animation will essentially fade from white to black instead of from invisible black to black. I have written more about this in on my blog.
In your case, you may be better off specifying the transparent black instead of Transparent manually:
<Storyboard x:Name="topbagroundfadeinout">
<ColorAnimationUsingKeyFrames Storyboard.TargetName="topcmdbar"
Storyboard.TargetProperty="(CommandBar.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="#00000000" />
<EasingColorKeyFrame KeyTime="0:0:1" Value="Black" />
<EasingColorKeyFrame KeyTime="0:0:4" Value="Black" />
<EasingColorKeyFrame KeyTime="0:0:6" Value="#00000000" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
Although the difference might not be noticeable in all situations, you may try both solutions to see which better fits with your intended use.

Pause/Resume WPF storyboard

I've read about a dozen articles on how to pause and resume an WPF storyboard, but I just can't get it to work.
Here's my problem: I have a User control with a storyboard. The storyboard looks like this:
<UserControl.Resources>
<Storyboard x:Key="TheStoryboard" RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="Arc1">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="90"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="180"/>
<EasingDoubleKeyFrame KeyTime="0:0:6" Value="270"/>
<EasingDoubleKeyFrame KeyTime="0:0:8" Value="360"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
Simple enough, it makes an ark spin forever.
Now in the code behind I have a dependency property which is bound to a Boolean value indicating when the animation should be spinning or stopped. This triggers a method which should, in theory, pause or resume the animation.
It looks something like this:
private void SetStoryBoardActivity(bool play)
{
var storyboard = (Storyboard)this.Resources["TheStoryboard"];
if (play)
{
storyboard.Resume();
}
else
{
storyboard.Pause();
}
}
The execution path enters the method as intended, however the animation doesn't stop when calling Pause(); I've tried
storyboard.Stop();
storyboard.Stop(this);
storyboard.Stop(this.Arc1);
storyboard.Freeze();
storyboard.Pause();
storyboard.Pause(this);
storyboard.Pause(this.Arc1);
but nothing seems to work. Does anyone know what I'm doing wrong?
So the Answer in my case seems to be visual states.
I manged to make this work in the following manner:
I moved the storyboard to a visual state manager like this:
<Grid x:Name="LayoutRoot">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Animation">
<VisualState x:Name="On">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="Arc1">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:2" Value="90"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="180"/>
<EasingDoubleKeyFrame KeyTime="0:0:6" Value="270"/>
<EasingDoubleKeyFrame KeyTime="0:0:8" Value="360"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Off"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ed:Arc x:Name="Arc1" ArcThickness="15" ArcThicknessUnit="Pixel" StartAngle="30" EndAngle="150" Fill="#C0375E77" HorizontalAlignment="Center" Height="200" Width="200" Margin="0,0,0,0" Stretch="None" Stroke="#FF204050" StrokeThickness="2" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5" >
<ed:Arc.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</ed:Arc.RenderTransform>
</ed:Arc>
</Grid>
Please note that the visual state manager is not placed in the control's resources, but in the control's main container.
Where ed is:
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
And the SetStoryBoardActivity method becomes:
private void SetStoryBoardActivity()
{
VisualStateManager.GoToState(this, this.AnimationActive ? "On" : "Off", true);
}
Where this.AnimationActive is the dependency property mentioned in my question.
How about trying out with:
Storyboard storyboard = (Storyboard)this.Resources["TheStoryboard"];
private void BeginStoryBoard()
{
storyboard.begin(this, true);
}
private void SetStoryBoardActivity(bool play)
{
if (play)
{
storyboard.Resume(this);
}
else
{
storyboard.Pause(this);
}
}

C# - Windows Phone 8, how can I start animation or storyboard first before I changed the visibility of an Element

I have a question, how can I make an animation running first before the visibility changed on an XAML element?
The situation should be like this.
<Grid Name=Header Visiblity="visible">
<Grid.Resources>
<Storyboard x:Name="HeaderGridUp">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="Height"
Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame Value="30" KeyTime="00:00:02">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="HeaderGridDown">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="Height"
Storyboard.TargetName="HeaderGrid">
<EasingDoubleKeyFrame Value="30" KeyTime="00:00:02">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseIn"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
</Grid>
<Grid Name=Items Visiblity="collapsed">
<Grid.Resources>
<Storyboard x:Name="ItemsGridUp">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="Height"
Storyboard.TargetName="ItemsGrid">
<EasingDoubleKeyFrame Value="30" KeyTime="00:00:02">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseOut"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="ItemsGridDown">
<DoubleAnimationUsingKeyFrames
Storyboard.TargetProperty="Height"
Storyboard.TargetName="ItemsGrid">
<EasingDoubleKeyFrame Value="30" KeyTime="00:00:02">
<EasingDoubleKeyFrame.EasingFunction>
<ExponentialEase EasingMode="EaseIn"/>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
</Grid>
In my case, I would like an animation that move like Fade in and Fade out, that's why I make two storyboard each grid. First thing that I want my app run is.
The current state of HeaderGrid is visible, and when I touch a button, this grid will be going up and the visibility changed to collapsed. After that, the ItemsGrid will be going up change it's visibility and then going up replacing the HeaderGrid original place. Then I do the revert of the exact thing.
Can I possible doing that? and I would like to do it in code behind.
Any answer will be appreciated. :)
Thank You.
Regards,
Budi Prasetyo
where you were supposed to add the visibility code in c#, u start a dispatchertimer with interval equal to your animations length and when the timer ticks change the visibility
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0,0,x); //length of your animation in seconds
in main()
{
timer.Tick += timer_Tick;
}
and the event handler for the tick
void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
element.Visibility = Visibility.Visible; //or Collapsed
}
and where you're supposed to change the visibility of the element, just start the timer
timer.Start();
hope it helps :) and not too late ;)
Once you set the Visibility of any element to Collapsed, you will not be able to see any animation, because the element is already collapsed. I just had this issue myself, wanting to ease out when items disappear from a list. My solution was to add another property to my ViewModel, IsRemoving, then set this to true before removing the element. You can then bind the animation to the changing of IsRemoving property to run the animation before setting Visibility on the element.
You can do the trick like this, using some async/await magic. This will delay for 1 second before actually removing the item. Because the delay is awaited, UI will not hang.
public async void RemoveItem(SomeItem item){
IsRemoving = true;
await Task.Delay(1000);
<Your collection>.Remove(item);
IsRemoving = false;
}
EDIT: IN XAML:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsRemoving}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="border">
<EasingDoubleKeyFrame KeyTime="0" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<QuinticEase EasingMode="EaseOut"></QuinticEase>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<QuinticEase EasingMode="EaseOut"></QuinticEase>
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
Hope it helps :-)

Fire the .xaml storyboard on button click

In my Silverlight application, I made a stroyboard manually through Blend.
<UserControl.Resources>
<Storyboard x:Name="Swivel">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)" Storyboard.TargetName="Menus">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="-90"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="Menus">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
I want to fire this storyboard in button click. Is there any way to do that without writing the storyboard in .cs and then storyboard.Start() etc.?
You want to tie a ControlStoryboardAction to the button's Click event. Here's a quick guide on how to add the action in Blend. If you don't see the ControlStoryboardAction in your assets panel, just add a reference to Microsoft.Expression.Interactions.dll. You can find this in your Blend SDK folder (on my Win7 x64 system, that's in the C:\Program Files (x86)\Microsoft SDKs\Expression\Blend\Silverlight\v5.0\Libraries folder.
Look into event triggers see StoryBoard on MSDN.

Blend Interaction Behaviour gives "points to immutable instance" error

I have a UserControl that is a base class for other user controls, that are shown in a "modal view".
I want to have all user controls fading in when shown and fading out when closed. I also want the user to be able to move the controls around. My constructor looks like this:
var tg = new TransformGroup();
tg.Children.Add(new ScaleTransform());
RenderTransform = tg;
var behaviors = Interaction.GetBehaviors(this);
behaviors.Add(new TranslateZoomRotateBehavior());
Loaded += ModalDialogBase_Loaded;
And the ModalDialogBase_Loaded method looks like this:
private void ModalDialogBase_Loaded(object sender, RoutedEventArgs e)
{
var fadeInStoryboard = (Storyboard)TryFindResource("modalDialogFadeIn");
fadeInStoryboard.Begin(this);
}
When I press a Close-button on the control this method is called:
protected virtual void Close()
{
var fadeOutStoryboard = (Storyboard)TryFindResource("modalDialogFadeOut");
fadeOutStoryboard = fadeOutStoryboard.Clone();
fadeOutStoryboard.Completed += delegate
{
RaiseEvent(new RoutedEventArgs(ClosedEvent));
};
fadeOutStoryboard.Begin(this);
}
The storyboard for fading out look like this:
<Storyboard x:Key="modalDialogFadeOut">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<BackEase EasingMode="EaseIn" Amplitude="0.3" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<BackEase EasingMode="EaseIn" Amplitude="0.3" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="1">
<EasingDoubleKeyFrame.EasingFunction>
<BackEase EasingMode="EaseIn" Amplitude="0.3" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0">
<EasingDoubleKeyFrame.EasingFunction>
<BackEase EasingMode="EaseIn" Amplitude="0.3" />
</EasingDoubleKeyFrame.EasingFunction>
</EasingDoubleKeyFrame>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="{x:Null}">
<EasingDoubleKeyFrame KeyTime="0" Value="1" />
<EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
If the user control is shown, and the user does NOT move it around on the screen, everything works fine. However, if the user DOES move the control around, I get the following error when the modalDialogFadeOut storyboard is started:
'Children' property value in the path '(0).(1)[0].(2)' points to immutable instance of 'System.Windows.Media.TransformCollection'.
How can I fix this?
The issue is that the TranslateZoomRotateBehavior is replacing your ScaleTransform with a MatrixTransform, causing the first two animations in your Storyboard to target a property that no longer exists.
Since you can't animate the values of a Matrix to get the fadeout effect, I would use an additional container control. The outermost for the behavior and then an inner one to fade out visually.

Categories