AnimateVectorDrawable - Creating an animation in code, rather than pure XML

Andy Pelton 1 Reputation point
2020-12-12T14:44:09.477+00:00

Hi.

I have been really struggling to find any good information on how to create an animation with a vector drawable in code in Xamarin.Android.

I have successfully got an animation working purely in with using XMLs.

with a animatehand.xml

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
  <objectAnimator 
    android:name="needle"
    android:propertyName="rotation"
    android:duration="2000"
    android:valueFrom="0"
    android:valueTo="1080"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
</set>

this is the clockanimation.xml

<?xml version="1.0" encoding="utf-8" ?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/fullclock">
  <target android:name="needle" android:animation="@anim/animatehand"/>
</animated-vector>

and the fullclock.xml

<?xml version="1.0" encoding="utf-8" ?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
      android:width="99.99845dp"
      android:height="99.99489dp"
      android:viewportWidth="26.457922"
      android:viewportHeight="26.456982">
  <group android:name="face"
    android:pivotX="13.22"
    android:pivotY="13.22">
    <path
      android:pathData="M13.229,13.2285m-13.2274,0a13.2269,13.2274 90.0155,1 1,26.4548 0a13.2269,13.2274 90.0155,1 1,-26.4548 0"
      android:strokeWidth="0.1"
      android:fillColor="#808080"
      android:strokeColor="#000000"/>
    <path
        android:pathData="M13.229,13.2285m-11.9513,0a11.9509,11.9513 90.0765,1 1,23.9026 0a11.9509,11.9513 90.0765,1 1,-23.9026 0"
        android:strokeAlpha="0.309237"
        android:strokeWidth="0.1"
        android:fillColor="#f2f2f2"
        android:strokeColor="#808080"/>
    <path
        android:pathData="m13.229,22.2148l0.9717,1.3374L13.229,24.8897l-0.9717,-1.3374z"
        android:strokeAlpha="0.309237"
        android:strokeWidth="0.0918885"
        android:fillColor="#f2f2f2"
        android:strokeColor="#000000"/>
  </group>
  <group
    android:name="needle"
    android:pivotX="13"
    android:pivotY="13">
    <path
        android:pathData="m13.229,23.5523 l1.1186,-10.7632h-2.2372zM13.232,13.4289a0.2073,0.2209 0,0 1,-0.003 0,0.2073 0.2209,0 0,1 -0.2073,-0.2209 0.2073,0.2209 0,0 1,0.2073 -0.2209,0.2073 0.2209,0 0,1 0.2073,0.2209 0.2073,0.2209 0,0 1,-0.2038 0.2209z"
        android:strokeWidth="0.0151836"
        android:fillColor="#333333"
        android:strokeColor="#000000"/>
  </group>
</vector>

This is all well and good for static animations, I need to be able to set the stopping point of the hand to a particular angle, so I need to be able to define the animation in the code.

