Skinning a Flex Mobile Button with Bitmaps

Flex Mobile Button Skin

Flex Mobile Button Skin

I’m working on a mobile app that requires a lot of skinning and since Flex 4 we’ve gotten a MUCH improved skinning model for Flex. Unfortunately, with Flex mobile, for performance reasons it’s a good idea to use ActionScript-only skins which means going back to the world of Flex 3 and diving into the component lifecycle a bit.

My colleague Terry Ryan has a good blog post that shows what he used to do skinning and I started off with that approach. But something didn’t seem quite right about skinning a button with a function associated with borders, so I tracked down the skinning master on the Flex team, Jason San Jose, and asked him the best way to do it. It turns out it’s really pretty simple to skin a button using Bitmap assets. If you look at the source code for ButtonSkin you’ll see that in the constructor, they associate FXG files (as classes) with specific button states.

upBorderSkin = spark.skins.mobile320.assets.Button_up;
downBorderSkin = spark.skins.mobile320.assets.Button_down;

In that constructor they also check for the DPI of the device and then load in the correct skin class according to that DPI. In general it’s going to be a good idea to check for different screen sizes and load in different assets accordingly, but for this example, I’ve just got one size that stretches reasonably well. The first step is to embed the assets and give them a variable name.

[Bindable]
[Embed(source="/assets/combobox_button_up.png")]
private var up:Class;
 
[Bindable]
[Embed(source="/assets/combobox_button_down.png")]
private var down:Class;

Then, in the constructor, simply set the upBorderSkin and downBorderSkin to those classes:

public function ComboBoxButtonSkin()
{
     super();
 
     upBorderSkin = up;
     downBorderSkin = down;
}

In this example I also wanted to create a graphic that would be overlaid on top of my skin. It’s a triangle graphic that will change depending on whether the app is on iOS or Android because of the different ways that those two platforms do ComboBoxes. On iOS you often have the app slide over to reveal the list (so the arrow would be pointing to the right) and on Android you often get a pop-up window with a list of choices (so the arrow would point down).

Adding Objects To Your Button Skin

To add something to your skin you need to go back and think about how Flex components work. You first want to make sure it’s added using the createChildren method. In this example I have already created the _triangle variable and instantiated it in the constructor.

override protected function createChildren():void
{
     super.createChildren();
     addChild(_triangle);
 
}

Then, once it’s added, you can start to modify it. In this example I wanted to make sure I could dynamically place the arrow depending on the height and width of my button. Luckily, the drawBackground method gives you an unscaledHeight and unscaledWidth, which you can use to calculate where the object should be placed. So all I had to do was tie into the drawing API, use those numbers, and voila, my skin now has the triangle.

override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
{
     var halfUnscaledHeight:int = Math.round(unscaledHeight/2);
 
     _triangle.graphics.beginFill(0xcee410);
     _triangle.graphics.moveTo(unscaledWidth-20,halfUnscaledHeight-5);
     _triangle.graphics.lineTo(unscaledWidth-10,halfUnscaledHeight);
     _triangle.graphics.lineTo(unscaledWidth-20,halfUnscaledHeight+5);
     _triangle.graphics.lineTo(unscaledWidth-20,halfUnscaledHeight-5);
     _triangle.graphics.endFill();
}

One of the benefits of this approach as opposed to just including the triangle in the skin is that the triangle kind of looked odd when it was stretched and morphed to different screen sizes. This way it’s independent of the bitmap skin and I can change the size according to the size of the underlying button.

Here’s what the finished button looks like and here’s the full code for you to grab.

Mobile Button Up State

Mobile Button Up State

Mobile Button Down Skin

Mobile Button Down Skin

    package skins
    {
    import flash.display.Sprite;
 
    import spark.skins.mobile.ButtonSkin;
 
    public class ComboBoxButtonSkin extends ButtonSkin
    {
    [Bindable]
    [Embed(source="/assets/combobox_button_up.png")]
    private var up:Class;
 
    [Bindable]
    [Embed(source="/assets/combobox_button_down.png")]
    private var down:Class;
 
    protected var _triangle:Sprite;
 
    public function ComboBoxButtonSkin()
    {
    super();
 
    upBorderSkin = up;
    downBorderSkin = down;
 
    _triangle = new Sprite();
    }
 
    override protected function createChildren():void
    {
    super.createChildren();
    addChild(_triangle);
 
    }
 
    override protected function drawBackground(unscaledWidth:Number, unscaledHeight:Number):void
    {
    var halfUnscaledHeight:int = Math.round(unscaledHeight/2);
 
    _triangle.graphics.beginFill(0xcee410);
    _triangle.graphics.moveTo(unscaledWidth-20,halfUnscaledHeight-5);
    _triangle.graphics.lineTo(unscaledWidth-10,halfUnscaledHeight);
    _triangle.graphics.lineTo(unscaledWidth-20,halfUnscaledHeight+5);
    _triangle.graphics.lineTo(unscaledWidth-20,halfUnscaledHeight-5);
    _triangle.graphics.endFill();
    }
 
    }
    }

Slides and Code from 360Flex Denver

360Flex Denver360|Flex Denver was an absolutely fantastic event. I was at the first 360 and have watched the event grow and mature over the years. It’s always been the best Flex conference out there, but there was something about those first few 360′s that had so much energy and got the community excited. This event felt like that. I’d rank it as one of the best 360′s I’ve ever been to. The attendees were enthusiastic and great to talk to, the keynotes were awesome and inspiring, and John and Nicole were at the top of their game as far as hosting went. It might have been all of the mobile stuff, it might have been the announcement on day 2 of Project Spoon, but this 360Flex was just all around great.

