Setting Custom Labels on Lists with the PlayBook

By default, it seems like the only property that the QNX List class can display as a label is label. In Flex I was used to being able to set my own label property or my own custom label function but it doesn’t look like that’s possible in the QNX List. Luckily, it’s not TOO hard to set up a class to do just that.

First step is to create a class that extends CellRenderer. There’s a lot that seems to go on in CellRenderer but I’m just starting to dig into it so I’ll save that for a later post. The easiest method I found was to override the setter for data and then use the setLabel function there. You can pass in whatever string you want to that setLabel function and it will display as the default label with the default font.

One of the issues I’m having is trying to figure out exactly when the data gets set on the CellRenderer component. Some of the other things I tried (setting it in the constructor, setting it on the drawLabel method) all didn’t work because the data object was still null. So I’m trying to figure out exactly when data becomes available. Hopefully I’ll have that for a followup post that goes into more detail on creating custom renderers for the list class.

Here’s the code I used for my example:

package com.pintley.components.listClasses
{
import qnx.ui.core.SizeUnit;
import qnx.ui.listClasses.CellRenderer;
import qnx.ui.text.Label;
 
public class BeerItemRenderer extends CellRenderer
{
     private var _beer:Object;
 
     public function BeerItemRenderer()
     {
          super();
     }
 
     override public function set data(value:Object):void
     {
          _beer = value;
          setLabel(_beer.beerName);
     }
}
}

Using the Container Classes to Lay Out PlayBook Applications

I just put together a tutorial on using the Container classes in the PlayBook SDK. Coming from a Flex background I found the Container classes kind of confusing at first but ultimately a nice elegant solution to the problem of laying objects out on the screen. The tutorial provides an introduction to the Container classes, shows some of the properties that you use to customize them, then walks through a basic example of a header and two sub containers and how to place and align components inside them. Finally it talks about resizing containers.

Here’s a link to the video. The code for the demo is below.

package
{
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.MouseEvent;
 
	import qnx.ui.buttons.LabelButton;
	import qnx.ui.core.Container;
	import qnx.ui.core.ContainerFlow;
	import qnx.ui.core.Containment;
	import qnx.ui.core.SizeMode;
	import qnx.ui.core.SizeUnit;
	import qnx.ui.core.Spacer;
	import qnx.ui.data.DataProvider;
	import qnx.ui.listClasses.List;
	import qnx.ui.text.Label;
 
	[SWF(height="600",width="1024")]
	public class ContainerDemo extends Sprite
	{
 
		private var base:Container;
 
		private var left:Container;
 
		private var right:Container;
 
		private var header:Container;
 
		public function ContainerDemo()
		{
			super();
 
			// support autoOrients
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			// Set up the base container with a horizontal flow
			base = new Container();
			base.debugColor = 0xff0000;
			base.margins = Vector.<Number>([10,10,10,10]);
			base.flow = ContainerFlow.HORIZONTAL;
 
			// Set up the left container with a size of
			// 50 percent
			left = new Container();
			left.debugColor = 0x00ff00;
			left.margins = Vector.<Number>([10,10,10,10]);
			left.size = 50;
			left.sizeUnit = SizeUnit.PERCENT;
 
			// Set up the right container with a size of 
			// 50 percent. By default the Container class
			// lays things out with a vertical flow.
			right = new Container();
			right.debugColor = 0x0000ff;
			right.margins = Vector.<Number>([10,10,10,10]);
			right.size = 50;
			right.sizeUnit = SizeUnit.PERCENT;
 
			// Create the header and dock it to the top 
			// of the screen
			header = new Container();
			header.debugColor = 0x00ffff;
			header.margins = Vector.<Number>([10,10,10,10]);
			header.size = 15;
			header.sizeUnit = SizeUnit.PERCENT;
			header.containment = Containment.DOCK_TOP;
			header.flow = ContainerFlow.HORIZONTAL;
 
 
			// Set up the array, data provider, and 
			// list component 
			var arr:Array = new Array({label:"Deschuttes The Abyss"},
				{label:"New Belgium Sahti"},{label:"Russian River Pliney The Elder"},
				{label:"Dogfish Head Bitches Brew"},{label:"Stone Double Bastard Ale"},
				{label:"Elysian Bifröst Winter Ale"},{label:"Odell 90 Shilling"},
				{label:"Lagunitas Undercover Investigation Shut-Down Ale"},
				{label:"Delirium Tremens"},{label:"Alaskan Double Black IPA"},
				{label:"John John Juniper"},{label:"Full Sail Wreck the Halls"},
				{label:"Samuel Adams Winter Lager"});
			var data:DataProvider = new DataProvider(arr);			
			var list:List = new List();
				list.dataProvider = data;
				list.containment = Containment.BACKGROUND;
 
			// Create the nav buttons with a width of 33.3 percent
			// and set the sizeMode to both so they take up 100% 
			// of the height of the parent container.
			var nav1:LabelButton = new LabelButton();
				nav1.label = "Home";
				nav1.size = 33.3;
				nav1.sizeUnit = SizeUnit.PERCENT;
				nav1.sizeMode = SizeMode.BOTH;
				nav1.addEventListener(MouseEvent.CLICK,onClick);
 
			var nav2:LabelButton = new LabelButton();
				nav2.label = "Recommended Beers";
				nav2.size = 33.3;
				nav2.sizeUnit = SizeUnit.PERCENT;
				nav2.sizeMode = SizeMode.BOTH;
 
			var nav3:LabelButton = new LabelButton();
				nav3.label = "Rated Beers";
				nav3.size = 33.3;
				nav3.sizeUnit = SizeUnit.PERCENT;
				nav3.sizeMode = SizeMode.BOTH;
 
			var spacer:Spacer = new Spacer();
				spacer.size = 50;
				spacer.sizeUnit = SizeUnit.PERCENT;
 
			var label:Label = new Label();
				label.text = "Pintley For PlayBook!";
 
 
			// The addChild calls
			header.addChild(nav1);
			header.addChild(nav2);
			header.addChild(nav3);				
 
			right.addChild(spacer);
			right.addChild(label);
 
			left.addChild(list);
 
			base.addChild(header);
 
			base.addChild(left);
			base.addChild(right);
 
 
			addChild(base);
 
			base.setSize(stage.stageWidth,stage.stageHeight);
		}
 
		protected function onClick(event:MouseEvent):void
		{
			// changes the size of the left/right containers
			// and then calls base.layout() so that all of
			// the base container's children are re-configured
			// and redrawn
			left.size = 25;
			right.size = 75;
			base.layout();
		}
 
	}
}