I found a tutorial in Kotlin on doing this, which is what I have tried to implement in Xamarin (https://blog.jakelee.co.uk/programmatically-creating-and-scheduling-animations-for-android-drawable-layers-with-objectanimator/)

this is my layerlist.xml

<?xml version="1.0" encoding="utf-8" ?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/face"
      android:drawable="@drawable/clockface">
  </item>
  <item android:id="@+id/hand">
    <rotate android:drawable="@drawable/hand" />
  </item>
</layer-list>

I broke up the fullclock.xml in to two clockface.xml and hand.xml

clockface.xml

<?xml version="1.0" encoding="utf-8" ?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
      android:width="99.99845dp"
      android:height="99.99489dp"
      android:viewportWidth="26.457922"
      android:viewportHeight="26.456982">
  <group android:name="face"
    android:pivotX="13.22"
    android:pivotY="13.22">
    <path
      android:pathData="M13.229,13.2285m-13.2274,0a13.2269,13.2274 90.0155,1 1,26.4548 0a13.2269,13.2274 90.0155,1 1,-26.4548 0"
      android:strokeWidth="0.1"
      android:fillColor="#808080"
      android:strokeColor="#000000"/>
    <path
        android:pathData="M13.229,13.2285m-11.9513,0a11.9509,11.9513 90.0765,1 1,23.9026 0a11.9509,11.9513 90.0765,1 1,-23.9026 0"
        android:strokeAlpha="0.309237"
        android:strokeWidth="0.1"
        android:fillColor="#f2f2f2"
        android:strokeColor="#808080"/>
    <path
        android:pathData="m13.229,22.2148l0.9717,1.3374L13.229,24.8897l-0.9717,-1.3374z"
        android:strokeAlpha="0.309237"
        android:strokeWidth="0.0918885"
        android:fillColor="#f2f2f2"
        android:strokeColor="#000000"/>
  </group>
</vector>

hand.xml

<?xml version="1.0" encoding="utf-8" ?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
      android:width="99.99845dp"
      android:height="99.99489dp"
      android:viewportWidth="26.457922"
      android:viewportHeight="26.456982">
  <group
    android:name="needle"
    android:pivotX="13"
    android:pivotY="13">
    <path
        android:pathData="m13.229,23.5523 l1.1186,-10.7632h-2.2372zM13.232,13.4289a0.2073,0.2209 0,0 1,-0.003 0,0.2073 0.2209,0 0,1 -0.2073,-0.2209 0.2073,0.2209 0,0 1,0.2073 -0.2209,0.2073 0.2209,0 0,1 0.2073,0.2209 0.2073,0.2209 0,0 1,-0.2038 0.2209z"
        android:strokeWidth="0.0151836"
        android:fillColor="#333333"
        android:strokeColor="#000000"/>
  </group>
</vector>

in my activity_main.xml I have the following ImageView

<ImageView
        android:id="@+id/ClockTest"
        android:layout_width="100sp"
        android:layout_height="100sp"
        android:layout_column="0"
        android:gravity="center"
        android:scaleType="center"
        android:src="@drawable/layerlist"
        />

this is the code that I thought would create the animation, which is in my MainActivity.cs

AnimatorSet animset;   // class field

This is all in OnCreate

ImageView iva = FindViewById<ImageView>(Resource.Id.ClockTest);

var animationlayer = iva.Drawable as LayerDrawable;
var _ned = animationlayer?.FindDrawableByLayerId(Resource.Id.hand);

var rotate = ObjectAnimator.OfFloat(_ned, "rotation", 0f, 360f).SetDuration(3000);

animset = new AnimatorSet();
animset.SetInterpolator(new AccelerateDecelerateInterpolator());
animset.Play(rotate).With(ObjectAnimator.OfFloat(_ned, "pivotX", 13.22f)).With(ObjectAnimator.OfFloat(_ned, "pivotY", 13.22f));

In a Button Click event handler.

animset.Start() 

The animation does not play. I cannot see what I'm missing.
Can anyone offer any assistance or advice on this please. The subject of Animated Vector Drawables seems to be one people only do in XML for tutorial purposes.

Thanks
Andy

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,292 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Andy Pelton 1 Reputation point
    2020-12-15T21:05:00.537+00:00

    Hi,

    I have just tried that and the cast isn't valid. It throws a runtime exception, as does using AnimatedVectorDrawableCompat

    (Edit: the exceptions were due to the Resource.Drawable.Needle isn't an <animated-vector> but a <vector-drawable>. if I set up an XML with an <animated-vector> then it does load ok)

    If I set the animation purely in XML so an <animated-vector> which references a <vector-drawable> and targets a <set> with <objectanimator> then that will play.

    What I cannot get working is getting the Animation to play when I create an AnimatorSet in C# and assign it to a vector drawable. I need to be able to do this as the start and end angles are different each time.

    I have managed to get a partially working solution by animating the ImageView (and rotating that) rather than the vector-drawable.

    Is it not possible to animate the vector drawable directly in C# code?

    )

    This is the code I have, the set.Start() function is in a button click event handler

    ImageView image = FindViewById<ImageView>(Resource.Id.TestVect);
    
    //AnimatedVectorDrawableCompat avd = (AnimatedVectorDrawableCompat) this.GetDrawable(Resource.Drawable.Needle);   // Runtime Exception
     //AnimatedVectorDrawable avd = (AnimatedVectorDrawable)this.GetDrawable(Resource.Drawable.Needle);   // Runtime Exception
    
    AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.Create(ApplicationContext, Resource.Drawable.Needle);
    image.SetImageDrawable(avd);
    
    set = new AnimatorSet();
    set.Play(ObjectAnimator.OfFloat(avd, "rotation", 0f, 360f).SetDuration(3000));
    

    It does not when the button is pressed, the animation does not start.

    I cannot see why it does not play (unless it is not intended to work this way.) I have got an animation working by animating the ImageView rather than the drawable. Though it seem to me that the your should be able to animate the drawable.