So of course, I made a bit of a misstep with my talk. At what was one of the greatest Flex conferences of the past 5 years, I go and give an HTML5/jQuery talk. But hopefully it was still fun. And to be honest, as I dove into HTML5 and jQuery for this talk, I learned that we have it very, very good as Flex developers. There are a lot of great things about HTML5 and especially jQuery, but if you’re building complex RIAs and applications, Flex has solved a ton of problems that the HTML5/JS world hasn’t yet. I definitely think they will, and the community around JS and jQuery is great, but as a Flex developer, you’ve got the best of both worlds and you’re going to be able to jump over to jQuery/HTML5 projects and bring some very valuable knowledge about building complex apps.

Thanks to everyone who attended the talk. My slides can be found here (and embedded below) and you can grab the demos from my GitHub repository here.

Adobe SXSW Roundup

If you haven’t been to SXSW, as a geek, you need to go at least once. The badge is pretty cheap for what you get, and the experience is unreal. As I’ve gone to SXSW I find myself going to fewer and fewer panels, but there is always very good content. And the subject matter is incredibly diverse so you can geek out on everything from development to food. But the best part is connecting with people. One of the best things about SXSW is that everyone there is happy to talk to you about anything. There are always a few people who came to pitch their stuff, but even they’re fun to talk about and the enthusiasm they have is infectious. We held some Adobe events at breakfast and lunch and I got to meet some really fun people who were jazzed about what Adobe is doing.

We also had our crack video team at the event and they did a ton of video. To get a feel for what it was like at SXSW, check out the videos below. And hopefully we’ll see you there next year. Big thanks to the Edge team, and the CS marketing team for putting on some great Adobe events at SXSW this year.

There’s a cool video about the Interactive Web Awards, which we sponsor and some of the winners and how they use Adobe tools. Some of the finalists for these awards are amazingly impressive.

We also snuck some of the next-gen features in Flash Professional and Dreamweaver and did a couple of videos of feedback from the people that came to the session. You can find Part 1 here and Part 2 here.

Using OAuth for Twitter Authentication on the BlackBerry PlayBook

OAuth is becoming critical for any app developer who wants to access data from a 3rd party source on behalf of their users. If you aren’t familiar with OAuth there is an excellent intro doc over at Hueniverse. I had some difficulty wrapping my head around OAuth the first few times I tried to use it so I’ll provide a quick explanation below as I walk through the sample.

OAuth is a way of providing access from a 3rd party site (in this case Twitter) to your application without needing the user to type their 3rd party credentials into your application. Instead, OAuth lets you register with the 3rd party site and uses a series of tokens, which eventually give you access to the data. One of the main benefits with OAuth is that if an application does something nefarious with the data, the user can immediately revoke access. For this example I’ll be walking through the Twitter API and using the very excellent OAuth-AS3 library by Shannon Hicks, the founder of Pintley, which you should check out if you’re the kind of person who likes to drink beer.

The first step to using OAuth is to register with the site you want to get data from. Twitter has a very easy way to do this with their App portal. Fill in the necessary info (select the type as client since this won’t be a web-based application) and Twitter will provide a bunch of random strings that you’ll use to request access to the data and then request the data itself. The data that we’re most worried about is in the OAuth 1.0a Settings section and includes a consumer key, consumer secret, request token URL, access token URL, and authorize URL.

Getting information out of Twitter has the following flow:

  1. Use the consumer key and the consumer secret to create an OAuthConsumer object.
  2. Use the OAuthConsumer object to ask Twitter for permission to access data.
  3. Prompt the user to authorize your application to access their specific data.
  4. Request the user’s data and perform operations on their behalf.

This is all done with a series of tokens until the last stage when you get an access token that you will use to access data after the user gives you permission. I set those up as private field variables so they can be accessed from any method in this example and set the unchanging data as static variables.

private static var CONSUMER_SECRET:String = "<YOUR CONSUMER SECRET>";
private static var CONSUMER_KEY:String = "<YOUR CONSUMER KEY>";
private static var REQUEST_TOKEN_URL:String = "https://api.twitter.com/oauth/request_token";
private static var ACCESS_TOKEN_URL:String = "https://api.twitter.com/oauth/access_token";
private static var AUTHORIZE_URL:String = "https://api.twitter.com/oauth/authorize";
private static var API_URL:String = "https://api.twitter.com";
private static var SIGNATURE:OAuthSignatureMethod_HMAC_SHA1 = new OAuthSignatureMethod_HMAC_SHA1();
 
private var _consumer:OAuthConsumer;
private var _authRequest:OAuthRequest;
private var _accessRequest:OAuthRequest;
 
private var _requestToken:OAuthToken;
private var _accessToken:OAuthToken;

The first step is to have our application log into Twitter with the consumer secret and the consumer key so that it can make the authorization request. In the init() function I set up the UI as well as create the consumer and my first OAuthRequest object, the authorization request. This will just go out to Twitter and make sure that the consumer key and secret are registered to an application. If they are, then Twitter will return a request token that I can use to request access from the user for his or her data. In that init() method I have a button that will start the process and the event handler on the button uses the buildRequest method of my _authRequest object to format the URL string correctly and then I send it off to Twitter.

protected function init():void
{    
     _consumer = new OAuthConsumer(CONSUMER_KEY,CONSUMER_SECRET);
     _authRequest = new OAuthRequest(OAuthRequest.HTTP_MEHTOD_GET,REQUEST_TOKEN_URL,null,_consumer);
 
     _loginContainer = new Container();
     _loginContainer.align = ContainerAlign.MID;
 
 
 
     var button:LabelButton = new LabelButton();
          button.label = "Login to Twitter";
          button.addEventListener(MouseEvent.CLICK,onClick);
 
 
     _loginContainer.addChild(_spacer);
     _loginContainer.addChild(button);
     _loginContainer.setSize(1024,600);
     addChild(_loginContainer);
 
}
 
protected function onClick(event:MouseEvent):void
{
     var urlRequest:URLRequest = new URLRequest(_authRequest.buildRequest(SIGNATURE));
     var loader:URLLoader = new URLLoader(urlRequest);
     loader.addEventListener(Event.COMPLETE,onRequestComplete);
}

