12.1: Creating property animations

Contents:

The Property Animation system allows you to animate almost any property of an object. You can define an animation to change any object property (a field in an object) over time, regardless of whether the change draws to the screen or not. A property animation changes a property's value over a specified length of time. For example, you can animate a circle to grow bigger by increasing its radius.

With the property animation system, you assign animators to the properties that you want to animate, such as color, position, or size. You also define aspects of the animation such as interpolation. For example, you would create an animator for the radius of a circle whose size you want to change.

The property animation system lets you define the following characteristics of an animation:

  • Duration: You can specify the duration of an animation. The default length is 300 milliseconds.
  • Time interpolation: You can specify how the values for the property are calculated as a function of the animation's current elapsed time. You can choose from provided interpolators or create your own.
  • Repeat count and behavior: You can specify whether or not to have an animation repeat when it reaches the end of a duration, and how many times to repeat the animation. You can also specify whether you want the animation to play back in reverse. Setting it to reverse plays the animation forwards then backwards repeatedly, until the number of repeats is reached.
  • Animator sets: You can group animations into logical sets that play together or sequentially or after specified delays.
  • Frame-refresh delay: You can specify how often to refresh frames of your animation. The default is set to refresh every 10 ms, but the speed in which your application can refresh frames ultimately depends on how busy the system is overall and how fast the system can service the underlying timer.

What you should already KNOW

You should be able to:

  • Create and run apps in Android Studio.
  • Create a custom View object.
  • Draw to the screen using a Canvas object.

What you will LEARN

You will learn how to:

  • Create a custom View that includes a radius property.
  • Create simple radius-based animations using ObjectAnimator objects.
  • Combine and sequence animations using an AnimatorSet object.

What you will DO

  • Create an app with a single activity and a PulseAnimationView custom view.
  • Add an mRadius property to the PulseAnimationView class and a setRadius() "setter" method for that custom property. The Animator instance will call the setRadius() method to change the size of mRadius during the animation.
  • Override the onDraw() method to draw a circle of size mRadius. The circle is created at the place where the user taps.
  • Create three Animator instances for the radius property.
  • Use an AnimatorSet to play the animations in sequence after the user taps the screen.

App overview

The PropertyAnimation app opens with a white screen. When the user taps the screen, an animation plays. The animation draws an expanding circle, then pauses. Then the animation draws a shrinking circle that changes color. Finally, the animation draws the same circle again, reversing without pausing. The screenshot below shows a snapshot of the PropertyAnimation app during animation.  Snapshot of the PropertyAnimation app during animation

Task 1. Creating the PropertyAnimation app

Note that in this advanced practical, you are expected to create member variables, import classes, and extract values as needed.

1.1 Create an app with one activity

Create an app that uses the Empty Activity template. Make sure that Backwards Compatibility and Generate Layout File are enabled.

1.2 Create the custom view to animate

  1. Create a new custom view class called PulseAnimationView that extends View.
     public class PulseAnimationView extends View {}
    
  2. Add two two required constructors. When you create your custom view from code exclusively, you only need the first constructor. When you inflate your custom view from XML, the system calls the second constructor and if it's missing, you get an error.

     public PulseAnimationView(Context context) {this(context, null);}
    
     public PulseAnimationView(Context context, AttributeSet attrs) {
        super(context, attrs);
     }
    
  3. In activity_main.xml, remove the TextView and add a PulseAnimationView that matches the size of the parent.
     <com.example.android.propertyanimation.PulseAnimationView
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    
  4. Run your app. It shows a white screen and the name of the app.

1.3 Implement the method to set the radius

This app uses a property animator, which changes a property's value over a specified length of time. You will change the radius property of the PulseAnimationView to animate a circle by changing its size.

