How to programatically start and stop lottie animations in .NET Maui - c#

I would like to be able to play an animation in my .NET task project whenever I add a task.
Right now I'm able to use SkiaSharp.Extended.UI.Maui to load and animate a lottie file. I can also trigger an animation with a tapgesture. It plays the animation but when I press again it does not play the animation again or it keeps repeating the animation without stopping.
I tried using the property repeatcount. I tried setting it to -1(keep repeating), 0, 1
I have also tried using the duration, isvisible, and isenabled along with a timer. An event gets fired when the duration of the animation has passed and I hide the animation again. That works however only once. I can't get it to run again after that.
Xaml
<skia:SKLottieView
x:Name="animatedPlusIcon"
Source="WhiteCheck.json"
HeightRequest="150"
WidthRequest="150"
RepeatCount="0"
IsAnimationEnabled="True">
<skia:SKLottieView.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"/>
</skia:SKLottieView.GestureRecognizers>
</skia:SKLottieView>
Code behind
void TapGestureRecognizer_Tapped(System.Object sender, System.EventArgs e)
{
animatedPlusIcon.IsAnimationEnabled = true;
}

The only workaround i found is: Each time you create a new task, you could remove Lottie first and then add it again with custom settings. Such like the following:
In xaml, add it in a mystack
<StackLayout x:Name="mystack">
<skl:SKLottieView
....
</skl:SKLottieView>
</StackLayout>
Each time you create a task, first remove it and then add it:
(mystack as StackLayout).Clear();
SKLottieView myanimatedview = new SKLottieView();
var a = new SKFileLottieImageSource();
a.File = "dotnetbot.json";
myanimatedview.Source = a;
myanimatedview.RepeatCount = 3;
(mystack as StackLayout).Add(myanimatedview);
For more info, you could refer to SKLottieView. As we can see there is not many APIs for us as it is still a perview. You could also report an issue on Github: SKLottie Issues on Github
Hope it works for you.

Related

How to show tick marks in Xamarin forms slider control

I am using Xamarin forms Slider control in portable project.This is correctly showing minimum, maximum values.But i want to show tick marks in this control so that user can know number of steps.
This solutions supports Android as well as ios.
Below is the control I am using:-
<Slider x:Name="slider" HorizontalOptions="FillAndExpand"
Maximum="{Binding Maximum}"
Minimum="{Binding Minimum}" />
Can someone help me with links or code snippets ?
The slider which comes with Xamarin.Forms doesn't have any tick marks.
Basically you have three options:
1) Make a custom Xamarin Forms View with a grid that holds the slider and several BoxViews forming your tick marks and expose the needed values as BindableProperties.
Pro: Can be done quite fast, no need for custom renderers
Con: Not the most elegant solution
2) Make a custom renderer, rendering the slider the way you want it.
Pro: Better solution, since you only need to add your custom slider to the view
Con: You need to create a custom renderer for each target platform handling the drawing of the tick marks
3) Look for a suitable component or a nuGet Package that contains a slider suitable for your needs
So I finally ended with this solution for Android:-
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
base.OnElementChanged(e);
if (this.Control != null)
{
var slider = (MySlider)this.Element;
this.Control.TickMark = Resources.GetDrawable(Resource.Drawable.Icon);//works by setting whole slider image
//this.Control.SetThumb(Resources.GetDrawable(Resource.Drawable.Icon));//works by setting thumbnail movable image
Control.Max = (int)(slider.Maximum - slider.Minimum);//shows RHS value plus one
Control.Progress = (int)slider.Value;
}
}
You can try this for xamarin forms slider with ticks
https://xamarinexperience.blogspot.com/2019/03/how-to-add-tick-marks-on-sliders-in.html

UI not updating from EventAggregator

