Custom Titanium Animation

Simon Buckingham
All Titanium
Published in
4 min readSep 28, 2017
Coding is just a ray of sunshine with Titanium!

This tutorial shows how to play a sequence of images in Axway/Appcelerator Titanium using 2 different techniques: one the standard SDK supported method using Ti.UI.ImageView and an alternative, and much better, custom controller method. It assumes that you have basic knowledge of how to use Titanium and Alloy.

I originally wrote this custom controller (or something like it) several years ago as Ti.UI.ImageView was very buggy and offered little control of playing back animations of image sequences.

The example project was developed using Ti SDK 6.1.2 on OS X 10.12.6 (with XCode 8.3.3) and tested on iOS 10.3 and Android 4, 5 and 6.

The Standard Way

Using Ti.UI.ImageView we can assign an array of filenames to the “images” property and control the playback with the start() and stop() methods. So for example:

imageViewExample.xml:<Alloy>
<Window id="win">
<ImageView id="animation"/>
<Button onClick="onClickPlayAnimation">Play</Button>
<Button onClick="onClickStopAnimation">Stop</Button>
</Window>
</Alloy>
imageViewExample.tss:"#animation": {
width: 300,
height: 300,
repeatCount: 1
}
imageViewExample.js:$.animation.images = Alloy.Globals.animationFilenames;function onClickPlayAnimation(_evt) {
$.animation.start();
}
function onClickStopAnimation(_evt) {
$.animation.stop();
}

I have omitted several of the details for simplicity, but Alloy.Globals.animationFilenames is an array of filenames e.g. [“/images/animation/animation_001.png”, “/images/animation/animation_002.png”, “/images/animation/animation_003.png”….etc] (see the end of the post for further details).

The problem with this is that — when setting the images property — the frames don’t load/show until start() is called on the view. Also, it is not possible to use the “image” and “images” property simultaneously as one might imagine. So we have to create a placeholder view underneath the animation to hold the first frame.

imageViewExample.xml:<Alloy>
<Window id="win">
<ImageView id="animationStartFrame”/>
<ImageView id="animation"/>
<Button onClick="onClickPlayAnimation">Play</Button>
<Button onClick="onClickStopAnimation">Stop</Button>
</Window>
</Alloy>
imageViewExample.tss:"#animationStartFrame": {
width: 300,
height: 300
}
"#animation": {
width: 300,
height: 300,
repeatCount: 1
}
imageViewExample.js:$.animation.images = Alloy.Globals.animationFilenames;
$.animationStartFrame.image = animationFilenames[0];
function onClickPlayAnimation(_evt) {
$.animation.start();
}
function onClickStopAnimation(_evt) {
$.animation.stop();
}

This then is already a bit fiddly. In older versions of Ti prior to 6 there was also a bad bug which meant that image sequences on Android would flash as they loaded (the caching obviously was not working correctly). In addition it is not possible to seek (jump to a specific frame) using this technique as the SDK does not support it and there is no “animation finished” event.

So a better way is to use a custom animation controller.

Custom Animation Controller

The code for the custom animation controller library file is:

And we use it in an Alloy controller by instantiating it with a view — and frame rate and an array of filenames as for Ti.UI.ImageView — plus a callback, if we want, to signal the end of playback of all the animation frames (the view can either be a Ti.UI.View or a Ti.UI.ImageView, but as noted later it works best with a Ti.UI.View).

manualExample.xml:<Alloy>
<Window id="win">
<View id="animation"/>
<Button onClick="onClickPlayAnimation">Play</Button>
<Button onClick="onClickStopAnimation">Stop</Button>
</Window>
</Alloy>
manualExample.tss:"#animation": {
width: 300,
height: 300
}
manualExample.js:var ManualAnimation = require('manualAnimation');
var animation;
function initAnimation() {
animation = new ManualAnimation({
view: $.animation,
filenames: Alloy.Globals.animationFilenames,
frameRate: 12,
finishedCallback: onFinishedAnimation
});
animation.init();
}
function onFinishedAnimation() {
console.log('Finished animation!!!!!!');
}
function onClickPlayAnimation(_evt) {
animation.start();
}
function onClickStopAnimation(_evt) {
animation.stop();
}
initAnimation();

So the code looks very similar to the first example using a single ImageView, except we are using in this case a View and our custom library controller.

With this technique we can stop/start an animation as usual, but also seek and add a callback when the animation has finished playing. Plus of course, add anything else we want to do — such as play at varying factors of the original speed or in reverse — including adding various custom events.

It uses only one view, which can be either an Ti.UI.ImageView or a plain old Ti.UI.View. An ImageView is a heavier component than a View. It does support more sophisticated image control such as fitting an image proportionally to a parent view, given either its width or height, whereas with a View we have to specify both width and height. ImageView’s also have better management of remotely loaded images. However, for the purposes of straightforward playback of local images, using a View performs the best in my experience. In particular, there is still a problem with flashing images using an ImageView on Android (you can try it out by swapping <View id=”animation”/> in manualAnimation.xml for <ImageView id=”animation”/>).

And finally, I create the array for the animation image file names by the following code (used in index.js), where I have used my own personally created cell animation of 118 frames saved in /images/animation:

Alloy.Globals.animationFilenames = [];function initAnimationFilenames() {
var filename;
for (var i = 1; i <= 118; i++) {
filename = '/images/animation/animation_' + padNumber(i, 3) + '.png';
Alloy.Globals.animationFilenames.push(filename);
}
}
//@_number Integer
//@_length Integer - length to pad string with '0's
function padNumber(_number, _length) {
var str = _number + '';
while (str.length < _length) {
str = '0' + str;
}
return str;
}
initAnimationFilenames();

To see and download the whole project (with examples of the standard ImageView method and the custom animation method) get it on GitHub: https://github.com/icecandy/TitaniumCustomAnimationExample

Code strong!

Simon Buckingham is a designer, UX specialist, animator, creative and technical director and developer with more than 20 years experience. He has been making cross-platform iOS/Android mobile apps with Titanium for more than 5 years. Simon runs his own company Icecandy Entertainment, based in London, UK. You can find out more about his digital career at simonbuckingham.me.

--

--