HIDIHO!

giving something back to the Flash community

( Air + Android ) – Air

Tags: , , ,

cover air + android
3 weeks ago I bought myself a HTC Desire HD, it’s a phone the size of a small brick, it’s not really handy as a telephone but it has a bigger screen.

2 weeks ago I attended 2 inspiring sessions by Erick Ghaumez * and Nicolas Gans * about Air for Android & IOS. I discovered that the workflow was pretty straightforward as long as:
1 you have a legal copy of Flash CS5 or FlexBuilder
2 you don’t want to target IPhones, Ipads or anything beginning with an i.

anyway after the session I was pleasantly surprised – and very enthusiastic – about getting my own apps to work on mobile devices. the possibilities of these little toys are very inspiring, I’ll try to address that later.

SO, how do you compile an AIR for Android app without Flash CS5 ? ( on a PC )

    1. download and install FLASHDEVELOP 4. they recommend to install it as a standalone if you’re already using FD3 for production projects.
    2. give them money, they deserve it.
  1. download the latest FLEX SDK : NOT THE OPEN SOURCE SDK! you’ll need the adl.exe that doesn’t ship open source (I’ve lost an hour because of that).
  2. download the ANDROID SDK, install it and donwload evrything (what I did : 2.6Go… go for a coffee… or 2 ) or select only the components you need if you can.
    1. create a new AS3 Android App project in FD4, read, understand and follow the instructions.
    2. give them money, they deserve it.

and off I went, after 3 hours of labour, I had my first app running smoothly on my brick phone: a black screen with a text reading “hello chicken!” (yay ! \o/ ).

then I tried something more exciting than a black surface and tried to compile this:

draw something / hit any key to clear.
it’s the world famous scribbler by ZeFrank, made extra famous by Mr Doob & Quasimondo (as far as I can remember ).

it was also running… well running might be some kind of an overkill in this case though, slowly and painfully crawling would be more appropriate or as Nicolas Gans put it: “it brings us back to the good old time of Framerate hunting and ruthless hacks” … or something.

I had already gone through the basic optimizations and I knew from the sessions that Air for Android does not like vector drawing very much. I learnt that it doesn’t like BitmapData twitching at all :)

after a while, I managed to get a decent framerate ( at least something quite responsive ).
you can try it out here : scribbler.apk and if you don’t have a device or don’t want to bother, there’s a video at the bottom of the page.

btw if anyoone finds this useful, I’ve adapted an Adobe example to handle the 5 gestures:

here’s what it does:

then I tried to save my masterpiece on the brick to impress my girlfriend.
first, exporting the file as a PNG doesn’t seem to work. the really bothering part is when I tried to use a JPG encoder, the app would freeze for a couple of seconds during the JPG conversion.

there is surely a way to do that, as I’m not familiar with Air’s specific methods, I looked for it and as I couldn’t find it, I gave up.
during my searches, I found a lot of android examples and they didn’t look that terrible.
this led me to THE question: why using a VM inside a VM when one could just skip the second VM?

so last week I started digging that Android thing, it’s very interesting, not extremely complex and pretty well documented.

how to compile pure android then ?
if you’ve installed the android SDK, then the next step would be to download and install eclipse.
after that, follow the instructions on that page: http://developer.android.com/sdk/eclipse-adt.html, pretty straight forward also.

having tried processing before, I was less puzzled by the Java syntax and could get the very same scribbler example to work pretty easily.

here’s the code for the equivalent of a Main.as class.

package net.nicoptere.scribbler;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;

public class ScribblerActivity extends Activity implements OnClickListener
{

	private Scribbler scribbler;

	public void onCreate(Bundle savedInstanceState)
	{
			
		super.onCreate(savedInstanceState);
		    
		// Set full screen view
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		
		//adds the Scribbler
		scribbler = new Scribbler(this);
		setContentView( scribbler, new ViewGroup.LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT ) );
		scribbler.requestFocus();
		
		//reset button
		Button reset = new Button(this);
		reset.setText("reset");
		reset.setOnClickListener( this );
		addContentView( reset, new ViewGroup.LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT ) );
		    
	}

	@Override
	public void onClick(View v)
	{
		scribbler.reset();
	}
}

we can see that it’s pretty different, the thing that makes it look complex is that it’s using many many many Enums and Interfaces but as we’re using eclipse, most of the hassle is done automatically and usually quick fixes just work :)
as mentionned above, there are plenty of resources available on the net addressing specific issues ; you’ll never be alone.
now the interesting part is the Scribbler class:

package net.nicoptere.scribbler;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;