For a project I use a very simple progress overlay.
It just displays a small marquee progressbar and covers the screen.
So in my ShellView I have
<Border Grid.Row="0"
Grid.RowSpan="11"
Grid.Column="0"
Grid.ColumnSpan="11"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Panel.ZIndex="3"
Background="#9E000000"
BorderBrush="Black"
BorderThickness="3"
Visibility="{Binding IsProgressing,
Converter={StaticResource BoolToVisibility}}">
<!-- omitted progressbar, text etc -->
</Border>
And I have a very simple event, which just sets the Visibility (IsProgressing) binding and some text to show.
Whenever I want to have a progressbar, I just publish that event, like
_eventAggregator.Publish(new ProgressingChange(true, "Loading ..."));
This works very well so far, besides one case:
For the application I use events for navigation of my screens.
So there is another event which I publish, like:
_eventAggregator.Publish(new NavigationEvent(typeof(TargetViewModel)));
which just sets the target screen:
public void Handle(NavigationEvent navigate)
{
var target = _screenFactory.FromType(navigate.TargetScreen);
this.ActivateItem(target);
}
One of my Screens has lots of items and takes about 3 seconds to load.
So I wanted to show my Progress overlay while the screen is loading.
This is what does not work. Both the new Screen and the Overlay are
showing simultaneously when those events are combined.
This is:
_eventAggregator.Publish(new ProgressingChange(true, "Loading ..."));
_eventAggregator.Publish(new NavigationEvent(typeof(LongLoadingViewModel)));
For debugging reasons I did not deactivate the Progressing overlay to the
what is happening.
So the screen is loaded, nothing is shown on the screen for about 3 seconds, and then
both the Progress overlay and the new screen are shown.
I have tried
sleeping after publishing the ProgressingChange event
sleeping in both event handlers
running the publish of both events on separate threads
running only one of those publishes on a separate threads
tried to force UI update like this here force UI update
What am I missing?
What is happening here?
How can I get that to work?
-edit-
Here is the code of my Handler method:
public void Handle(ProgressingChange progressing)
{
this.IsProgressing= progressing.IsProgressing;
this.ProgressingText= progressing.ProgressingText;
NotifyOfPropertyChange(() => IsProgressing);
NotifyOfPropertyChange(() => ProgressingText);
// of course, there is Notify in the setters themselves, too
}
And I used this code from the source linked above to force UI updates,
but that did not work
void AllowUIToUpdate() {
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Render, new DispatcherOperationCallback(delegate(object parameter)
{
frame.Continue = false;
return null;
}), null);
Dispatcher.PushFrame(frame);
}
Also, I tried publishing in a critical section to force the first
publish to be executed before the second one, but that did not work either.
-edit2-
Code that at least shows the Progress overlay
_eventAggregator.Publish(new ProgressingChange(true, "Activating ..."));
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => _eventAggregator.Publish(new NavigationEvent(typeof(LongLoadingViewModel)))), DispatcherPriority.ContextIdle, null);
It might be a hack, but you could try to first wait for the Border (porgress bar) to be rendered before the navigation-event is published. To do this, you might be able to adapt the solution that is given here to execute some code when the UI-thread is no longer busy (see link for full explanation):
Dispatcher.BeginInvoke(new Action(() => Trace.WriteLine("DONE!", "Rendering")), DispatcherPriority.ContextIdle, null);
If this works, then at least you have something to start off making the code a bit cleaner.

Why does my Storyboard only run once?

I have a storyboard that I would like to run multiple times. Unfortunately, after it runs the first time, even when this code is hit, it doesn't animate again. It only animates the first time. I'm setting an initial value....even when I set the value before I run it again.
DoubleAnimationUsingKeyFrames animateTransformX = new DoubleAnimationUsingKeyFrames();
EasingDoubleKeyFrame initialTransformXKeyframe = new EasingDoubleKeyFrame();
initialTransformXKeyframe.KeyTime = TimeSpan.FromSeconds(0);
//Resetting the value before running it again.
initialTransformXKeyframe.Value = 0;
animateTransformX.KeyFrames.Add(initialTransformXKeyframe);
EasingDoubleKeyFrame animateTransformXKeyframe = new EasingDoubleKeyFrame();
animateTransformXKeyframe.KeyTime = TimeSpan.FromSeconds(0.5);
animateTransformXKeyframe.Value = aDynamicValueGoesHere;
animateTransformX.KeyFrames.Add(animateTransformXKeyframe);
Storyboard.SetTarget(animateTransformX, image);
Storyboard.SetTargetProperty(animateTransformX, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(animateTransformX);
myStoryboard.Begin(this);
I feel like it is very simple but for the life of me I can't understand why. Any help would be appreciated.
EDIT
The reason I was doing it from codebehind is because storyboards are freezable and I have had trouble in the past with dynamic values in them. For simplicity I didn't put that I was using a dynamic value in the code; I updated the code above to show I'm using a dynamic value.
I tried adding the storyboard as XAML inside a resource with the use of a dynamic value. It works fine (and replays) if a non-dynamic value is used but it only plays once (the first time) when a dynamic value is used.
So, if {Binding OffsetX} were replaced with 50, for example, this works fine, and repeatedly.OffsetX gets updated through a property that implements INotifyPropertyChanged. Even if it were the same, if it were never updated, I would think it should still animate, just to the same value it animated to before (like it does with 50). But that isn't the case; it just doesn't animate again. So now I'm really lost.
<Storyboard x:Key="InitialAnimate">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)" Storyboard.TargetName="StoryImage">
<EasingDoubleKeyFrame KeyTime="0:0:0.6" Value="{Binding OffsetX}"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
EDIT 2
I didn't fix the problem but I found a workaround that works for my particular situation. I was getting this error in my output window:
System.Windows.Media.Animation Warning: 6 : Unable to perform action because the specified Storyboard was never applied to this object for interactive control.; Action='Stop'; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='28715394'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='6255521'; TargetElement.Type='System.Windows.Controls.Grid'
I found a few StackOverflow answers regarding it, including how to find what Storyboard caused the issue and another unanswered question.
I am still not sure how to fix my original issue, but here's my workaround. Since I only needed the animation to run when you restarted (an event that happens in my viewmodel), I use an event raised in the viewmodel and consumed in the parent view to remove and then re-add the UserControl that contains the animation.
In that UserControl, I trigger the storyboard through a ControlStoryboardAction tied to a Loaded EventTrigger. This makes the animation run when it loads, and it only ever runs the animation once during the life of the UserControl. I am able to use the dynamic values in XAML. Here's how I'm removing and readding the view from it's parent:
Parent View:
<grid x:Name="imageHolder></grid>
Parent Codebehind:
private void CreateAnimations(object sender, EventArgs e)
{
imageHolder.Children.RemoveAt(0);
Views.Image image = new Image();
image.Name = "image";
imageHolder.Children.Add(image);
}
You can't change the Value property at runtime the way you're doing after the edit.
This is because Animations are freezable objects. There is more
information in the MSDN Documentation, but basically it means you
can't use binding because properties in the frozen object (i.e. the
animation) cannot change.
To get around this limitation, you will need to do some or all of the
work in code-behind.
See Why Storyboard value wouldn't get from a resource or binding?
So You probably want to go back to the codebehind method.
So as a final answer, what I would do is when I want to run it again:
Remove the previous storyboard
create a whole brand new Storyboard(or just the animation) and run that.