360Flex Denver

360Flex Denver360Flex is officially coming to Denver this year! I’m stoked because Denver is the second or third best city in the country. It’s going to be April 10th – 13th. Right now there are some good deals on tickets (if you buy before January 7th) so get them while they’re hot. It’s going to be a fun year for Flex with all of the devices coming to market and with people having had a chance to play with Flex “Hero” and Flash Builder “Burrito” (plus Flash Catalyst “Panini”) so this could be the best 360Flex in a while.

While you’re registering, you might as well think about speaking. They’ve opened the call for papers as well. See you there!

Skinning PlayBook Components

I’ve been having a lot of fun with the PlayBook. It’s really nice to be using ActionScript 3 to write a “native” application and the QNX components have been really good. One of the things I wanted to do right away was skin them to customize them for the Pintley app I’m building. Luckily, if you’ve spent any time doing Flash, it’s pretty straightforward. The main issue is that the SDK is still fairly new so there were some small things I had to figure out.

There are basically three ways you can skin components on the PlayBook: with a Flash Professional SWC that contains the graphic objects, with images, and using the AS3 drawing API. I’ll show off the last two.

Skinning with Images

What I wanted to do was skin a TextInput and a Button for my login page. The PlayBook SDK has a few classes that are specifically for skinning, which I was able to check out via the documentation. There’s an entire package qnx.ui.skins that has classes for skinning. It looks like, based on this page, that most of the default skins, are images. So I tried that first. What I found helpful was to grab those stock images, bring them into Photoshop, and start tweaking them.

I create three different PNGs for each of the states the TextInput has: up, down, and focus. Once I have those, I embed them in my custom skin class and use the specific QNX APIs to set the look and feel. The class extends the qnx.ui.skins.text.TextInputSkin class, which is the base class for creating a TextInputSkin. Using the attributes on the Embed tag you can set the numbers to make the nine-slice scaling work for your image.

