12.1: Creating property animations
Contents:
- What you should already KNOW
- What you will LEARN
- What you will DO
- App overview
- Task 1. Creating the PropertyAnimation app
- Solution code
- Coding challenge
- Summary
- Related concept
- Learn more
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 aradius
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 thePulseAnimationView
class and asetRadius()
"setter" method for that custom property. TheAnimator
instance will call thesetRadius()
method to change the size ofmRadius
during the animation. - Override the
onDraw()
method to draw a circle of sizemRadius
. 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.
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
- Create a new custom view class called
PulseAnimationView
that extendsView
.public class PulseAnimationView extends View {}
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); }
- In
activity_main.xml
, remove theTextView
and add aPulseAnimationView
that matches the size of the parent.<com.example.android.propertyanimation.PulseAnimationView android:layout_width="match_parent" android:layout_height="match_parent"/>
- 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 theradius
and asetRadius()
method to set the variable's value. - The property setter's name needs to be of the form
set
PropertyName
().
ThePropertyName
can be any valid string. When you create the animator, you will pass thePropertyName
as string to the constructor. To cause the object to be redrawn after the property changes, you must call the
invalidate()
method. Callinginvalidate()
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 afloat
radius
as its argument. SetmRadius
to the passed-in radius and call theinvalidate()
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 settingmRadius
and before calling theinvalidate()
method, change the color of themPaint
variable. Colors are integers and can thus be used in integer operations. You can change the value of theCOLOR_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.
- In the custom view class, create
private float
member variablesmX
andmY
to store the event coordinates.private float mX; private float mY;
- Override the
onTouchEvent()
method to get the event coordinates and store them in themX
andmY
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.
- 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;
Override the
onSizeChanged()
method.@Override public void onSizeChanged(int w, int h, int oldw, int oldh) {}
Inside the
onSizeChanged()
method, you will create threeObjectAnimator
objects and oneAnimatorSet
object. You cannot create them in theonCreate()
method, because the views have not been inflated, and so the call togetWidth()
used below would not return a valid value.In the
onSizeChanged()
method, create anObjectAnimator
calledgrowAnimator
. 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 thePulseAnimationView
.ObjectAnimator growAnimator = ObjectAnimator.ofFloat(this, "radius", 0, getWidth());
- Set the duration of the animation in milliseconds.
growAnimator.setDuration(ANIMATION_DURATION);
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.
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 startingradius
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.- 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. - 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());
- Create a second
ObjectAnimator
object namedshrinkAnimator
. Use the same parameters as forgrowAnimator
but swap the start and end values.ObjectAnimator shrinkAnimator = ObjectAnimator.ofFloat(this, "radius", getWidth(), 0);
- Set the duration to
ANIMATION_DURATION
and use aLinearOutSlowInInterpolator
. This is a more complex interpolator and you can check the documentation for details.shrinkAnimator.setDuration(ANIMATION_DURATION); shrinkAnimator.setInterpolator(new LinearOutSlowInInterpolator());
- Add a starting delay. When the animation is started, it will wait for the specified delay before it runs.
shrinkAnimator.setStartDelay(ANIMATION_DELAY);
- Still in
onSizeChanged()
, create a thirdObjectAnimator
instance calledrepeatAnimator
.ObjectAnimator repeatAnimator = ObjectAnimator.ofFloat(this, "radius", 0, getWidth()); repeatAnimator.setStartDelay(ANIMATION_DELAY); repeatAnimator.setDuration(ANIMATION_DURATION);
- Add a repeat count to
repeatAnimator
. A repeat count of0
is the default. With a repeat count of0
, the animation plays once and does not repeat. With a repeat count of1
, the animation plays twice.repeatAnimator.setRepeatCount(1);
- 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, isRESTART
.)repeatAnimator.setRepeatMode(ValueAnimator.REVERSE);
Create a
private AnimatorSet
member variable calledmPulseAnimatorSet
and initialize the variable with anAnimatorSet
.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 otherAnimatorSet
objects. See the Property Animation guide for all the cool things you can do with animator sets.The following
AnimatorSet
is very simple and specifies that thegrowAnimator
should play before theshrinkAnimator
, followed by therepeatAnimator
. Add it after you have created the animators.mPulseAnimatorSet.play(growAnimator).before(shrinkAnimator); mPulseAnimatorSet.play(repeatAnimator).after(shrinkAnimator);
- 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.
- Override the
onDraw()
method to draw a circle at themX
,mY
coordinates. - Give the circle a radius of
mRadius
and set the color tomPaint
.@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(mX, mY, mRadius, mPaint); }
1.7 Play the animation
- In the
onTouchEvent()
method, add code to play the animation if there is anACTION_DOWN
event. If an animation is running, cancel it. This resets themPulseAnimatorSet
and its animations to the starting values.if(mPulseAnimatorSet != null && mPulseAnimatorSet.isRunning()) { mPulseAnimatorSet.cancel(); } mPulseAnimatorSet.start();
- Run your app and tap the white screen to see the animations play. Have some fun and experiment with the animation!
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 theAnimator
object to change the property value during animation. You must callinvalidate()
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.
Related concept
The related concept documentation is in Animations.
Learn more
Android developer documentation:
- View Animation
- Property Animation
- Drawable Animation
- Physics based animation
- See the Graphics Architecture series of articles for an in-depth explanation of how the Android framework draws to the screen.