public class Scribbler extends View implements OnTouchListener
{
	private ArrayList<Point> points = new ArrayList<Point>();
	private ArrayList<Float> lines = new ArrayList<Float>();
	private int[] colors = {0xf48b49,0xf48b46,0xf48c43,0xf48c40,0xf38c3d,0xf38d3a,0xf38d37,0xf38d34,0xf38d30,0xf38e2d,0xf28e2a,0xf28e27,0xf28f24,0xf28f21,0xf28f1e,0xf2901b,0xf29018,0xf19015,0xf19112,0xf1910f,0xf29515,0xf39b1e,0xf5a227,0xf6a72f,0xf8ad38,0xf9b341,0xfab94a,0xfcbf53,0xfdc55b,0xffcb64,0xffcb64,0xffca62,0xffc95f,0xffc75c,0xffc65a,0xffc558,0xffc355,0xffc252,0xffc150,0xffc04e,0xffbe4b,0xffbd48,0xffbc46,0xffbb43,0xffb941,0xffb83e,0xffb73c,0xffb639,0xffb437,0xffb334,0xffb232,0xffb12f,0xffaf2d,0xffae2a,0xffad28,0xffac25,0xffaa23,0xffa920,0xffa81e,0xffa71b,0xffa518,0xffa416,0xffa314,0xffa211,0xffa00e,0xff9f0c,0xff9e0a,0xff9c07,0xff9b04,0xff9a02,0xff9900,0xfd9800,0xfb9700,0xf99600,0xf79500,0xf59400,0xf39300,0xf19200,0xef9100,0xee9000,0xec8f00,0xea8e00,0xe88d00,0xe68d00,0xe48c00,0xe28b00,0xe08a00,0xdf8900,0xdd8800,0xdb8700,0xd98600,0xd78500,0xd58400,0xd38300,0xd18200,0xcf8100,0xcd8000,0xcc7f00,0xca7e00,0xc87d00,0xc67c00,0xc47c00,0xc27b00,0xc07a00,0xbf7900,0xbd7800,0xbb7700,0xb97600,0xb77500,0xb57400,0xb37300,0xb17200,0xaf7100,0xad7000,0xac6f00,0xaa6e00,0xa86d00,0xa66c00,0xa46b00,0xa26b00,0xa06a00,0x9e6900,0x9d6800,0x9b6700,0x996600};
	private int colorId = 0;

	private Paint paint;
	private Bitmap background;
	
	public Scribbler( Context context )
	{
		super(context);
		
		//setting controls
		setFocusable(true);
		setFocusableInTouchMode(true);
		this.setOnTouchListener(this);
		
		//creates paint attributes
		paint = new Paint();
		paint.setAntiAlias(true);
		background = BitmapFactory.decodeResource( getResources(), R.drawable.background);
		
	}

	//renders the lines
	public void onDraw( Canvas canvas )
	{
		//draws the background image
		Rect rect = new Rect( 0,0, getWidth(), getHeight());
		canvas.drawBitmap(background, null, rect, null);
		
		int i = colorId = 0;
		for( i = 0; i < lines.size(); i += 4 )
		{
			//grabs the next color
			nextColor();
			//draws a line
			canvas.drawLine(lines.get(i), lines.get((i + 1)), lines.get((i + 2)), lines.get((i + 3)), paint);
		}

	}

	public boolean onTouch(View view, MotionEvent event)
	{
		
		if( event.getAction() == MotionEvent.ACTION_MOVE )
		{
			//adds a point to the points list
			Point pt = new Point(event.getX(),event.getY());
			points.add( pt );
			
			//and tests its distance against the existing points
			float dx, dy;
			for( Point p : points )
			{
				dx = pt.x - p.x;
				dy = pt.y - p.y;
				
				//if the distance is smaller than 50px ( 2500 = 50^2 )
				//gives 50% chance to create line ( Math.random() > .5 = 50 % )
				if( Math.abs((dx * dx + dy * dy)) < 2500 && Math.random() > .5 )
				{
					lines.add(pt.x);
					lines.add(pt.y);
					lines.add(p.x);
					lines.add(p.y);
				}
			}
		}
		
		//flush the view, call for onDraw and wait
		invalidate();

		return true;
	}
	
	//assigns the next color to the canvas
	private void nextColor()
	{
		//picking the next int
		int c = 0xFF << 24 | colors[ colorId ++ % colors.length ];
		
		//converting color to ARGB components
		paint.setARGB( 	0xFF, ( c>>16 & 0xFF ), ( c>>8 & 0xFF ), ( c & 0xFF ) 	);
	}
	
	//deletes points and lines
	public void reset()
	{
		for( Point p : points )
		{
			p = null; 
		}
		points = new ArrayList<Point>();
		lines = new ArrayList<Float>();
		//clear the view
		invalidate();
	}
	
	//point dataholder
	private class Point
	{
		public float x, y;
		public Point( float x, float y )
		{
			this.x = x;
			this.y = y;
		}
	}
}

and that’s it!
here’s what they do ; first test is Air for Android, the second is Android only.

the test is pretty unfair by the way ; in the Air app, I use a bitmapData to store the strokes thus sparing lots of resources while in the Android app 1/ everything is redrawn in a loop ( pretty much like Processing ) 2/ there are +40% more strokes. the framerate is very convinving in both cases :)
ok I still can’t save the masterpieces, I’ll have to look into it.

for now you can download :

conclusion:
Air for Android is very easy to use and presents huge advantages: a “not-so-dev” friendly API, powerful tools already built-in and ready to go, everything is wrapped behind the actionscript API and this can spare a lot of energy.
on the other hand, the Air player is required and you have to watch the performance and the resources very carefully. actually this is something everybody should alaways do :) note that the next releases will bring some comfortable updates.

Droid: swapping to Android is not hard. it takes some time and energy but not that much and the result so far is very rewarding. performance are rather good in average, there’s also a pretty active community, fewer gurus than back in the days of Flash, plenty of resources and workarounsd.
but mostly, MOSTLY: native OPENGL ES support \o/

expect some android experiments in the near future :)

Tags: , , ,

© 2009 HIDIHO!. All Rights Reserved.

This blog is powered by Wordpress and Magatheme by Bryan Helmig.