With that response I can now build the _requestToken, which I can use to let the user grant me access to his or her data. The next step is to prompt the user to authorize my application. Once I get the response back from Twitter’s servers I save that information as the request token and then build a UI to let the user start the authorization process.

protected function onRequestComplete(event:Event):void
{
     _requestToken = getTokenFromResponse(event.currentTarget.data);
 
     _authContainer = new Container();
     _authContainer.align = ContainerAlign.MID;
 
 
     var authBtn:LabelButton = new LabelButton();
          authBtn.label = "Authorize this application";
          authBtn.addEventListener(MouseEvent.CLICK,onAuthClick);
 
     _authContainer.addChild(_spacer);
     _authContainer.addChild(authBtn);
     _authContainer.setSize(1024,600);
 
     removeChild(_loginContainer);
     addChild(_authContainer);
}

When the user clicks the button my application will send them to Twitter where they will have to log in and authorize my application to use their data. They’re prompted either to deny my app or allow it.

Twitter Authorization Form

If they click Allow, then Twitter provides a PIN number that the user has to enter back in the application to complete the authorization process. For web-based applications there is a callback URL which is where the user is sent after they authorize the application. But for client-side applications, Twitter uses the PIN number and asks the user to enter it back in the application.

Getting the PIN Number

In the code, I first create the UI elements I need to track the PIN number and then make a URLRequest using the key that Twitter provided in the request token.

protected function onAuthClick(event:MouseEvent):void
{
     _verifyContainer = new Container();
     _verifyContainer.align = ContainerAlign.MID;
 
     var label:Label = new Label();
          label.size = 100;
          label.sizeUnit = SizeUnit.PERCENT;
          label.text = "Enter the PIN from Twitter.com";
 
     var font:TextFormat = new TextFormat();
          font.align = TextFormatAlign.CENTER;
          font.bold = true;
          font.size = 24;
 
     text = new TextField();
     text.type = TextFieldType.INPUT;
     text.border = true;
     text.width = 250;
     text.height = 30;
     text.defaultTextFormat = font;
 
 
     var getDataBtn:LabelButton = new LabelButton();
          getDataBtn.label = "Get Tweets";
          getDataBtn.addEventListener(MouseEvent.CLICK,onGetDataClick);
 
     _verifyContainer.addChild(_spacer);
     _verifyContainer.addChild(label);
     _verifyContainer.addChild(text);
     _verifyContainer.addChild(getDataBtn);
     _verifyContainer.setSize(1024,600);
 
 
     removeChild(_authContainer);
     addChild(_verifyContainer);
 
     var authRequest:URLRequest = new URLRequest('http://api.twitter.com/oauth/authorize?oauth_token='+_requestToken.key);
     navigateToURL(authRequest);
}

Once the user comes back, puts in the PIN, and clicks the button, the application uses that information to build the request for the access token. By passing the PIN as the oauth_verifier property we get the access token we need to start requesting data from Twitter.

protected function onGetDataClick(event:MouseEvent):void
{
     var params:Object = new Object();
          params.oauth_verifier = text.text;
 
     _accessRequest = new OAuthRequest(OAuthRequest.HTTP_MEHTOD_GET,ACCESS_TOKEN_URL,params,_consumer,_requestToken);
 
     var accessUrlRequest:URLRequest = new URLRequest(_accessRequest.buildRequest(SIGNATURE));
     var accessLoader:URLLoader = new URLLoader(accessUrlRequest);
          accessLoader.addEventListener(Event.COMPLETE,onAccessRequestComplete);
}
 
protected function onAccessRequestComplete(event:Event):void
{
     _accessToken = getTokenFromResponse(event.currentTarget.data);
 
     var mainRequest:OAuthRequest = new OAuthRequest(OAuthRequest.HTTP_MEHTOD_GET,API_URL+'/1/statuses/friends_timeline.xml',null,_consumer,_accessToken);
 
     var getStatusURLRequest:URLRequest = new URLRequest(mainRequest.buildRequest(SIGNATURE));
     var getStatusLoader:URLLoader = new URLLoader(getStatusURLRequest);
          getStatusLoader.addEventListener(Event.COMPLETE,onStatusLoadComplete);
}

Requesting data is pretty straightforward. Using the consumer and access tokens we just got we can build a normal request, send it off, and then parse the data that comes back. In this case I’m going to go through and display usernames and status for the tweets.

protected function onStatusLoadComplete(event:Event):void
{
     _mainContainer = new Container();
     _mainContainer.flow = ContainerFlow.HORIZONTAL;
 
     var sendTweetContainer:Container = new Container(25);
          sendTweetContainer.containment = Containment.DOCK_TOP;
 
     var font:TextFormat = new TextFormat();
          font.align = TextFormatAlign.CENTER;
          font.bold = true;
          font.size = 24;
 
     twitterTextField = new TextField();
     twitterTextField.type = TextFieldType.INPUT;
     twitterTextField.border = true;
     twitterTextField.width = 500;
     twitterTextField.height = 30;
     twitterTextField.defaultTextFormat = font;
 
     var tweetLabel:LabelButton = new LabelButton();
          tweetLabel.label = "Tweet This";
          tweetLabel.addEventListener(MouseEvent.CLICK,onTweetClick);
 
          sendTweetContainer.addChild(twitterTextField);
          sendTweetContainer.addChild(tweetLabel);
 
     // Code for parsing the XML from the response
     var xml:XML = new XML(event.currentTarget.data);
     var statusList:XMLList = xml.children();
     var arr:Array = new Array();
 
     for(var i:int=0;i<statusList.length();i++)
     {
          var obj:Object = new Object();
               obj.label = statusList[i].user.name.toString() +': ' + statusList[i].text.toString();
          arr.push(obj);
     }
 
     // Create the DataProvider out of the parsed data
     var dataProvider:DataProvider = new DataProvider(arr);
     var list:List = new List();
          list.dataProvider = dataProvider;
          list.size = 100;
          list.sizeUnit = SizeUnit.PERCENT;
          list.setSkin(AlternatingCellRenderer);
 
 
     _mainContainer.addChild(list);
     _mainContainer.addChild(sendTweetContainer);
     _mainContainer.setSize(1024,600);
     removeChild(_verifyContainer);
     addChild(_mainContainer);
}

