Skip to content
March 18, 2011 / Daniel Freeman

MadComponents. Wow!

MadComponents are my contribution to free UI components for Adobe AIR for Mobile.  For iPhone, or Android, etc.


With MadComponents, you express your user interface in XML.  So if want two buttons vertically aligned, you might write:-

<vertical> <button>label1</button> <button>label2</button> </vertical>

The most powerful feature of MadComponents is that it renders the user interface, adapting to the device screen size, and resolution.  So the developer need not worry about this, or absolute positioning of components.

Also, orientation change, portrait or landscape, is handled automatically.

My focus for MadComponents has been powerful container components such as lists, grouped-lists, tab-button pages, view flipper, list navigation etc.  For example, to define a list write:-

<list id="aList"/>

But to define a list with a custom cell renderer, the XML description of the renderer is described in XML:-

<list id="myList">
<horizontal> <label id="label"/> <switch id="state" alignH="right"/>
</horizontal>
</list>

You can even define a list based navigator like this:-

<navigation> <list id="list1"/> <list id="list2"/> {DETAIL} </navigation>

Where DETAIL is some XML that you’ve defined above, specifying the layout of the detail page.  The navigator above consists of two lists, leading to a detail page.  It would give you a navigation bar at the top, clicking on a list cell would cause the next page to slide in from the right, and clicking on the back button would cause the previous page to slide in from the right.

Obviously, you’ll want to tie up your view controller actionScript code with your XML defined view.  The most useful method is the findViewById() method.  For example:-

var uiList:UIList = UIList(UI.findViewById("myList"));
uiList.data = dataSource;
var switchRow4:UISwitch = UISwitch(uiList.findViewById("state", 4));
var switchRow4Group3:UISwitch = UISwitch(uiGroupedList.findViewById("state", 4, 3));
var myButton:UIButton = UIButton(UI.findViewById("myButton"));
myButton.addEventListener(UIButton.CLICKED, mouseUpHandler);

Notice how we’re listening for a UIButton.CLICKED event above.  Some components, such as the UISwitch, UIList, and UIGroupedList dispatch an Event.CHANGE event.

Also, you might find the following public methods useful:-

uiSwitch.state = true; // or false;
var state:Boolean = uiSwitch.state;
var whichRow:int = uiList.index;
var whichGroup:int = uiGroupedList.group;
uiPages.nextPage();
uiPages.previousPage();
uiPages.goToPage(2);

I’ll get round to documenting MadComponents properly when I have time.  In the meantime, this demonstration example will answer a lot of questions:-

(Updated 29th April 2011 in accordance with MadComponents version 0.2)