The method to override is the initializeStates method. That’s the best place to register properties for states with the QNX component model. Then there are two other methods to use in that method. The first one, setSkinState, just associates a specific skin with a specific state. The last line of code, showSkin, takes a skin class and that’s what the component will use as the default skin.

That’s pretty much all there is to it. Here’s the class:

package com.pintley.skins
{
     import flash.display.Sprite;
 
     import qnx.ui.skins.SkinStates;
     import qnx.ui.skins.text.TextInputSkin;
 
     public class PintleyImageTextInputSkin extends TextInputSkin
     {
          protected var upSkin:Sprite;
          protected var downSkin:Sprite;
          protected var focusSkin:Sprite;
 
          [Embed(source="/assets/skins/TextInputUp.png",
               scaleGridLeft="22",scaleGridRight="350",
               scaleGridTop="18",scaleGridBottom="38")]
          private var UpGraphic:Class;
 
          [Embed(source="/assets/skins/TextInputDown.png",
               scaleGridLeft="22",scaleGridRight="350",
               scaleGridTop="18",scaleGridBottom="38")]
          private var DownGraphic:Class;
 
          [Embed(source="/assets/skins/TextInputFocus.png",
               scaleGridLeft="22",scaleGridRight="350",
               scaleGridTop="18",scaleGridBottom="38")]
          private var FocusGraphic:Class;
 
          public function PintleyImageTextInputSkin()
          {
               super();
          }
 
          override protected function initializeStates():void
          {
               super.initializeStates();
 
               // Up State
               upSkin = new UpGraphic();
               setSkinState(SkinStates.UP,upSkin);
 
               // Down State
               downSkin = new DownGraphic();
               setSkinState(SkinStates.DOWN,downSkin);
 
               // Focus Skin
               focusSkin = new FocusGraphic();
               setSkinState(SkinStates.FOCUS,focusSkin);
 
               showSkin(upSkin);
          }
     }
}

Skinning with the Drawing API

Another way to skin the components is using the drawing API. This one is mostly similar to using images with a couple of minor changes. Create the variables in the same way, and override the initializeStates function. Skinning with the drawing API also uses the same methods as the images. setSkinState works the same way and showSkin sets the default skin.

The main difference is that there is an external function in this example, which is where the drawing takes place. Since the different states of the component will look generally the same, I pass in some variables to change the appearance based on which state it’s being drawn for. The method gets called in the initializeStates function after the skin sprites are initialized. You could also just do the drawing in the initializeStates method, but this is a bit better in terms of code reuse.

package com.pintley.skins
{
     import flash.display.DisplayObject;
     import flash.display.Sprite;
     import flash.filters.DropShadowFilter;
     import flash.geom.Rectangle;
 
     import qnx.ui.skins.SkinStates;
     import qnx.ui.skins.text.TextInputSkin;
 
     public class PintleyTextInput extends TextInputSkin
     {
          protected var upSkin:Sprite;
          protected var downSkin:Sprite;
          protected var focusSkin:Sprite;
 
          public function PintleyTextInput()
          {    
               super();
          }
 
          protected function drawSprite(item:Sprite,lineThickness:int,shadow:Boolean=false):void
          {
               var innerShadow:DropShadowFilter = new DropShadowFilter();
                    innerShadow.distance = 10;
                    innerShadow.angle = 40;
                    innerShadow.strength = .15;
                    innerShadow.blurX = 25;
                    innerShadow.blurY = 25;
                    innerShadow.inner = true;
 
               item.graphics.beginFill(0xffffff,1);
               item.graphics.lineStyle(lineThickness,0x3c2414,1);
               item.graphics.drawRoundRect(0,0,350,30,5);
               item.graphics.endFill();    
               item.filters = [innerShadow];
 
               if(shadow)
               {
                    var dropShadow:DropShadowFilter = new DropShadowFilter();
                         dropShadow.distance = 0;
                         dropShadow.blurX = 21;
                         dropShadow.blurY = 21;
                         dropShadow.angle = 0;
                         dropShadow.strength = 1;
                         dropShadow.color = 0x3c2414;
 
                    item.filters = [innerShadow,dropShadow];
               }
          }
 
          override protected function initializeStates():void
          {
               /**
                * UpSkin
                **/
               upSkin = new Sprite();
               drawSprite(upSkin,2);
               setSkinState(SkinStates.UP, upSkin );
 
               /**
                * DownSkin
                **/
               downSkin = new Sprite();
               drawSprite(downSkin,3);
               setSkinState(SkinStates.DOWN, downSkin );
 
               /**
                * FocusSkin
                **/
               focusSkin = new Sprite();
               drawSprite(focusSkin,3,true);
               setSkinState(SkinStates.FOCUS, focusSkin );
 
               showSkin( upSkin );
          }
     }
}

