In order to animate an object we can make use of the Android Property Animation API.
The most easy way is to use an ObjectAnimator
to animate the properties of a Node
.
A Node
has multiple things that can be animated:
The following slides will give you a short introduction to the Property Animation API and after this we learn how this can be applied on Sceneform Nodes.
The Property Animation API can be used for animating (almost) anything in Android. All you need is to create an Animator object and define the characteristics of the animation. There are two main types of Animators available:
ValueAnimator
: it generates values for your animation that you then can use to change your objects properties. This is more low level and only used when the ObjectAnimator does not workObjectAnimator
: it generates values for your animation and directly sets the corresponding properties on the object to animate. This is slightly constrained on settable properties of certain types, but for most cases it will workYou can define the following characteristics:
The Animator will use this defined characteristics to calculate the new values of the properties of your object for each fragment of a timeframe. For example in the below graphic, the value of x at the beginning of the animation with a duration of 40 ms will be x = 0. At the end the value of x will be x = 40. The Animator will now calculate the value for every frame that shall be rendered. How often is defined by the refresh delay (in this case 10 ms). How the value changes within each fraction can be linear or otherwise defined by a function based on the current elapsed time (non-linear animation).
To create a non-linear interpolation, the TimeInterpolator
class can be used.
There are a number of predefined TimeInterpolators provided, but you can always write your own. Some examples:
The class TypeEvaluator
defines how the values for the property have to be calculated. For example, if a property has integer values, the IntEvaluator
is used. If a properties requires floats the FloatInterpolator is used. And for a color property the ArgbInterpolator
can be used.
The problem with animation 3D objects (nodes) is, that the use properties that require special types such as a Vector3
for scale and location and a Quaternion
for rotation.
Fortunately the Sceneform.Math
package provides us with a QuaternionEvaluator
and a Vector3Evaluator
.
The following example code shows how to create an ObjectAnimator
that animates the intensity of the Light of a node.
To get access to the Light of the node use getLight()
and the animate the property intensity
like shown here:
final int durationInMilliseconds = 8000;
final float minimumIntensity = 1000.0f;
final float maximumIntensity = 6000.0f;
ObjectAnimator intensityAnimator =
ObjectAnimator.ofFloat(
node.getLight(), "intensity", minimumIntensity, maximumIntensity);
intensityAnimator.setDuration(durationInMilliseconds);
intensityAnimator.setRepeatCount(ValueAnimator.INFINITE);
intensityAnimator.setRepeatMode(ValueAnimator.REVERSE);
intensityAnimator.start();
For rotation we need to animate the localRotation property of the node. This properties is of type Quaternion. As a rotation can be maximum 180 degrees in one direction, the first value of the animation is 0, the second 180 and the third 360 degrees.
By using setObjectValues()
the animations interpolation values are defined. setPropertyName()
defines to alter the localRotation of the node. We are using a LinearInterpolation here.
Quaternion orientation1 = Quaternion.axisAngle(new Vector3(0.0f, 1.0f, 0.0f), 0);
Quaternion orientation2 = Quaternion.axisAngle(new Vector3(0.0f, 1.0f, 0.0f), 180);
Quaternion orientation3 = Quaternion.axisAngle(new Vector3(0.0f, 1.0f, 0.0f), 360);
ObjectAnimator rotateAnimator = new ObjectAnimator();
rotateAnimator.setObjectValues(orientation1, orientation2, orientation3);
// Next, give it the localRotation property.
rotateAnimator.setPropertyName("localRotation");
// Use Sceneform's QuaternionEvaluator.
rotateAnimator.setEvaluator(new QuaternionEvaluator());
// Allow orbitAnimation to repeat forever
rotateAnimator.setRepeatCount(ObjectAnimator.INFINITE);
rotateAnimator.setRepeatMode(ObjectAnimator.RESTART);
rotateAnimator.setInterpolator(new LinearInterpolator());
rotateAnimator.setAutoCancel(true);
rotateAnimator.setTarget(node);
rotateAnimator.setDuration(8000);
rotateAnimator.start();
Scaling of a node is basically done in the same way as with the rotating. This time we need to use the Vector3Evaluator
instead of the QuaternionEvaluator
as the localScale property requires this.
Vector3 maxSize = new Vector3(3,3,3);
ObjectAnimator scaleAnimation = new ObjectAnimator();
scaleAnimation.setObjectValues(maxSize);
scaleAnimation.setPropertyName("localScale");
scaleAnimation.setEvaluator(new Vector3Evaluator());
scaleAnimation.setDuration(8000);
scaleAnimation.setRepeatCount(ValueAnimator.INFINITE);
scaleAnimation.setRepeatMode(ValueAnimator.REVERSE);
scaleAnimation.setInterpolator(new LinearInterpolator());
scaleAnimation.setTarget(node);
scaleAnimation.start();
When using the TransformableNode
instead of a normal Node
class you will notice a flickering when using the above method to scale its size. This is due to the ScaleController
that is part of the TransformableNode
. The ScaleController will always try to rezise to node to its normal size and that cause the flickering.
So in this case it is better to animate the MaxScale
property of the ScaleController
instead.
You can get the ScaleController
instance by calling getScaleController()
on the TransformableNode
.
private void animateNode(TransformableNode node){
final int durationInMilliseconds = 8000;
final float minimumSize = 1.0f;
final float maximumSize = 6.0f;
sizeAnimator = ObjectAnimator.ofFloat(node.getScaleController(),"MaxScale", minimumSize,maximumSize);
sizeAnimator.setDuration(durationInMilliseconds);
sizeAnimator.setRepeatCount(ValueAnimator.INFINITE);
sizeAnimator.setRepeatMode(ValueAnimator.REVERSE);
sizeAnimator.start();
}
To make the app more individual, you can change the texture of the plane that is shown when ARCore detects it. This is done by creating a Texture.Sample instance and then use this to build a Texture and set it on the ARSceneView PlaneRenderer as Material. This code snipped shows, how this is done:
Texture.Sampler sampler =
Texture.Sampler.builder()
.setMagFilter(Texture.Sampler.MagFilter.LINEAR)
.setWrapMode(Texture.Sampler.WrapMode.REPEAT)
.build();
Texture.builder()
.setSource(this, R.drawable.ic_launcher)
.setSampler(sampler)
.build()
.thenAccept(texture -> {
arSceneView
.getPlaneRenderer().getMaterial().thenAccept(material -> material.setTexture(PlaneRenderer.MATERIAL_TEXTURE, texture));
});
Now you should be able to create a simple ARCore app that displays 3D objects. You have learned how to place the objects in space and handling tap events by using a TranformableNode to manipulate the objects. Next you have learned how to animate nodes using the ObjectAnimator. Last but not least you have learned how to create a Texture and replace the standard PlaneRenderer texture with your own. This should make it possible to create your own simple IKEA App clone or create your own app idea. Have fun!