</span>
<pre>package
{
	import com.danielfreeman.madcomponents.*;

	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.text.TextFormat;
	import flash.utils.getQualifiedClassName;

	public class MadComponents extends Sprite {

		protected static const FRUIT_DATA:XML = <data>
											<Apple/>
											<Orange/>
											<Banana/>
											<Pineapple/>
											<Lemon/>
											<Mango/>
											<Plum/>
											<Cherry/>
											<Lime/>
											<Peach/>
											<Pomegranate/>
											<Grapefruit/>
											<Strawberry/>
											<Melon/>
										</data>;

		protected static const DATA:XML = <data>
											<Red/>
											<Orange/>
											<Yellow/>
											<Green/>
											<Blue/>
											<Indigo/>
										 </data>;

		protected static const COLUMNS:XML = <columns alignH="fill">
												<button>one</button><button>two</button><button>three</button>
											</columns>;

		protected static const LAYOUT0:XML = <vertical>
												<frame colour="#993333">{COLUMNS}</frame>
												<frame colour="#339933">{COLUMNS}</frame>
												<frame colour="#333399">{COLUMNS}</frame>
												<image/>
												<button id="popup" alignH="centre">show pop-up</button>
											</vertical>;

		protected static const LAYOUT1:XML = <vertical background="#EEEEEE,#FFFFFF" colour="#99CC99">
												<button alignH="fill" colour="#00ff00">a button</button>
												<horizontal><button>hello</button><input alignH="fill" background="#7777AA,#555588"/></horizontal>
												<horizontal><label id="label0">hello world</label><button colour="#ff9000">hi</button>
													<vertical colour="#0000ff"><button alignH="fill">vertical1</button><button>vertical2</button></vertical>
												</horizontal>
												<horizontal>
													<switch colour="#999999"/><switch colour="#FF8000">YES,NO</switch><image id="image0" alignH="right">48</image>
												</horizontal>
											</vertical>;

		protected static const LIST0:XML = <list id="list0" colour="#FFFFFF" background="#CCCCCC,#FFFFFF">
												<horizontal><image id="image">48</image><vertical><label id="label"/><label id="label2"/></vertical></horizontal>
											</list>;

		protected static const LIST1:XML = <list id="list1" gapV="16" background="#EEFFEE,#778877" colour="#000000">
												<horizontal><label id="label"/><arrow colour="#FFDDCC" alignH="right"/></horizontal>
											</list>;

		protected static const LIST2:XML = <list id="list2" background="#BBBBFF,#BBBBFF,#CCCCFF" colour="#BBBBFF">
												{FRUIT_DATA}
												<font color="#333366"/>
											</list>;

		protected static const LIST_GROUPS:XML = <groupedList id="list4" background="#333333,#FF9999,#FF9966,#FFFF99,#99FF99,#9999FF" gapH="64" gapV="10"/>;

		protected static const TICK_LIST:XML = <tickOneList gapV="6" id="tickList" colour="#333333" background="#EEEEEE">{FRUIT_DATA}</tickOneList>;

		protected static const DATA_GRID:XML = <dataGrid colour="#999999" background="#888899,#EEEEFF,#DDDDEE">
												<widths>30,30,40</widths>
												<header>one,two,three</header>
												<data>
													<row>1,2,3</row>
													<row>4,5,6</row>
													<row>7,8,9</row>
													<row>2,7,5</row>
													<row>1,2,3</row>
													<row>4,5,6</row>
													<row>7,8,9</row>
													<row>2,7,5</row>
												</data>
											</dataGrid>;

		protected static const FLIPPER:XML = <viewFlipper background="#CCCC00,#CCCC33" scrollBarColour="#FFFFFF">{LAYOUT0}{LAYOUT1}{DATA_GRID}</viewFlipper>;

		protected static const LIST_GROUPS_RENDERER:XML = <groupedList id="list3" background="#C6CCD6,#FFFFFF" colour="#CCCC66" gapH="32" gapV="4">
															<horizontal><label id="label"/><switch id="switch" colour="#996600" alignH="right"/></horizontal>
														</groupedList>;

		protected static const NAVIGATOR:XML = <navigation background="#FFFFFF" id="navigator">{LIST0}{LIST1}{LIST2}{LIST_GROUPS_RENDERER}{TICK_LIST}</navigation>;

		protected static const TAB_NAVIGATOR:XML = <tabPages id="tabPages" background="#333366,#333333" colour="#111122">{NAVIGATOR}{FLIPPER}</tabPages>;

		protected static const POPUP_WINDOW:XML = <vertical alignH="fill">
													<columns gapH="0">
														<picker id="picker1" background="#FFFFFF">
															{DATA}
														</picker>
														<picker id="picker1" background="#FFFFFF">
															{DATA}
														</picker>
													</columns>
													<columns>
														<button colour="#669966" id="cancel">cancel</button>
														<button colour="#996666" id="ok">ok</button>
													</columns>
												</vertical>;

		protected static const FONT:String = '<font size="20">';
		protected static const END_FONT:String = "</font>";

		[Embed(source="images/mp3_48.png")]
		protected static const MP3:Class;

		[Embed(source="images/MP4_48.png")]
		protected static const MP4:Class;

		[Embed(source="images/palm_48.png")]
		protected static const PALM:Class;

		[Embed(source="images/psp_48.png")]
		protected static const PSP:Class;

		[Embed(source="images/usb_48.png")]
		protected static const USB:Class;

		[Embed(source="images/list.png")]
		protected static const LIST_ICON:Class;

		[Embed(source="images/views.png")]
		protected static const VIEWS_ICON:Class;

		[Embed(source="images/list_highlight.png")]
		protected static const LIST_ICON_HIGHLIGHT:Class;

		[Embed(source="images/views_highlight.png")]
		protected static const VIEWS_ICON_HIGHLIGHT:Class;

		protected static const PICTURES:Vector.<Class> = new <Class>[MP3, MP4, PALM, PSP, USB];
		protected static const FRUIT:Vector.<String> = new <String>["Apple","Orange","Banana","Pineapple","Lemon","Mango","Plum","Cherry","Lime","Peach","Pomegranate","Grapefruit","Strawberry","Melon"];

		protected var _popUp:UIWindow;

		public function MadComponents(screen:Sprite = null) {
			var i:int;

			if (screen)
				screen.addChild(this);

			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;

			UI.create(this, TAB_NAVIGATOR);

			//Set up the tab buttons
			var uiTabPages:UITabPages = UITabPages(UI.findViewById("tabPages"));
			uiTabPages.setTab(0, "lists", LIST_ICON, LIST_ICON_HIGHLIGHT);
			uiTabPages.setTab(1,"view flipper", VIEWS_ICON, VIEWS_ICON_HIGHLIGHT);

			//Populate the first list
			var data0:Array = [];
			for (i=0; i<FRUIT.length; i++) {
				data0.push({label:'<font size="18">'+FRUIT[i]+'</font>', image:getQualifiedClassName(PICTURES[i%PICTURES.length]), label2:'<font color="#666666" size="11">here is some small text</font>'});
			}
			var uiList1:UIList = UIList(UI.findViewById("list0"));
			uiList1.data = data0;

			//Populate the second list
			var data1:Array = [];
			for (i=0; i<FRUIT.length; i++) {
				data1.push({label:'<font color="#FFDDCC">'+FRUIT[i]+'</font>'});
			}
			var uiList:UIList = UIList(UI.findViewById("list1"));
			uiList.data = data1;

			//Populate the grouped list
			var uiGroupedList:UIGroupedList = UIGroupedList(UI.findViewById("list3"));
			uiGroupedList.data = [[{label:FONT+"zero"+END_FONT}],[{label:FONT+"one"+END_FONT},{label:FONT+"two"+END_FONT},{label:FONT+"three"+END_FONT}],[{label:FONT+"four"+END_FONT},{label:FONT+"five"+END_FONT},{label:FONT+"six"+END_FONT}],[{label:FONT+"seven"+END_FONT},{label:FONT+"eight"+END_FONT},{label:FONT+"nine"+END_FONT},{label:FONT+"ten"+END_FONT}]];

			//Set up an images
			var image0:UIImage = UIImage(UI.findViewById("image0"));
			image0.imageClass = PALM;

			//Write to a text label
			var label0:UILabel = UILabel(UI.findViewById("label0"));
			label0.defaultTextFormat = new TextFormat("Arial",14,0xFFFFFF);
			label0.text = "White text";

			//Set up navigation title
			var navigator:UINavigation = UINavigation(UI.findViewById("navigator"));
			navigator.text = "lists";

			//Set up a pop-up window
			_popUp = UI.createPopUp(POPUP_WINDOW,180.0,200.0);
			UI.hidePopUp(_popUp);

			var showPopUpButton:UIButton = UIButton(UI.findViewById("popup"));
			showPopUpButton.addEventListener(UIButton.CLICKED,showPopUp);

			var hidePopUpButton:UIButton = UIButton(_popUp.findViewById("ok"));
			hidePopUpButton.addEventListener(UIButton.CLICKED,hidePopUp);

			var cancelPopUpButton:UIButton = UIButton(_popUp.findViewById("cancel"));
			cancelPopUpButton.addEventListener(UIButton.CLICKED,hidePopUp);
		}

		protected function showPopUp(event:Event):void {
			UI.showPopUp(_popUp);
		}

		protected function hidePopUp(event:Event):void {
			UI.hidePopUp(_popUp);
		}

	}
}