One last example that I have uses the image method but skins a button instead of the TextInput control. One thing I’ve noticed about the QNX controls is that some of the components (like TextInput) have their own skin files that can be subclassed, while others, like Buttons, have skins that look like they just subclass UISkin. So instead of calling super.initializeStates() like I did with the TextInput, with the Button, I just override initializeStates() and put the code there.

Update: Thanks to a comment below, I realized that when you extend UISkin, the skinned objects have to be of type DisplayObject, not Sprite.

package com.pintley.skins
{
     import flash.display.DisplayObject;
 
     import qnx.ui.skins.SkinStates;
     import qnx.ui.skins.UISkin;
 
     public class LoginButton extends UISkin
     {
          protected var upSkin:DisplayObject;
          protected var downSkin:DisplayObject;
          protected var selectedSkin:DisplayObject;
          protected var disabledSkin:DisplayObject;
 
          [Embed(source="/assets/skins/LoginButtonUp.png")]
          private var UpGraphic:Class;
 
          [Embed(source="/assets/skins/LoginButtonSelected.png")]
          private var DownGraphic:Class;
 
          [Embed(source="/assets/skins/LoginButtonDisabled.png")]
          private var DisabledGraphic:Class;         
 
          public function LoginButton()
          {
               super();
          }
 
          override protected function initializeStates():void
          {
 
               // Up State
               upSkin = new UpGraphic();
               setSkinState(SkinStates.UP,upSkin);
 
               // Down State
               downSkin = new DownGraphic();
               setSkinState(SkinStates.DOWN,downSkin);
 
               // Selected State
               selectedSkin = new DownGraphic();
               setSkinState(SkinStates.SELECTED,selectedSkin);
 
               // Disabled Skin
               disabledSkin = new DisabledGraphic();
               setSkinState(SkinStates.DISABLED,disabledSkin);
 
               showSkin(upSkin);
          }         
     }
}

So hopefully that gets you started on skinning. As I get to more complicated components I’ll blog about skinning those as well, but the basic premise seems the same across most of the components.

Slides and Demo for ‘Intro to Flex “Hero” Mobile’ Presentation at SDFUG

I had a blast presenting at the San Diego Flash Users Group last week and getting a chance to show off Flex “Hero”‘s mobile features as well as getting to hang out with the SDFUG crew. Big thanks to Chris Griffith and Kyle Tyacke for setting it up and to Andrew Walpole, Aaron Pederson, and James Polanco for the great beer opportunities.

I got a chance tonight to upload my slides to Slideshare. They can be found here and embedded below. You can also check out the demo app I built, which shows off some of the things I talked about in the deck. It’s here on Github.

Motodev Podcast on December 16th

motodev_podcastOn December 16th, I’ll be participating in a podcast with Randy Ksar from Motodev to talk about Android, Motorola, and the year in review along with some Adobe AIR talk. It’s going to be done live so you can tune into the stream (and RSVP here). With the Motorola Tablet being in the news lately it seems like a very fun time to recap everything in anticipation for next year.

Getting Started with the BlackBerry PlayBook and Adobe AIR

As I’ve mentioned, I’m excited about the fact that RIM has wholeheartedly embraced AIR for their PlayBook. That means if you’re a Flash or Flex developer you’re going to be able to easily target this device along with all of the other phones and tablets that support AIR.

And there is no reason you can’t get started right away. RIM just released a new version of the AIR SDK that you can use along with their simulator to get stared building apps. I did a quick tutorial embedded below that shows the steps needed to get going. It’s pretty easy to get a jump and the emulator gives you a feel for how your application will behave on the device.

I’ll be talking a lot more about development on the PlayBook over the next couple of months and I’ve got a few projects that will be specifically targeting the 7″ form factor so I’ll be talking about those and showing some best practices.