The next step is to enable the ability to send a tweet. This one is a bit different. For all of the previous calls we just created a params object, and sent that with the OAuthRequest. But when we’re using POST instead of GET, things have to be done differently. Soenke Rohde has a Flash Twitter library that I used for help, but essentially we have to strip the URL params from the original request and then reset them as URLVariables to match the POST request.

protected function onTweetClick(event:MouseEvent):void
{
     var params:Object = new Object();
          params.status = twitterTextField.text;
 
     // Use the same consumer and accessToken to update the Status
     var tweetRequest:OAuthRequest = new OAuthRequest(OAuthRequest.HTTP_MEHTOD_POST,API_URL+'/1/statuses/update.json',params,_consumer,_accessToken);
 
     var setStatusURLRequest:URLRequest = new URLRequest(tweetRequest.buildRequest(SIGNATURE));
          setStatusURLRequest.method = URLRequestMethod.POST;
 
          // use the replace function to strip out the status
          setStatusURLRequest.url = setStatusURLRequest.url.replace("&status=" + URLEncoding.encode(params.status),"");
 
     // Add the status as a URLVariable since it's a POST operation
     setStatusURLRequest.data = new URLVariables( "status=" + twitterTextField.text  );
 
     var setStatusLoader:URLLoader = new URLLoader(setStatusURLRequest);
          setStatusLoader.addEventListener(Event.COMPLETE,onSetStatusComplete);
}

And that’s pretty much all there is to it. You can grab the whole bit of code over on Snipplr. Just swap in your own information from Twitter and you should be set to go.

One thing to keep in mind is that this example makes you authorize each time you use the application. In reality that would be very annoying so if you were using this in production you’d want to save the consumer and accessToken somewhere so you didn’t have to create those each time.

PlayBook Development: Flex Mobile Framework or the QNX Components?

Flex or QNX

One of the first decisions you’ll have to make when you start building a PlayBook application is whether you want to use the Flex Framework for mobile devices or the QNX component set that ships as part of the AIR SDK for PlayBook. Even if you’re a Flex developer it’s not a straightforward decision so it’s important to be able to weigh the pros and cons of each side. I’ve been talking to a number of developers about Flex versus QNX and wanted to put down my thoughts to help developers decide. If you think I missed something, let me know in the comments and I’ll add it.

Performance

I don’t yet have a PlayBook to test the differences on, but in talking to people that do, performance generally seems like a wash. There’s something to be said for the fact that the QNX team is working really closely with the hardware to optimize their component set, but I also know the Flex team has made leaps and bounds in performance for Flex Mobile. When I get an actual device I’ll update this section.

Component Set

This is an area where I thought Flex would have a decided advantage. And if you count the entire component set, it does. But if you limit the comparison to mobile-optimized components, Flex ends up a little behind. For both platforms the core components are there, lists, buttons, etc. Flex has a TextArea component while QNX doesn’t, but QNX has a slider component while Flex’s hasn’t been mobile-optimized yet. Where QNX ends up winning though is in the number of components that are being built to support UI features of the PlayBook. QNX has support for a number of different dialog components including the LoginDialog component as well as built in support for lists with sections. If you’re sticking to the normal base components, Flex is a perfect fit, but the QNX components are a bit higher level and provide some specific UI elements that your users will see across the PlayBook that you can then add to your own application.

The Picker component from the PlayBook AIR SDK



The Picker component selected

Layout

Flex wins this hands down in my opinion. The layout system that the QNX components employ works just fine, and it’s elegantly simple. It uses a container system and I’ve been told it’s similar to Java. I did a screencast on how to use it and it generally accomplishes most of what you’ll need to do. But if you want to do anything above and beyond, the Flex layout system still rules. Having to use things like a Spacer container to align a component in the middle of the screen just seems hacky compared to being able to set properties on a Group. Plus, with Flex you can very easily create your own layouts, which means you can create far more customized looking applications with Flex than with the QNX components.

Skinning/Designer-Developer Workflow

Currently I see this one as a draw. The Spark components of Flex offer a lot of customization and on the desktop provide some great integration with tools like Flash Catalyst, Illustrator, and Photoshop. The only problem is that you don’t really get those integrations if you’re building mobile Flex applications because for most components you won’t want to use MXML-based skins, you’ll want to use AS3 skins. Because all of the designer-developer tooling uses MXML, you won’t be able to pull out all of the benefits of Spark on mobile devices. On the other hand, the QNX components don’t really have any designer developer workflow either. I found the skinning model for QNX to be similar in a lot of ways to the skinning model of Spark. Either way, when you skin these applications you’ll be doing a lot of ActionScript and relying a lot on images.

Mobile UI Paradigms

The TabNavigator UI Paradigm

The Flex mobile team has done a fantastic job of building the framework from the ground up with mobile user interface paradigms in mind. A base Flex Mobile application gives you an ActionBar, which can contain global content, an easy way to add and remove pages from within the application (complete with default transitions between screens), and components for tabbed navigation. With QNX you’re going to be creating most of those from scratch. On one hand it means that you can build up your own mobile UI paradigms if for some reason you don’t want to use what Flex has to offer. On the other hand, the paradigms that Flex Mobile uses are ubiquitous at this point and I’ve found myself trying to copy them in my PlayBook applications.

Flex Mobile's ActionBar Component

Native

