Parallel Animations in an AR Ionic App using Wikitude
As I said in a previous Angular 2 tutorial.
Animations can make or break your UX.
Too much of them and your application looks like a Vegas Casino (although this can be an interesting idea for an Augmented Reality application).
None and your app will be missing the little spark to make it stand out.
In this tutorial we have a look at how to implement them, starting from the previous Ionic AR tutorial.We are going to resize our Drawables when switching between the idle and the selectede states.
Let's start by adding three properties to our Marker Class:
class Marker {
constructor (poiData) {
this.RESIZE_DURATION = 2000;
this.animationGroupIdle = null;
this.animationGroupSelected = null;
.
.
.
}
}
The RESIZE_DURATION represents the duration of the animation.
Animations in Wikitude are very simple.
Their base object is an AnimationGroup. The animations will be stocked into an array-like object then triggered.
Two types of AnimationGroups:
- Parallel: Where every animations are run at the same time
- Sequential: Where the animations are triggered one after the other
In this tutorial we use a Parallel Group because it's the most suitable for our transition. Each group has it's own use.
We have two AnimationGroups, one that will be used when the Marker is selected and another one when it's deselected.
Back to the code now!
Our animations last two seconds so we need to prevent any action on a Marker if an animation is running.
We need the following method to check this condition:
isAnyAnimationRunning(marker) {
if ((marker.animationGroupSelected === null) ||
(marker.animationGroupIdle === null)) {
return false;
} else {
if ((marker.animationGroupSelected.isRunning() === true) ||
(marker.animationGroupIdle.isRunning() === true)) {
return true;
} else {
return false;
}
}
}
Three cases here:
- When both AnimationGroups are not yet initialized: we are good to go
- When one group is running an animation: we lock any action
- When both groups aren't running any animation: we are good to go
This method will be used in the getOnClickTrigger method in order to see if we allow the click action or not:
getOnClickTrigger (marker) {
return function() {
if (!marker.isAnyAnimationRunning(marker)) {
if (marker.isSelected) {
marker.setDeselected(marker);
} else {
marker.setSelected(marker);
try {
World.onMarkerSelected(marker);
} catch (err) {
alert(err);
}
}
return true;
} else {
AR.logger.debug('a animation is already running');
};
}
};
When no animations are running the setSelected and setDeselected methods can be used.
Selection
Starting with the setSelected method:
setSelected (marker) {
marker.isSelected = true;
if (marker.animationGroupSelected === null) {
var selectedDrawableResizeAnimationX =
new AR.PropertyAnimation(marker.markerDrawableSelected, 'scale.x', null, 3,
this.RESIZE_DURATION,
new AR.EasingCurve(AR.
CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC,
{
amplitude: 2.0
}));
var selectedDrawableResizeAnimationY =
new AR.PropertyAnimation(marker.markerDrawableSelected, 'scale.y', null, 3,
this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
amplitude: 2.0
}));
var idleDrawableResizeAnimationX =
new AR.PropertyAnimation(marker.markerDrawableIdle, 'scale.x', null, 1,
this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
amplitude: 2.0
}));
var idleDrawableResizeAnimationY =
new AR.PropertyAnimation(marker.markerDrawableIdle, 'scale.y', null, 1,
this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
amplitude: 2.0
}));
marker.animationGroupSelected =
new AR.AnimationGroup(AR.CONST.ANIMATION_GROUP_TYPE.PARALLEL, [
selectedDrawableResizeAnimationX,
selectedDrawableResizeAnimationY,
idleDrawableResizeAnimationX,
idleDrawableResizeAnimationY
]);
}
.
.
.
}
We already had this method (previous tutorial).
The first part is to create a new AnimationGroup and stock it in the animationGroupSelected property if it's the first time.
We create four PropertyAnimations. A PropertyAnimation will modify a target's property over time.
The first PropertyAnimation selectedDrawableResizeAnimationX will increase the scale of the marker when it's selected, making it bigger on the horizontal axe.
It will do it during a two seconds time period using the Ease Out Elastic easing curve.
An Easing Curve represents how the animation is done, for example, it can be linear and constantly move to the finished state or start slowly and finish faster.
More information on this subject in the documentation.
The second part is about the markerDrawableIdle, these PropertyAnimations just make it smaller.
Those PropertyAnimations are stocked in the animationGroupSelected property.
Finally after our if condition, we start the animations in this group:
marker.markerDrawableIdle.enabled = false;
marker.markerDrawableSelected.enabled = true;
marker.animationGroupSelected.start();
Deselection
The deselection process is the reverse of what we have done before:
setDeselected (marker) {
marker.isSelected = false;
if(marker.animationGroupIdle === null) {
var selectedDrawableResizeAnimationX =
new AR.PropertyAnimation(marker.markerDrawableSelected, 'scale.x', null, 1,
this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
amplitude: 2.0
}));
var selectedDrawableResizeAnimationY =
new AR.PropertyAnimation(marker.markerDrawableSelected, 'scale.y', null, 1,
this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
amplitude: 2.0
}));
var idleDrawableResizeAnimationX =
new AR.PropertyAnimation(marker.markerDrawableIdle, 'scale.x', null, 3,
this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
amplitude: 2.0
}));
var idleDrawableResizeAnimationY =
new AR.PropertyAnimation(marker.markerDrawableIdle, 'scale.y', null, 3,
this.RESIZE_DURATION,
new AR.EasingCurve(AR.CONST.EASING_CURVE_TYPE.EASE_OUT_ELASTIC, {
amplitude: 2.0
}));
marker.animationGroupIdle =
new AR.AnimationGroup(AR.CONST.ANIMATION_GROUP_TYPE.PARALLEL, [
selectedDrawableResizeAnimationX,
selectedDrawableResizeAnimationY,
idleDrawableResizeAnimationX,
idleDrawableResizeAnimationY
]);
}
}
Here we increase the markerDrawableIdle's size and decrease the markerDrawableSelected's size.
We use another group located in the animationGroupIdle property.
And we trigger the animations:
marker.markerDrawableIdle.enabled = true;
marker.markerDrawableSelected.enabled = false;
marker.animationGroupIdle.start();
And Voila!
Conclusion
Animations in Wikitude are very easy.
The workflow is very simple to master: creating some AnimationProperties, stocking them in an AnimationGroup that can run them Sequentially or Parallelly and trigger the group when we need the animations to occur.
All of that is encapsulated in the Marker so the World doesn't care how things are running, once again, this Marker can be replaced by any other Marker.