Or: How to replicate Twitter’s animated launch image
For some time I’ve been wanting to demonstrate how to re-use your app’s launch image later on in the app. As a use case I picked Twitter’s seamless transition from a static to an animated launch image.
Over the holidays I finally found some time to give it a go. While creating the example I ran into several bugs I will also share the workarounds for.
NOTE: When (still?) using classic, wherever you read app/assets
replace this with Resources
and you’re good.
The end-result
Watch this movie to see both Twitter’s iOS launch image and the example I made in Titanium. The end-result on Android is identical.
The full example
Here’s all 80 lines needed for the example:
var win = Ti.UI.createWindow({
backgroundColor: 'white'
});
var img = Ti.UI.createImageView({
// 1. Finding the launch image
image: (function getImage() {
if (Ti.Platform.name === 'iPhone OS') {
// Working around orientation-bug
if (Ti.Platform.osname === 'ipad' || Math.max(Ti.Platform.displayCaps.platformWidth, Ti.Platform.displayCaps.platformHeight) === 736) {
return 'Default-' + (Ti.Gesture.isPortrait() ? 'Portrait' : 'Landscape') + '.png';
} else {
return 'Default.png';
}
} else if (Ti.Platform.name === 'android') {
return Ti.App.Android.R.drawable.background;
}
})(),
// Working around 9-patch drawable bug
width: Ti.UI.FILL,
height: Ti.UI.FILL,
});
// 2. Fixing the background-shift on Android
if (Ti.Platform.name === 'android') {
var view = Ti.UI.createView({
// Working around pixel bug
height: Ti.Platform.displayCaps.platformHeight / Ti.Platform.displayCaps.logicalDensityFactor,
bottom: 0
});
view.add(img);
win.add(view);
} else if (Ti.Platform.name === 'iPhone OS') {
win.add(img);
}
// 3. Animating the image
win.addEventListener('postlayout', function onOpen(e) {
win.removeEventListener('postlayout', onOpen);
// win.backgroundColor = 'white';
var imgWidth = img.rect.width,
imgHeight = img.rect.height;
img.animate({
width: imgWidth * 0.8,
height: imgHeight * 0.8,
delay: 1000
}, function after() {
img.animate({
width: imgWidth * 5,
height: imgHeight * 5,
opacity: 0,
delay: 100
});
});
});
// 4. Opening the window
win.open({
animated: false
});
1. Finding the launch image
iOS
For iOS, launch images are the Default*.png
files that should be in your app/assets/iphone
folder.
Although not in an images
subfolder, you can use them like any other image. Titanium will automatically select the best file for the density (@2x
, @3x
), os-name (~ipad
, ~iphone
) and/or screen subtype (-736h
, -667h
, -568h
).
Working around orientation-bug
However, at the moment it does not find orientation-specific images (-Landscape
, -Portrait
). So for iPhone 6 Plus and iPad we need to add this ourselves.
Android
On Android regular launch images (default.png
) are normally found in density-specific folders under app/assets/android/images
. The more preferable 9-patch images are found as background.9.png
in the platform/android/res/drawable-*
folders.
Titanium moves and renames the regular launch images to the drawable folders when it compiles. So in both cases we can use the Ti.App.Android.R.drawable.background
to get the id of the application-resource in the required density and/or orientation.
Working around 9-patch drawable bug
If you are using a 9-patch image, use Ti.UI.SIZE
for the width
and height
of the image. At the moment Titanium doesn’t load backgroundImages from the application-resources while an ImageView doesn’t support 9-patch images.
2. Fixing the background-shift on Android
Like it was on iOS 6 and earlier, on Android the launch image gets the full height of the screen, while the windows begin under the status bar. Because of this you’ll notice the launch image shifting down a little when we transition from the actual launch image to our full-size ImageView.
To fix this we wrap the ImageView in a view that we give the full height of the screen and position on the bottom of it. For a regular launch image we could do this directly on the ImageView, but this way it also works for a 9-patch launch image which would then be correctly centered in the wrapping view.
Working around pixel bug
Note that we have to convert Ti.Platform.displayCaps.platformHeight
from pixels to dp. It’s one of the few remaining properties that don’t apply the default unit correctly.
3. Animating the image
We wait to animate the image until the first postlayout
event after opening the window so we can read the exact dimension of the image and use that to calculate the animation.
We start with zooming out a little beforing zooming in and fading out the image. Note that we gave the window a white backgroundColor
on line #2 so that zooming out and then fading out the image produces a smooth transitions.
In an actual app you would probably wait to animate the image until you’ve done whatever you need to do when your app first starts.
Finally we open the window with no animation
. Depending on the distribution Android uses different animations to open windows which would disturb the seamless transition between the actual launch image and our re-use of it.
Finetuning
You could further finetune the animation by using TitaniumAnimator with one of its easing functions. For the sake of simplicity I have not done so in this example.
Update: Manuel Conde Vendrell did an Alloy version of this example, including some finetuning for Android 2.3 and an example of how to show the actual content after the animation ends.
Code strong!
Comments
abada henno
Nice !!
Thanks for sharing
David He
Very nice and useful. Thanks Fokke
Efrain Hernandez
Thanks :D
Manuel Conde Vendrell
Beautiful, thanks
Victor Casé
Wow! Great work man, tks for sharing!
Matías Schmid
Thanks man! Works like a charm!
Manuel Conde Vendrell
One off-topic. Why you still use the “old way” if (Ti.Platform.name === ‘iPhone OS’) instead the shortcut if (OS_IOS)? Less to write and faster to read.
Fokke Zandbergen Post author
@Manuel because it’s Titanium classic, not Alloy ;)
Manuel Conde Vendrell
Ok @Fokke, classic code for classic Titanium, got it.
Manuel Conde Vendrell
@Fokke, I did the Alloy version (I’m working with Alloy preferably) and a little fix for when you have “something” in the window (not just empty, like the example, nor white). How can I send it to you, so you can post it?
The fix will fix two issues: on Android 2.3 (I know no one cares, but there are still 10% of users with it) will hide the image (now it remains, seems opacity is not working for the image and u can´t interact with other controls), and, for Android also, will not mix the anim image and the content of the window.
On iOS the current example works well even if you have content on the window.
Fokke Zandbergen Post author
@Manuel I would be happy to update the examples with fixes and link to a gist with an Alloy version.
Manuel Conde Vendrell
@Fokke, done, thanks.
Nick den Engelsman
Wow!!! Will add this :)
ebrahim3bmo3ty
thanks, thats very nice :)
Chris T Stuart
how would I do this with NavigationWindow?
Fokke Zandbergen Post author
@Chris I think it should work the same way, just wrap the window in the example in a NavigationWindow.
Did you try it and run into any specific challenges?
Chris T Stuart
I was trying to open this window first and animate the image and then open the nav one but nav opens first
fokkezb
@chris did you do th the nav.open after the last animation called back? Have a gist?
Chris T Stuart
Yes. I know i’m asking to much but maybe you can write the code and reply me with it? @fokkezb
fokkezb
@chris, add a callback as second argument to the last .animate() in the example:
img.animate({ … }, function() {
yourNavWin.open();
});
Jack
not working ios9 + default splash missing can’t be read
Fokke Zandbergen Post author
@Jack, that’s (almost) correct. Is not due to iOS 9 but because Titanium 5.x now uses asset catalogs for launch images. I mentioned it in the second-last bullet in the 5.0.0 Sample App blog post: http://www.appcelerator.com/blog/2015/10/titanium-5-0-0-sample-app/#ios_icons_launch_images
Clément Blanco (@claymm)
So what’s the workaround? Copy/paste our splashscreen images into `/images` and use them from there?
fokkezb
Yep, you’ll need to do that indeed.