This is one of those intangible things but I think it’s important to look at critically. Alex Payne noted some of the issues with AIR applications and how they compare to native applications (also read Ed Finkler’s post on the subject). One of the things I find most exciting about the PlayBook is that it’s the only platform where Flash is native. The QNX components that you use in the SDK are the exact same ones that are being used across the device. It’s very exciting to be able to build native UIs with my Flash/Flex skills. I’m obviously a big fan of Flex and AIR for a lot of things, but given the chance to build native apps, I’ll do it. And it couldn’t be easier for Flash developers to use the QNX components to create native PlayBook apps. That being said, the native APIs and device functionality are all exposed via ActionScript APIs so even if you’re building a Flex app you can still take advantage of the same native and device-specific functionality that developers who use QNX can. So it just comes down to how you want your application to feel compared to other applications on the device.

Another important consideration is being able to reuse your code to deploy to other devices. If you’re looking for a consistent brand across multiple devices and the ability to reuse big chunks of code, Flex is the only answer. You won’t be able to use the QNX components outside of the PlayBook environment so you’ll have to rewrite the application for iOS or for Android or for any of the other devices AIR supports.

Conclusion

I don’t really see a winner for either one of these because I think it comes down to developer skills and what kind of experience you’re going for. I’ve really enjoyed diving into the native QNX components because I like when applications have a native look and feel. If you’re primarily a Flex developer you’re probably going to miss some of the things that you’ll lose by moving to the QNX components. And someone may come up with a Flex skin that mirrors the look and feel of the QNX components so you can get the best of both worlds. Either way building applications for the PlayBook will be right up the alley of any Flash developer. It’s a device that has basically been built from the ground up to support Flash and I’ve been enjoying it immensely. I’m hoping to have a Pintley application out for the PlayBook at launch so if you’re a beer lover, check back here.

One good place to start hashing out the differences is to take a look at the developer documentation for each: Flex, QNX. That will give you a feel for some of what’s supported in each one. And again, I’ll be updating this post with comments so feel free to comment below or to drop me an email.

Creating Custom List Skins for the BlackBerry PlayBook

Disclaimer: I’m fairly certain this isn’t best practice. There is a ton of stuff going on with List and CellRenderer that I don’t fully understand. I’m hoping to sit down with the QNX team at some point and figure out how this might be done correctly, but for now I’ve found a way that works so I wanted to share it for anyone who is having issues.

Skinning a List is quite a bit different than skinning something simple like a Button or a TextInput box (which I talked about in my post here). It seems like the correct way to customize the look and feel of a list would be to create a CellRenderer which gives you access to the label for the list, and then swap out the graphics by creating a skin specifically for that CellRenderer. But my particular code didn’t rely on the label field so I skipped the first step and just created a CellRendererSkin that extends UISkin and implements ICellRenderer. By implementing ICellRenderer I get access to the data methods, but as you’ll see, that created some issues.

First off, here’s the list I wanted to create. It’s got an image and a few lines of text. You can see the regular state (white) and what it looks like when it’s selected (grey). I wanted a little bit more of a custom look so I put some space between each item and drew a rounded rectangle with a border around the content.

List Screenshot

To skin a list this way, the most important method is still initializeStates(). That method has to be overridden just like if we were skinning any other component, and it’s where we call setSkinState to associate a graphic with a state. A list has basically 8 different SkinStates: SkinStates.UP, SkinStates.UP_ODD, SkinStates.DOWN, SkinStates.FOCUS, SkinStates.DISABLED, SkinStates.SELECTED, SkinStates.DOWN_SELECTED, and SkinStates.DISABLED_SELECTED. I have no idea what FOCUS does, I’m not alternating my rows so I don’t care about ODD, and I decided I could live with my DOWN, SELECTED, and DOWN_SELECTED being the same. So here’s the code for my initializeStates() method

override protected function initializeStates():void
{
     _upSkin = new Sprite();    
     setSkinState(SkinStates.UP,_upSkin);
 
     _downSkin = new Sprite();    
     setSkinState(SkinStates.DOWN,_downSkin);
     setSkinState(SkinStates.DOWN_SELECTED,_downSkin);
     setSkinState(SkinStates.SELECTED,_downSkin);
 
     SkinStates              
     showSkin(_upSkin);
}

Here’s where things get a bit messier. With normal skinning, I could just start adding graphics to my sprites and then set the skin state accordingly. But what I found was that when the initializeStates() method got called, the width and height of the component hadn’t been set yet. So when I tried to draw a rectangle that used the height/width of the component, it would look scrunched. If you know the exact dimensions you want, you can just hard-code the values. But I wanted to be able to use this on different sized lists, so I wanted those dynamic values.

What I found was that if I overrode the setState(state:String) method, I could get the values for width/height there and then draw the correct graphics depending on whatever state was being set based on the height/width of each cell.

override protected function setState(state:String):void
{
     super.setState(state);
 
     var matrix:Matrix = new Matrix();
     matrix.createGradientBox(width,height,90/180*Math.PI);
 
     if(state == SkinStates.UP)
     {                   
          _background.graphics.clear();
          _background.graphics.beginGradientFill(GradientType.LINEAR,
                    [0xffffff,0xf2f2f2,0xffffff],[1,1,1],[0,127,255],matrix);
          _background.graphics.lineStyle(2,0x221206);
          _background.graphics.drawRoundRect(20,10,width-35,height-20,7,7);
          _background.graphics.endFill();
     }
     if(state == SkinStates.DOWN ||
          state == SkinStates.DOWN_SELECTED ||
          state == SkinStates.SELECTED)
     {                        
          _background.graphics.clear();
          _background.graphics.beginGradientFill(GradientType.LINEAR,
                    [0xaaaaaa,0xcfcfcf,0xaaaaaa],[1,1,1],[0,127,255],matrix);
          _background.graphics.lineStyle(2,0x221206);
          _background.graphics.drawRoundRect(20,10,width-35,height-20,7,7);
          _background.graphics.endFill();
     }              
}