How I will get dropping media on mediaelement in wpf?

I am working with mediaelement in wpf, but the problem is I can't drop media on medaiaelement.
can anyone tell me the solution.the following code is .cs file code. I set allow drop property= true
private void mediaElement1_Drop(object sender, System.Windows.DragEventArgs e)
{
String[] FileName = (String[])e.Data.GetData(System.Windows.Forms.DataFormats.FileDrop, true);
if (FileName.Length > 0)
{
String VideoPath = FileName[0].ToString();
mediaElement1.Source = new Uri(VideoPath);
mediaElement1.Play();
}
e.Handled = true;
}
Tried it myself. Actually it will work after you play something there.
Here's the point: assume we have a Grid:
<Grid AllowDrop="True"></Grid>
It won't allow drop.
Now the following
<Grid Background="Transparent" AllowDrop="True"></Grid>
Will allow drop.
The first Grid doesn't have background at all, so actually there's no way to drop anything on it - there's no grid. And in second case there is grid's background even though we can't see it.
The same thing applies to MediaElement. Unfortunately it doesn't have any Background or Content property, so it won't allow drop until you start playing something there.
Solution is to handle drop on MediaElement parent container.
By the way, don't forget to set LoadedBehavior="Manual" for MediaElement so that it will play dropped file.
EDIT.
Here is explanation why MediaElement doesn't allow drop till any content was loaded in it.
Every WPF component is in fact composed of some other basic elements: Borders, Grids, ContentPresenters etc. So something inside the MediaElement handles drop. I cannot tell you what element it is because MediaElement's Template is not accessible. But it really doesn't matter what exactly is the element that handles drag and drop there. What does matter is that there's is nothing material in MediaElement's area until you load content on it - just like in case of my example with Grid at the beginning of this post. I mean that when you move mouse cursor over it's area there is nothing between cursor and MediaElement's container. Try to handle MouseDown event: result will be the same - it won't fire until you load any video. Why? Because there is nothing to raise event. Nothing cannot raise anything.
As I mentioned before there is great difference between Background="{x:Null}" and Background="Transparent": in first case there's no background brush, no background, but in second case there is one. Feel the difference.

Paragraph.BringIntoView() works only when focused

I am working on a text editor that is based on RichEditBox. I have implemented functionality "Go to line" which eventually resolves to
TextPointer.Paragraph.BringIntoView();
Along with this I also set the caret position.
What I found out is that BringIntoView only works when I click on the RichEditBox first (focus it). Otherwise it seems to get ignored. I can see that the caret position has been adjusted by the code around BringIntoView though.
Does anybody know what is the reason/nature of that problem? How can I overcome it?
Found a workaround for this, not sure if it will work in a pure WPF environment, in my case I'm running WPF inside a mainly Windows Forms solution using WPF UserControls where needed.
Instead of invoking BringIntoFocus() immediately, defer it to a later moment by adding it to a queue that gets handled by a timer. For example:
System.Windows.Forms.Timer DeferredActionTimer = new System.Windows.Forms.Timer() { Interval = 200 };
Queue<Action> DeferredActions = new Queue<Action>();
void DeferredActionTimer_Tick(object sender, EventArgs e) {
while(DeferredActions.Count > 0) {
Action act = DeferredActions.Dequeue();
act();
}
}
In your forms constructor, or in the OnLoad event add:
DeferredActionTimer.Tick += new EventHandler(DeferredActionTimer_Tick);
DeferredActionTimer.Enabled = true;
Finally, instead of calling TextPointer.Paragraph.BringIntoView(); directly, call it like this:
DeferredActions.Enqueue(() => TextPointer.Paragraph.BringIntoView());
Note that the Windows Forms timer kicks events off in the main thread (via the message pump loop). If you have to use another timer you need a bit of extra code. I'd recommend you to use System.Timers.Timer rather than the System.Threading.Timer (it's a little more thread-safe). You would also have to wrap the action in a Dispatcher.Invoke structure. In my case, the WinForms timer works like a charm.
Can't you just give the RichTextBox(?) focus first then, using Keyboard.Focus(richTextBox) or richTextBox.Focus()?

Categories