The property animator needs to be able to change the property that will be animated. It does this through a "setter" method for the property. In order for the animator to find and use the setter, the following conditions need to be met:

  • If the class whose property is being animated does not provide a setter property, you have to create one. For the PulseAnimationView, you will create a member variable for the radius and a setRadius() method to set the variable's value.
  • The property setter's name needs to be of the form set PropertyName ().The PropertyName can be any valid string. When you create the animator, you will pass the PropertyName as string to the constructor.
  • To cause the object to be redrawn after the property changes, you must call the invalidate() method. Calling invalidate() tells the system that something about the view has changed, and the system measures, lays out, and redraw the views.

  • Create a member variable for the radius in PulseAnimationView.

     private float mRadius;
    
  • Create a public void setRadius() method that takes a float radius as its argument. Set mRadius to the passed-in radius and call the invalidate() method.

     public void setRadius(float radius) {
        mRadius = radius;
        invalidate();
     }
    

    To make the animation more interesting, use the radius to affect other aspects of the animation. For example, you could play a sound as the circle grows, and the sound could change as a function of the radius. For the PropertyAnimation app, in addition to changing the size, you are going to change the color of the circle as a function of the radius.

  • Create and initialize a mPaint member variable.

     private final Paint mPaint = new Paint();
    
  • Create a constant COLOR_ADJUSTER and set it to 5. You will use this constant in the next step.
     private static final int COLOR_ADJUSTER = 5;
    
  • Inside the setRadius() method, after setting mRadius and before calling the invalidate() method, change the color of the mPaint variable. Colors are integers and can thus be used in integer operations. You can change the value of the COLOR_ADJUSTER constant to see how it affects the color of the circle. You can also use a more sophisticated color function, if you want to.
     mPaint.setColor(Color.GREEN + (int) radius / COLOR_ADJUSTER);
    

1.4 Add code to respond to touch events

In the PropertyAnimation app, animation is initiated by a user touch, and the animation originates at the location of the touch event.

  1. In the custom view class, create private float member variables mX and mY to store the event coordinates.
     private float mX;
     private float mY;
    
  2. Override the onTouchEvent() method to get the event coordinates and store them in the mX and mY variables.
     @Override
     public boolean onTouchEvent(MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            mX = event.getX();
            mY = event.getY();
        }
        return super.onTouchEvent(event);
     }
    

1.5 Add the animation code

The animation is performed by an Animator object that, once started, changes the value of a property from a starting value towards an end value over a given duration.

  1. Create class constants for the animation duration and for a delay before the animation starts. You can change the values of these constants later and explore how that affects the appearance of the animation. The time is in milliseconds.
     private static final int ANIMATION_DURATION = 4000;
     private static final long ANIMATION_DELAY = 1000;
    
  2. Override the onSizeChanged() method.

     @Override
     public void onSizeChanged(int w, int h, int oldw, int oldh) {}
    

    Inside the onSizeChanged() method, you will create three ObjectAnimator objects and one AnimatorSet object. You cannot create them in the onCreate() method, because the views have not been inflated, and so the call to getWidth() used below would not return a valid value.

  3. In the onSizeChanged() method, create an ObjectAnimator called growAnimator. You need to pass in a reference to the object that is being animated (this), the name of the property that is to be animated ("radius"), the starting value (0), and the ending value (getWidth()). In this case, the ending value is the width of the PulseAnimationView.

     ObjectAnimator growAnimator = ObjectAnimator.ofFloat(this,
            "radius", 0, getWidth());
    
  4. Set the duration of the animation in milliseconds.
     growAnimator.setDuration(ANIMATION_DURATION);
    
  5. Choose an interpolator for the animation. The interpolator affects the rate of change; that is, the interpolator affects how the animated property changes from its starting value to its ending value.

  6. With a LinearInterpolator, the rate of change is constant; that is, the value changes by the same amount for every animation step. For example, if the starting radius value were 0 and your ending value were 10, every step of the animation might increase the radius by 2. For example, 0 > 2 > 4 > 8 > 10.

  7. With an AccelerateDecelerateInterpolator, the rate of change starts and ends slowly but accelerates through the middle. The animation starts with increasingly larger steps and then ends with decreasingly smaller steps. For example 0 > 2 > 5 > 10 > 20 > 30 > 35 > 38 > 40.
  8. You can create custom interpolators, too. See the BaseInterpolator class for a list of many of the available interpolators, and try some of them with this app.
    growAnimator.setInterpolator(new LinearInterpolator());
    
  9. Create a second ObjectAnimator object named shrinkAnimator. Use the same parameters as for growAnimator but swap the start and end values.
     ObjectAnimator shrinkAnimator = ObjectAnimator.ofFloat(this,
            "radius", getWidth(), 0);
    
  10. Set the duration to ANIMATION_DURATION and use a LinearOutSlowInInterpolator. This is a more complex interpolator and you can check the documentation for details.
     shrinkAnimator.setDuration(ANIMATION_DURATION);
     shrinkAnimator.setInterpolator(new LinearOutSlowInInterpolator());
    
  11. Add a starting delay. When the animation is started, it will wait for the specified delay before it runs.
     shrinkAnimator.setStartDelay(ANIMATION_DELAY);
    
  12. Still in onSizeChanged(), create a third ObjectAnimator instance called repeatAnimator.
     ObjectAnimator repeatAnimator = ObjectAnimator.ofFloat(this,
            "radius", 0, getWidth());
     repeatAnimator.setStartDelay(ANIMATION_DELAY);
     repeatAnimator.setDuration(ANIMATION_DURATION);
    
  13. Add a repeat count to repeatAnimator. A repeat count of 0 is the default. With a repeat count of 0, the animation plays once and does not repeat. With a repeat count of 1, the animation plays twice.
    repeatAnimator.setRepeatCount(1);
    
  14. Set the repeat mode to REVERSE. In this mode, every time the animation plays, it reverses the beginning and end values. (The other possible value, which is the default, is RESTART.)
    repeatAnimator.setRepeatMode(ValueAnimator.REVERSE);
    
  15. Create a private AnimatorSet member variable called mPulseAnimatorSet and initialize the variable with an AnimatorSet.

    private AnimatorSet mPulseAnimatorSet = new AnimatorSet();
    

    An AnimatorSet allows you to combine several animations and to control in what order they are played. You can have several animations play at the same time or in a specified sequence. AnimatorSet objects can contain other AnimatorSet objects. See the Property Animation guide for all the cool things you can do with animator sets.

  16. The following AnimatorSet is very simple and specifies that the growAnimator should play before the shrinkAnimator, followed by the repeatAnimator. Add it after you have created the animators.

    mPulseAnimatorSet.play(growAnimator).before(shrinkAnimator);
    mPulseAnimatorSet.play(repeatAnimator).after(shrinkAnimator);
    
  17. If you run your app now, you still only see the white screen.