Here I ran into another odd issue. Notice that I’m not adding the graphics to the state, but rather adding them to a _background Sprite. What I found is that when I would try to draw the graphics right on the skin sprite it would either overwrite my other content when the state changed and I couldn’t get it back, or the graphics wouldn’t display at all. I’m still not entirely sure why those two things happened and I went through so many iterations that I don’t remember the code that caused it. But what I found was that if I created a background Sprite and added that to the display list first, I could alter it depending on the state and it would draw correctly. That’s also why I have a _background.graphics.clear() call because the _background Sprite is in every state, it just needs to be redrawn when the state changes.

The next step was to add everything to the cell renderer and then to clean it up when the cell renderer goes away. All of the QNX components have an onAdded() and onRemoved() method that gets called when the object is added to or removed from the stage. So I just overrode those methods and added my content.

override protected function onAdded():void
{
     super.onAdded();
     addChild(_background);
     addChild(_image);
     addChild(_name);
     addChild(_brewery);
     addChild(_beerType);
     addChild(_ratingText);
     addChild(_avgRating);
}
 
override protected function onRemoved():void
{
     super.onRemoved();
     removeChild(_background);
     removeChild(_image);
     removeChild(_name);
     removeChild(_brewery);
     removeChild(_beerType);
     removeChild(_ratingText);
     removeChild(_avgRating);
}

There’s just one final step. As you’ll see in my code below, I set up most of the properties of the labels and the image in the constructor. But I don’t set any of the dynamic data. That’s because I had a really hard time finding out when I could access the data property of the cell renderer. There isn’t any data on init(), onAdded(), or initializeStates() so trying to set the dynamic data in those methods threw an error. I could access it in the setState() method, but I found that when I tried to set it there, the list wouldn’t display the values correctly. I finally figured out that it was because of the way the list is virtualized. The setState() method doesn’t get called when you initially scroll the list because the state hasn’t changed, just the data has. So I was seeing the values repeat when I’d scroll the list and didn’t see the correct value until I clicked on it and forced setState() to be called.

The solution was just to embrace the fact that I was implementing ICellRenderer and set all of the data in the data setter method. That set the data correctly for each cell and didn’t depend on the state at all. I also had to set the width of the Label objects there so that the text wouldn’t be cut off.

public function set data(data:Object):void
{
     _beer = data;
 
     _image.setImage(data.thumb);
 
     _name.text = data.beerName;
     _name.width = width-150;
 
     _brewery.text = data.brewerName;
     _brewery.width = width-150;
 
     _beerType.text = data.styleName;
     _beerType.width = width-150;
 
     if(data.avgRating > 3)
     {
          _avgRatingFormat.color = 0x4c9d17;
     } else if (data.avgRating < 1)
     {
          _avgRatingFormat.color = 0x9d1717;
     }
 
     _avgRating.text = data.avgRating;
     _avgRating.format = _avgRatingFormat;
}

Again, I want to stress that this probably isn’t the ideal way to do this. Especially if you have a pretty basic label you could just extend CellRenderer and override the draw() and drawLabel() method to do what you want. Or, like I said above, apply a special skin to that CellRenderer that handles all of the states for the list correctly. This was the rabbit hole that I went down though, and I found it to be kind of handy because I killed all of my birds with one class. Even if it’s an ugly class. Here’s the full code.

