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.
Introduction
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.
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();
upSkin = new UpGraphic();
setSkinState(SkinStates.UP,upSkin);
downSkin = new DownGraphic();
setSkinState(SkinStates.DOWN,downSkin);
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 = new Sprite();
drawSprite(upSkin,2);
setSkinState(SkinStates.UP, upSkin );
downSkin = new Sprite();
drawSprite(downSkin,3);
setSkinState(SkinStates.DOWN, downSkin );
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 Button
s, 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
{
upSkin = new UpGraphic();
setSkinState(SkinStates.UP,upSkin);
downSkin = new DownGraphic();
setSkinState(SkinStates.DOWN,downSkin);
selectedSkin = new DownGraphic();
setSkinState(SkinStates.SELECTED,selectedSkin);
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.
Related Posts
- Building Custom Components in Flex 4: SkinParts
- Getting Started with the BlackBerry PlayBook and Adobe AIR
- The Camera API and Geolocation Exif Data on AIR for Android
- Fun with Flex 4 Skinning and the ToggleButton
- Couple of Good Skinning With Flex 4 Blog Posts
History
- 18th April, 2011: Initial version