Dissecting Google’s Mobile Animation Engine — Part 3

Continued from part 2

The final step in getting our animations working is actually animating the views. For this we’ll a series of nested Animator objects based on the keyframe data from the .btfy files.

"animations": [
{
"type": "animation",
"id": "Position",
"duration": 10.05,
"delay": 0,
"property": "position",
"keyframes": [
[
0,
{
"x": 1.1875,
"y": 0.22421875
},
{
"name": "cubic-bezier",
"x1": 0.0001,
"y1": 0,
"x2": 0,
"y2": 1
}
],
[
0.06301824212272,
{
"x": 0.99305555555556,
"y": 0.22421875
},
{
"name": "linear",
"x1": 1,
"y1": 1,
"x2": 1,
"y2": 1
}
],
[
0.13598673300166,
{
"x": 0.99305555555556,
"y": 0.22421875
},
{
"name": "cubic-bezier",
"x1": 0.412,
"y1": 0.046,
"x2": 0.17,
"y2": 0.977
}
],
[
0.23548922056385,
{
"x": 0.99305555555556,
"y": 0.55
},
{
"name": "linear",
"x1": 1,
"y1": 1,
"x2": 1,
"y2": 1
}
],
[
0.93366500829187,
{
"x": 0.99305555555556,
"y": 0.55
},
{
"name": "cubic-bezier",
"x1": 0.4,
"y1": 0,
"x2": 0.2,
"y2": 1
}
],
[
1,
{
"x": 0.99305555555556,
"y": 0.26796875
}
]
]
}
]

The Animators can be easily created by using if we use the BtfyAnimationViewTranslator as the target object.

private Animator createAnimator(BtfyAnimationViewTranslator btfyAnimationViewTranslator, BtfyAnimationGroup animg) {
Collection<Animator> animationSet = new HashSet<>();
for (BtfyAnimationElement ani : animg.animationList) {
List<Animator> keyframeAnimators = new ArrayList<>();
for (BtfyKeyframe keyframe : ani.keyframes) {
AnimatorSet animatorSet;
Animator currAnimator = null;
switch (ani.property) {
case OPACTIY:
BtfyKeyframeFloat ko = (BtfyKeyframeFloat) keyframe;
currAnimator = ObjectAnimator.ofFloat(btfyAnimationViewTranslator, "alpha",
ko.from, ko.to
);
break;
case POSITION:
BtfyKeyframeBtfyPoint kp = (BtfyKeyframeBtfyPoint) keyframe;

animatorSet = new AnimatorSet();
animatorSet.play(ObjectAnimator.ofFloat(btfyAnimationViewTranslator, "translationX",
kp.from.x, kp.to.x))
.with(ObjectAnimator.ofFloat(btfyAnimationViewTranslator, "translationY",
kp.from.y, kp.to.y));
currAnimator = animatorSet;
break;
case SCALE:
BtfyKeyframeBtfyPoint ks = (BtfyKeyframeBtfyPoint) keyframe;
animatorSet = new AnimatorSet();
animatorSet.play(ObjectAnimator.ofFloat(btfyAnimationViewTranslator, "scaleX",
ks.from.x, ks.to.x))
.with(ObjectAnimator.ofFloat(btfyAnimationViewTranslator, "scaleY",
ks.from.y, ks.to.y));
currAnimator = animatorSet;
break;
case ROTATION:
BtfyKeyframeInteger kr = (BtfyKeyframeInteger) keyframe;
currAnimator = ObjectAnimator.ofFloat(btfyAnimationViewTranslator, "rotation",
kr.from,
kr.to
);
break;
}
currAnimator.setDuration(Math.round(((double) ani.duration) * keyframe.framecount));
currAnimator.setInterpolator(keyframe.timeInterpolator);
keyframeAnimators.add(currAnimator);
}
AnimatorSet sequentialAnimatorSet = new AnimatorSet();
sequentialAnimatorSet.setStartDelay(ani.delay);
sequentialAnimatorSet.playSequentially(keyframeAnimators);
animationSet.add(sequentialAnimatorSet);
}

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animationSet);
return animatorSet;
}

If you play the animation now you should see something like this.

We have animations!

Obviously there is still work to be done, but we’ve proven that it can be done and if feels like we are on the right track.

With a lot of trial and error you should be able to get the animations working. If you would rather skip the trial and error you can grab my code here.