package com.pintley.components.listClasses
{
     import flash.display.GradientType;
     import flash.display.Sprite;
     import flash.filters.DropShadowFilter;
     import flash.geom.Matrix;
     import flash.text.TextFormat;
 
     import qnx.ui.display.Image;
     import qnx.ui.listClasses.ICellRenderer;
     import qnx.ui.skins.SkinStates;
     import qnx.ui.skins.UISkin;
     import qnx.ui.text.Label;
 
     public class BeerCellRendererSkin extends UISkin implements ICellRenderer
     {
          protected var _beer:Object;
          private var _row:int;
          private var _column:int;
          private var _section:int;
          private var _index:int;    
 
          private var _yOffset:int = 12;
          private var _xOffset:int = 35;
 
          /**
           * Skins
           **/
          protected var _upSkin:Sprite;
          protected var _selectedSkin:Sprite;
          protected var _downSkin:Sprite;
          protected var _disabledSkin:Sprite;
 
          /**
           * Cell Renderer content
           **/
          // I use a background sprite because I want to
          // make sure it's the lowest layer. Then I can just
          // modify the lowest layer without overwriting the text.
          protected var _background:Sprite;
          protected var _name:Label;
          protected var _brewery:Label;
          protected var _beerType:Label;
          protected var _ratingText:Label;
          protected var _avgRating:Label;
          protected var _image:Image;
 
          /**
           * TextFormats
           **/
          protected var _nameFormat:TextFormat;
          protected var _breweryFormat:TextFormat;
          protected var _beerTypeFormat:TextFormat;
          protected var _ratingTextFormat:TextFormat;
          protected var _avgRatingFormat:TextFormat;
 
          public function BeerCellRendererSkin()
          {
               super();
 
               /**
                * TextFormats
                **/
               _nameFormat = new TextFormat();
               _nameFormat.color = 0xbd5251;
               _nameFormat.size = 16;
               _nameFormat.bold = true;
 
               _breweryFormat = new TextFormat();
               _breweryFormat.color = 0x525252;
               _breweryFormat.size = 14;
               _breweryFormat.bold = true;
 
               _beerTypeFormat = new TextFormat();
               _beerTypeFormat.color = 0x525252;
               _beerTypeFormat.size = 14;
 
               _ratingTextFormat = new TextFormat();
               _ratingTextFormat.color = 0x79523e;
               _ratingTextFormat.size = 12;
               _ratingTextFormat.bold = true;
 
               _avgRatingFormat = new TextFormat();
               _avgRatingFormat.color = 0x000000;
               _avgRatingFormat.size = 14;
               _avgRatingFormat.bold = true;
 
               /**
                * CellRenderer Content
                **/
               _image = new Image();
               _image.x = _xOffset;
               _image.y = 20;
               _image.filters = [new DropShadowFilter(3,45,0x000000,.5,4,4,.5)];
 
               _name = new Label();
               _name.x = _xOffset+80;
               _name.y = _yOffset;
               _name.format = _nameFormat;
 
               _brewery = new Label();
               _brewery.x = _xOffset+80;
               _brewery.y = _yOffset+20;
               _brewery.format = _breweryFormat;
 
               _beerType = new Label();
               _beerType.x = _xOffset+80;
               _beerType.y = _yOffset+35;
               _beerType.format = _beerTypeFormat;
 
               _ratingText = new Label();
               _ratingText.x = _xOffset+80;
               _ratingText.y = _yOffset+55;
               _ratingText.format = _ratingTextFormat;
               _ratingText.text = "AVG Rating";
 
               _avgRating = new Label();
               _avgRating.x = _xOffset + 150;
               _avgRating.y = _yOffset+54;
 
               _background = new Sprite();
          }
 
 
 
          /**
           * Getters/Setters
           **/
 
          public function get data():Object
          {
               return _beer;
          }
 
          public function set data(data:Object):void
          {
               _beer = data;
 
               // Set the text and images for the
               // label after we get data from the list.
               _image.setImage(data.thumb);
 
               _name.text = data.beerName;
               _name.width = width-150;
 
               _brewery.text = data.brewerName;
               _brewery.width = width-150;
 
               _beerType.text = data.styleName;
               _beerType.width = width-150;
 
               if(data.avgRating > 3)
               {
                    _avgRatingFormat.color = 0x4c9d17;
               } else if (data.avgRating < 1)
               {
                    _avgRatingFormat.color = 0x9d1717;
               }
 
               _avgRating.text = data.avgRating;
               _avgRating.format = _avgRatingFormat;
          }
 
          public function get index():int
          {
               return _index;
          }
 
          public function set index(value:int):void
          {
               _index = value;
          }
 
          public function get row():int
          {
               return _row;
          }
 
          public function set row(value:int):void
          {
               _row = value;
          }
 
          public function get column():int
          {
               return _column;
          }
 
          public function set column(value:int):void
          {
               _column = value;
          }
 
          public function get section():int
          {
               return _section;
          }
 
          public function set section(section:int):void
          {
               _section = section;
          }
 
          public function get isHeader():Boolean
          {
               return false;
          }
 
          /**
           * Overriden Functions
           */
 
          override protected function initializeStates():void
          {
               // Set up the skin states
               _upSkin = new Sprite();    
               setSkinState(SkinStates.UP,_upSkin);
 
               _downSkin = new Sprite();    
               setSkinState(SkinStates.DOWN,_downSkin);
               setSkinState(SkinStates.DOWN_SELECTED,_downSkin);
               setSkinState(SkinStates.SELECTED,_downSkin);
 
               showSkin(_upSkin);
          }
 
          override protected function setState(state:String):void
          {
               super.setState(state);
 
               var matrix:Matrix = new Matrix();
               matrix.createGradientBox(width,height,90/180*Math.PI);
 
               // Check to see what state is being set and then draw
               // the graphics on the background Sprite accordingly.
               if(state == SkinStates.UP)
               {                   
                    _background.graphics.clear();
                    _background.graphics.beginGradientFill(GradientType.LINEAR,
                              [0xffffff,0xf2f2f2,0xffffff],[1,1,1],[0,127,255],matrix);
                    _background.graphics.lineStyle(2,0x221206);
                    _background.graphics.drawRoundRect(20,10,width-35,height-20,7,7);
                    _background.graphics.endFill();
               }
               if(state == SkinStates.DOWN ||
                    state == SkinStates.DOWN_SELECTED ||
                    state == SkinStates.SELECTED)
               {                        
                    _background.graphics.clear();
                    _background.graphics.beginGradientFill(GradientType.LINEAR,
                              [0xaaaaaa,0xcfcfcf,0xaaaaaa],[1,1,1],[0,127,255],matrix);
                    _background.graphics.lineStyle(2,0x221206);
                    _background.graphics.drawRoundRect(20,10,width-35,height-20,7,7);
                    _background.graphics.endFill();
               }              
          }
 
          override protected function onAdded():void
          {
               super.onAdded();
               addChild(_background);
               addChild(_image);
               addChild(_name);
               addChild(_brewery);
               addChild(_beerType);
               addChild(_ratingText);
               addChild(_avgRating);
          }
 
          override protected function onRemoved():void
          {
               super.onRemoved();
               removeChild(_background);
               removeChild(_image);
               removeChild(_name);
               removeChild(_brewery);
               removeChild(_beerType);
               removeChild(_ratingText);
               removeChild(_avgRating);
          }
     }
}

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();
		}
 
	}
}

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.

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.

Getting Started With jQuery Mobile

I’ve been spending a lot of time recently getting my JavaScript and HTML chops up because I think that Adobe is going to have a lot of impact in the HTML5/JS/CSS3 world next year and as a web developer, HTML and JavaScript have become incredibly interesting (and fun) with the rise of frameworks and the mobile landscape. As part of that experiment I’ve been digging into jQuery mobile. I’m obviously not a jQuery pro but after playing with both Flex “Hero” for mobile applications and now jQuery mobile, I thought it would be helpful to do a quick getting started post on jQuery mobile and talk about some of the UI paradigms it uses while walking through a basic application. jQuery mobile is in alpha2 right now, so it’s a little rough around the edges, but still very capable of providing a basis to create mobile applications.

jQuery Mobile divides the world up into pages, which are essentially just screens. Each page has three areas for content; the header, the main content, and the footer. Those are each defined by setting the data-role attribute within the div tag to specify “header”, “content”, or “footer”. The data-role attribute is also used to set up pages by setting it to the “page” value. With jQuery mobile the pages can be set up in different HTML files or all in one file. For this example I’m going to build a two-screen application. The first screen has a button that the user clicks to enable the use of the Geolocation APIs and the second screen is going to be a list of Wikipedia entries that are close to the coordinates. So I have two pages each with the three content types set using data-role.

 
<div>
 
