To create an animated loader UI control in my app (Xamarin AndroidX using Mvx and FFimageLoading) I created a custom control that inherits from MvxCachedImageView
.
Then I apply my animation as follow:
public class LoaderView : MvxCachedImageView
{
protected LoaderView(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{
Create();
}
public LoaderView(Context context)
: base(context)
{
Create();
}
public LoaderView(Context context, IAttributeSet attrs)
: base(context, attrs)
{
Create();
}
private void Create()
{
if (Context == null)
return;
ImageService.Instance.LoadCompiledResource("loader_indicator")
.Into(this);
ApplyRotation();
StartAnimation(Animation);
}
private void ApplyRotation()
{
var rotation = new RotateAnimation(
0,
360,
Dimension.RelativeToSelf,
0.5f,
Dimension.RelativeToSelf,
0.5f)
{
Duration = 1200,
Interpolator = new DecelerateInterpolator(1.25f),
RepeatCount = Animation.Infinite
};
Animation = rotation;
}
}
Note: loader_indicator
is a gif file
This control has been working properly until I tried to bind its visibility property to the 'classic' IsLoading
BaseViewModel's property as follow:
var set = CreateBindingSet();
set.Bind(loader).For(v => v.Visibility).To(vm => vm.IsLoading)
.WithConversion<MvxVisibilityValueConverter>();
set.Apply();
Executing the app as above, the Visibility
binding and the gif animation work randomly: sometimes they do, sometimes they don't; sometimes instead the RotateAnimation
is working but against the parent element and not itself - with the bad scenario where you have the icon going around into the page..
Doing different tests I've understood that if I remove the ApplyRotation
and StartAnimation
the control is displayed and binded
correctly.
This seems to be a threading issue somewhere. Does anyone ever seen and (possibly) fixed this issue?
EDIT: the Cheesebaron anwser gave me a right hint, so I changed the LoaderView
implementation as follow and it actually fixed the issue:
public class LoaderView : MvxCachedImageView
{
private Animation _animation;
protected LoaderView(IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
{ }
public LoaderView(Context context)
: base(context)
{ }
public LoaderView(Context context, IAttributeSet attrs)
: base(context, attrs)
{ }
protected override void OnVisibilityChanged(View changedView, ViewStates visibility)
{
base.OnVisibilityChanged(changedView, visibility);
if (visibility == ViewStates.Visible)
StartAnimation(_animation);
else
ClearAnimation();
}
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
Create();
}
private void Create()
{
if (Context == null)
return;
ImageService.Instance.LoadCompiledResource("loader_indicator")
.Into(this);
_animation = ApplyRotation();
}
private static RotateAnimation ApplyRotation() =>
new RotateAnimation(
0,
360,
Dimension.RelativeToSelf,
0.5f,
Dimension.RelativeToSelf,
0.5f)
{
Duration = 1200,
Interpolator = new DecelerateInterpolator(1.25f),
RepeatCount = Animation.Infinite
};
}
You can try change your binding expression to:
set.Bind(loader).For(v => v.BindVisible()).To(vm => vm.IsLoading);
However, you are applying the animation when the element is instantiated, not when visibility is set. So the animation might be running, while the element is not visible or vise-versa. You might want to start the animation at a more appropriate point of time.