1.6 Add code to draw the circle

The Animator that you implemented does not draw anything. The actual drawing of the circle must be done in the onDraw() method, which is executed after the view has been invalidated, which happens in the setRadius() method.

  1. Override the onDraw() method to draw a circle at the mX, mY coordinates.
  2. Give the circle a radius of mRadius and set the color to mPaint.
     @Override
     protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mX, mY, mRadius, mPaint);
     }
    

1.7 Play the animation

  1. In the onTouchEvent() method, add code to play the animation if there is an ACTION_DOWN event. If an animation is running, cancel it. This resets the mPulseAnimatorSet and its animations to the starting values.
     if(mPulseAnimatorSet != null && mPulseAnimatorSet.isRunning()) {
        mPulseAnimatorSet.cancel();
     }
     mPulseAnimatorSet.start();
    
  2. Run your app and tap the white screen to see the animations play. Have some fun and experiment with the animation!
This example app creates and runs animations from the Java code because it uses data that is not available until the view has been drawn. You can also define animators and animator sets in XML. See the Property Animation guide and the Animations concept for instructions and code examples.

Solution code

Android Studio project: PropertyAnimation.

Coding challenge

Write an app that demonstrates the use of the physics-based animation from the support library.

  • You need to add the library dependency to your build.gradle file. Use the latest version of the physics-based library as listed in the official documentation.
    dependencies {
      ...
      compile "com.android.support:support-dynamic-animation:26.0.0"
    }
    
  • Here is a code snippet for a simple vertical spring animation. See the Spring Animation documentation and SpringAnimation class for more information.
    final SpringAnimation anim = new SpringAnimation(
      this, DynamicAnimation.Y, 10)
     .setStartVelocity(10000);
    anim.getSpring().setStiffness(STIFFNESS_LOW);
    anim.start();
    
  • Here is a code snippet for a simple rotation fling animation. See the Fling Animation documentation and FlingAnimation class for more information.
    FlingAnimation fling = new FlingAnimation(this, DynamicAnimation.ROTATION_X);
    fling.setStartVelocity(150)
         .setMinValue(0)
         .setMaxValue(1000)
         .setFriction(0.1f)
         .start();
    
  • The PhysicsAnimation app shows a possible solution.

Summary

  • With property animation, you can use almost any property of an object to create an animation.
  • One way to create a property animation is to:

  • Create a view or custom view with the property.

  • If the view does not have a setter for the property, create one and name it set PropertyName . The setter is called by the Animator object to change the property value during animation. You must call invalidate() in the setter.
  • Override onDraw() to perform any drawing.
  • Decide how the animation is to be triggered. For example, the animation could be triggered when the user taps the screen.
  • In the method that responds to the trigger event, for example, in the onTouchEvent() method, create the objects.

  • To create an Animator, set the object and property to be animated. For the property, set a start value, an end value, and a duration.

  • Use interpolators to specify how the animated property changes over time. You can use one of the many supplied interpolators or create your own.
  • You can combine several animations to run in sequence or at the same time using AnimatorSet objects.
  • See the PropertyAnimation guide for a complete description of all the cool things you can do with property animations.

The related concept documentation is in Animations.

Learn more

Android developer documentation:

results matching ""

    No results matching ""