<div>
 
<h1>Find Location</h1>
</div>
 
 
<div>
 
This application will use the <a href="http://geonames.org">Geonames</a> API and your location to bring back a list of Wikipedia articles about features that are near you. To get started, click the button below and allow the application to read your geolocation information.
 
        <input id="getLocation" type="button" value="Get My Location" /></div>
 
 
<div>
 
<h4>By <a href="http://blog.digitalbackcountry.com">Ryan Stewart</a></h4>
</div>
</div>
 
 
<div id="dashboard">
 
<div>
 
<h1>Data List</h1>
</div>
 
 
<div>
 
<ul id="wikiList">
</ul>
</div>
 
 
<div>
 
<h4>By <a href="http://blog.digitalbackcountry.com">Ryan Stewart</a></h4>
</div>
</div>

With my structure set up I can now start adding jQuery-mobile specific interactions. As soon as I add the jQuery mobile libraries to my application it knows how to parse the data-role attributes and themes the application accordingly. What you get is something like on the right. Notice how it automatically styles the header and footer sections as well as providing a nice, big, touchable button and some styling on the footer link to indicate that it can be tapped.

With the external jQuery and jQuery mobile files included I can move on to adding interactivity. I start by adding a click handler to my button, which will go out and grab the geolocation information if it’s available. It’s just using the HTML5 geolocation API, which is supported by most modern browsers. When the user clicks the button, it will get the current position and then call onSuccess or onError depending on whether or not the API call worked.

$(document).ready(function(){
     // Add a click listener on the button to get the location data
     $('#getLocation').click(function(){
          if (navigator.geolocation) {
               navigator.geolocation.getCurrentPosition(onSuccess, onError);
          } else {
               // If location is not supported on this platform, disable it
               $('#getLocation').value = "Geolocation not supported";
               $('#getLocation').unbind('click');
          }
     });
 
});

Next I set up a namespace for the geonames information. It has variables for the baseURL and then a search function with the getJSON function that goes out and calls the Geonames API. The function loops through each one of the results and then creates an <li> tag and appends the data we want to show to it, including the title of the Wikipedia article, the distance, and a summary. This is all straight jQuery. But after that we get into a couple of jQuery mobile-specific parts.

// create the geonames namespace for calling the API
var geonames = {};
     geonames.baseURL = "http://ws.geonames.org/";
     geonames.method = "findNearbyWikipediaJSON";
     geonames.search = function(lat,lng){
 
     // get the data in JSON format from Geonames
     $.getJSON(geonames.baseURL + geonames.method + '?formatted=true&amp;lat=' + lat + '&amp;lng=' + lng + '&amp;style=full&amp;radius=10&amp;maxRows=25',function(data){
          // Loop through each item in the result and add it to the DOM
          $.each(data.geonames, function() {
               $('
 
 
')
               .hide()
               .append('<a href="http://'+this.wikipediaUrl+'">
 
<h2>'+this.title+'</h2>
 
 
</a>
 
'+ this.summary + '
 
<span class="ui-li-aside">
 
<h5>'+this.distance+' (km)</h5>
 
 
</span>')
               .appendTo('#wikiList')
               .show();
          });
          // Once the data is added to the DOM, make the transition
          $.mobile.changePage('#dashboard',"slide",false,true);
 
          // refresh the list to make sure the theme applies properly
          $('#wikiList').listview('refresh');
     });
};

The page model in jQuery mobile gives you a lot of control over how those pages are displayed. By default, when you set up an anchor tag it will play a default transition and go to the page you want. If that page is an external page it will load that external page in the browser. If it’s an internal, local link (with a hash), then jQuery will perform a slide transition and then swap out the old page content with the new one. It also updates the URL by default so you can use the back button that’s included in the header or the back button on your device/browser and go back to the original page. Pretty slick that this all happens by default. However, if you want more control you can use the $.mobile.changePage method, which is what I’m using because I wanted to only change the page after I had parsed the data from Geonames. With mobile.changePage I specify the URL, which in this case is just the id of the new div tag, the transition I want, whether I want the animation to be reversed, and finally whether I want to change the URL so the user can go back to the first screen. By default when you use mobile.changePage the last attribute is set to false so it won’t update the URL in the navbar. In this case I want them to be able to update their location when they move so I set the property to true.

The last thing I have to do is refresh the list to make sure the styles are applied to the new data. Lists are incredibly sick in jQuery an jQuery mobile. By using the data-role attribute and the data-theme attribute you can create some very powerful lists. In this case, I’m just using the default list so my <ul> tag has an id of wikiList, and a data-role of “listview”. That means any <li> tags I add to it will become jQuery mobile list items. And that’s exactly what happens in my Geonames callback function when I iterate through each item. It’s creating <li> elements with my data and appending them to my wikiList <ul> tag. Then when I call .listview('refresh') on my wikiList, it styles them appropriately. One cool feature of jQuery mobile lists is that there are a few different properties I can set to add some stylistic touch. For example, to show distance I’m using a span tag with the class set to ui-li-aside. That class will right-justify the content so my distance shows up on the right side.

And that’s pretty much all there is to it. The last bit of code I have is just the onSuccess and onError functions that handle the geolocation API.

// Success function for Geolocation call
function onSuccess(position)
{
     geonames.search(position.coords.latitude,position.coords.longitude);
}
 
// Error function for Geolocation call
function onError(msg)
{
     alert(msg);
}

You can check out the full application here and see it in action. This is just scratching the surface of what you can do with jQuery mobile but hopefully you got a sense of the page model and how to move back and forth between screens. In some later posts I’ll hopefully cover the new gestures, the new components, and some of the ways to lay out content.