MadComponents play nicely with other components too.  For example, this layout utilises a flobile picker component:-

		protected static const LAYOUT:XML =
				<vertical xmlns:flobile="com.custardbelly.as3flobile.controls.picker">
						<flobile:Picker id="picker" itemHeight="50" alignH="centre"/>
						<image />
						<label id="day"/>
						<label id="month"/>
						<label id="year"/>
					</vertical>;

MadComponents are hot off the press, and I’ll get around to documentation, further examples, and instructions for beginners when I have time.  In the meantime, I’m sure ActionScript developers will find it valuable for developing cross-platform Adobe AIR apps for iPhone, Android, etc.

Probably the most remarkable thing about MadComponents is that it is less than 64KB big.  It is available to download from:-

http://code.google.com/p/mad-components/downloads/list

Advertisements

14 Comments

Leave a Comment
  1. davidqi / Apr 14 2011 3:49 am

    Could you give me a FLA file? I don’t know how to use SWC file . thank you very much!

    • Daniel Freeman / Apr 14 2011 4:51 am

      There is no .fla file. The source is Pure ActionScript.

      But I realise that I need to publish instructions – as only very experienced developers are likely get what’s going on at the moment. (And there aren’t many of us around).

      I’ll write up some instructions for all three approaches (Flash , Flex, and amxmlc/adt) when I have time.

  2. Manx / Jun 6 2011 5:40 pm

    Lets say I’m pulling from a database a list such as:

    Food
    Cars
    Dogs
    Washington Monument

    Some of the items in the list will call up another list because there are many types of food, cars, and dogs. But, there is only one Washington Monument so, when “Washington Monument” is clicked on it will go straight to a detail page on the Washington Monument.

    I want to use the uiList to pull up another uiList when “food” is clicked on. This second list would contain types of food. Then when the user clicks on, let’s say “banana” it would go to a detail page on bananas.

    My question is how to make “Food” in “List 1” create a new “List 2” with a list of food items that would each link to a detail page? Or, how do target the button in the list?

    • Daniel Freeman / Jun 6 2011 10:00 pm

      There are two ways to do this.

      1. The first is to use a TreeNavigator, and how you define the XML data. See the MadComponentsTree.as example. I’ve just uploaded a small change. You’ll notice that if you choose “Colours” at the first level, you one need to make one other choice, until you get the detail page.

      But if you choose “Animals”, or “HouseHold”, you need to go through two levels. I’ve just added a “Nothing” to the first list. If you click on it you go straight to the detail page.

      2. The other way to do it is with a normal navigation. But switch off automatically going forward:

      _navigator = UINavigation(UI.findViewById(“navigator”));
      _navigator.autoForward = false;

      Set up your own listener for list clicks, and control the page changes yourself.

      _navigator.addEventListener(Event.CHANGE, changePage);

      You could even make more than one detail page if you wanted.

      • Manx / Jun 7 2011 8:19 pm

        Can’t I use UIList also? I have a demo setup pulling info from a sqlite database using the UIList now but I don’t how to make the items in the UIList clickable. What I need to be able to do is click on one of the items in ListOne and it will populate the UIList with ListTwo. An event listener with Event.CHANGE would seem to work if I new how to access it in the UILIST.

        Here is the code that I have for populating the UIList:

        //LIST ONE//////////////////////////////////////////////////////////////////////////////////////////////////////////////
        function retrieveListOneData(event:SQLEvent = null):void
        {
        //create a new sql statemant
        var sql:SQLStatement = new SQLStatement();
        sql.sqlConnection = conn;
        //this sql command retrieves all the fields from our table in the database and orders it by id
        sql.text = “SELECT Id, ListOne FROM Lists ORDER BY Id”;
        //add a new event listener if there is data to display it
        sql.addEventListener(SQLEvent.RESULT, populateListOne);
        sql.execute();
        }

        function populateListOne(event:SQLEvent):void
        {
        //creates a result variable that holds all our info
        var result:SQLResult = event.target.getResult();
        //we check if results is not empty
        if (result!=null&&result.data!=null)
        {
        //add list items from datbase
        var data:Array = [];
        for (var i:Number = 0; i < result.data.length; i++)
        {
        data.push({label:result.data[i].ListOne});
        }
        list.data = data;
        }
        }

        //LIST TWO//////////////////////////////////////////////////////////////////////////////////////////////////////////////
        function retrieveListTwo(event:SQLEvent = null):void
        {
        //create a new sql statemant
        var sql:SQLStatement = new SQLStatement();
        sql.sqlConnection = conn;
        //this sql command retrieves all the fields from our table in the database and orders it by id
        sql.text = "SELECT id, ListTwo FROM ListTwoList ORDER BY ListTwo";
        //add a new event listener if there is data to display it
        sql.addEventListener(SQLEvent.RESULT, populateListTwo);
        sql.execute();
        }

        function populateListTwo(event:SQLEvent):void
        {
        //creates a result variable that holds all our info
        var result:SQLResult = event.target.getResult();
        //we check if results is not empty
        if (result!=null&&result.data!=null)
        {
        //add list items from datbase
        var data:Array = [];
        for (var i:Number = 0; i < result.data.length; i++)
        {
        data.push({label:result.data[i].ListTwo});
        }
        list.data = data;
        }
        }

  3. Daniel Freeman / Jun 7 2011 9:18 pm

    I hope you blog about this app when you get it working – because what you’re doing looks interesting.

    You just want to do this:-

    var list:UIList; // A global variable

    list = UIList(UI.findViewById(“list”));
    list.addEventListener(UIList.CLICKED, listOneClicked);

    function listOneClicked(event:Event):void {
    var index:int = list.index; //This is the index of the list clicked.
    retrieveListTwo();
    list.removeEventListener(UIList.CLICKED, listOneClicked); //Set up to listen to the next list being clicked.
    list.addEventListener(UIList.CLICKED, listTwoClicked);
    }

    BUT:- Don’t you need to obtain the id? A database key field, or a field that link related database tables? It may not be the same as the index of the row clicked.

    In MadComponentsLib0.4.4 (today’s update) you can do the following:-

    Within populateListOne, change the following line:-

    data.push({label:result.data[i].ListOne, id:result.data[i].id}); // assuming it’s called “id” – I don’t know your table schema.

    function listOneClicked(event:Event):void {
    var id:int = list.row.id; //This is the id field of your table.

    (edited 9th June 2011 – corrected some errors in the code)

    • Manx / Jun 7 2011 10:48 pm

      This line is giving me an error: list = UIList(findViewById(“list”));

      1180: Call to a possibly undefined method findViewById.

      • Daniel Freeman / Jun 7 2011 11:05 pm

        Ooops. list = UIList(UI.findViewById(“list”));

        Oh, and don’t forget to give your list the id of “list”.

        [list id=”list”/];

  4. Manx / Jun 8 2011 9:29 pm

    Daniel,

    Would you take a look at my code. I have two different list pulling from a database and they both work. But, I cannot get ListOne to load ListTwo using the UIList with the code you show above. Do you have an email address I could send it to? I’d rather not post it here…

    • Daniel Freeman / Jun 9 2011 9:47 am

      Whoops. I just realised I gave some bad advice.

      To listen for a list being clicked, you listen for the UIList.CLICKED event – not Event.CHANGE. This problem had ME scratching my head for ages (even though I wrote it that way). That was why your example didn’t work. I’ll go back and correct my comments later – but that’s basically why your code didn’t work – you listened to me!

      The other thing I realised from looking at your code is something I should tell everyone – so I’ll mention it here.

      When you use MadComponents in the “sans XML” style. (like I blogged about before). To resize a UIList, or UIForm, or any container component, do it like this:-

      UIList.layout(new Attributes(0,0,newWidth,newHeight));

      for example:-

      UIList.layout(new Attributes(0,60,stage.stageWidth,stage.stageHeight-60));

  5. Manx / Jun 18 2011 2:25 pm

    Can you use the “Flipper” as a stand alone without tabs?

    • Daniel Freeman / Jun 18 2011 7:46 pm

      Sure. [viewFlipper] {PAGE1} {PAGE2} {PAGE3} [/viewFlipper];

  6. shawn / Dec 19 2011 4:54 pm

    Awesome components! I installed the APK on my Galaxy Nexus, and the performance is quite poor. Maybe some more mobile specific optimizations are in order?

    Not trying to sound douchey, but if it’s pure as3 it should be pretty smooth, especially on a dual core device.

    • Daniel Freeman / Dec 20 2011 12:06 am

      It’s about time I made a new apk. I made it with AIR 2.7. AIR 3.1 is much more optimised.

      The “more specific optimisation” is GPU acceleration. I had to leave it out before, because it was leaving odd artefacts. The problems were fixed in 3.x.

To discuss MadComponents/MC3D, join the Facebook group!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: