MAX Reflections

I’m sitting down with some tea while my little girl is taking a nap feeling the big exhale from MAX. The energy of the past few days has been largely fantastic and I always find MAX to be rejuvenating both from a professional standpoint and a personal standpoint. Getting to connect with the community and my colleagues at Adobe has been great. In the contrast to the buzzing of MAX, the current deep quiet of my house leaves me reflecting a bit on the week.

This will go down as a very transformational MAX. The announcement of the Creative Cloud and the fact that it will include all of Creative Suite Master Collection as well as the touch tools and services (including TypeKit) is one of the biggest things I’ve seen from Adobe in a long time. And it feels like we’re jumping in with both feet and getting back to the core of what Adobe does: empowering designers to create with great tools. I thought the news about the single edition of the Digital Publishing suite was a perfect example of that. It makes the blossoming world of digital publishing accessible to more people.

The PhoneGap announcement was, for me, the most significant announcement of the week. By acquiring Nitobi (fantastic guys) and contributing the PhoneGap project to the Apache Foundation, Adobe took a huge, huge step into the world of HTML5. It was a perfect way to start a day 2 keynote that focused on the things Adobe is doing to be a part of the HTML5 ecosystem.

Based on the Twitter stream there seemed to be a feeling that the lack of traditional Flash indicated that Adobe is giving up on it. I think that misses the big picture. With the Nitobi acquisition and the embracing of PhoneGap, Adobe is making a significant and meaningful bet on the web and cross-platform mobile applications. This can’t be overstated. For Flash developers we have AIR, which will let you build cross-platform mobile applications. For HTML developers we have PhoneGap, which will let you create cross-platform mobile applications. Both are web technologies that don’t require developers to be locked into a specific operating system or type of device. You see the same thing with our digital publishing suite; it doesn’t matter if you want to deploy on iOS, Android, or PlayBook, you can. And that’s possible largely because of the web formats that go into creating the DPS apps.

This isn’t about Flash versus HTML, this is about supporting creative and interactive content across the broadest platform in the world: the web. Whether it’s mobile apps or browser content; animations, interactive web applications, or 3D gaming experiences, Adobe genuinely believes that the web is the best way for our customers to deliver their creations. By making PhoneGap a cornerstone of our story, I think we’ve proven our commitment to that mission.

I’m glad I was at MAX to see all of this in person.

Edit: This is a great piece by Daryl Taft of eWeek that talks about Flash and HTML. And it’s great to see that the “and not or” message is getting picked up. But what I like about this particular message is that when you follow it upstream a bit more, it just means we love the web. And if that’s the case (and I feel like it is) then the technology becomes secondary to the goals of helping people create cross-os and cross-device content with web technologies.

Flash Player 11, AIR 3, and Flex/Flash Builder 4.6

Today is a pretty big day for Adobe developers. We’re officially announcing Flash Player 11, AIR 3, and Flex 4.6 and Flash Builder 4.6. The bits will be available in early October, but we’re announcing things today to help provide developers with information on what’s coming. I’ve been at Adobe for 4 years now and it’s been a very interesting 4 years as the landscape has evolved. It’s definitely been an up and down ride for Adobe developers, but the world has never been a better place for interactive developers, and these set of releases provide a ton of functionality aimed at helping Adobe developers create content in the most cutting edge places.

Gaming

We’ve been doing a lot of work to help enable console-esque games on top of the Flash Platform. Flash Player 11 includes Stage3D, which is going to open up a whole new world for game developers. Zombie Tycoon and Tanki are initial examples of what can be done and I can’t wait to see what comes of it. I’m reminded very much of the early days of Flash where a bunch of creative people were given a technology that was pretty open-ended and poked and prodded to create a bunch of very cool things. I think we’ll see that kind of revolution with Stage3D because of the ubiquity of Flash and the creativity of our developer community. Also in the gaming bucket is a framework we’re working on called Starling, which leverages Stage3D to create a super-fast way of doing parts of 2D games. It’s a great merger between the underlying technology/performance benefits of Stage3D and the kinds of things people want to do in 2D games. I think it’s also going to see some traction beyond games as agencies start to use it to enhance 2D content.

Mobile Applications

Flex and AIR have really found a great place in mobile applications. The performance enhancements in 2.7 made building native-experiences with AIR possible and we’ve seen some great examples of that in action including Machinarium and Caltrain Times. I’ve been impressed with performance on my 100 Days of Exercise application on iOS. I’m incredibly, incredibly excited by what this means for Flash. There’s a definite need to create mobile apps that can be deployed to multiple application stores. The Flash Platform provides a way to create great looking, high-design applications with near-native performance that can run on multiple devices. That’s a big deal.

And this release of AIR 3 goes where we haven’t gone before on the Flash Platform with native extensions. Now if there are features that aren’t included in AIR, like access to a credit card reader, you can build those extensions in native code and then link them to your AIR applications and leverage those libraries. It’s a great mix of native for specific use cases and AIR/Flash for fantastic user interfaces. It’s a big, big, big deal to be able to extend the platform and it’s a huge step.

I also think we have one of the best mobile-tool chains out there. Flash Builder 4.6 is going to help with creating those native extensions while also enabling the use of captive runtime in AIR so your applications don’t need to rely on the external AIR runtime on Android. Combine that with the enhancements that are coming in Flex 4.6 and it adds up to a world class mobile development platform that lets you reach more devices that matter. Flex 4.6 is especially exciting because of the new components that have been added. Flex and AIR are far and away the best toolset for interactive developers or any mobile developer who needs to create content for multiple devices. The apps you can build with Flex and AIR are going to stand out from the boring, standard apps that have started to litter app stores. Creativity will win the day and creativity is at the core of Flex/AIR.

Beyond

So this is a huge release, and I’m excited. But I’m also excited about the future of Adobe and how we are responding and will continue to respond to the evolving marketplace. As Danny Winokur, VP and GM of the Flash Platform, said recently:

“We’re not so concerned about what the right technology is for that as long as we’ll be able to deliver those experiences. We’re working with Microsoft and other members of the HTML community including Google, Apple, and others to enable rich experiences on HTML5.”

This is not a technology war. Adobe is about enabling developers to build the best possible experiences with the technology they want. We want to build tools and services that cater to that ethos. That takes the form of cutting-edge gaming features like Stage3D and world-class mobile app features with Flex, AIR and Flash Builder. But HTML5 is exciting for a lot of reasons, and Adobe will help developers there as well. If you’re an interactive developer, the future is very, very bright for you.

So you better get a good pair of sunglasses.

The Implications of Every Flash Developer Being a Mobile Developer

There’s an article in the Wall Street journal today about the demand and insufficient supply of mobile developers, which is becoming a huge problem for companies as mobile strategy becomes more and more critical. One of the main problems, as the WSJ draws out, is that these mobile platforms are relatively new so it’s tough to find developers with a lot of experience. Many companies are turning to good developers and retraining them as mobile developers to fill demand.

If a software engineer doesn’t have mobile experience, the company has sometimes been willing to spend several weeks training the engineer to work on mobile platforms, Mr. Rosenthal said.

Given the mismatch between supply and demand, many companies say they have no choice but to retrain software engineers in the art of mobile development. In the last year, Major League Baseball’s Internet company MLB.com nearly doubled the number of mobile engineers it has to 19, said MLB.com CEO Bob Bowman.

I thought this article was a perfect complement to the announcement this week of Flash Builder 4.5 and Flex 4.5, which are focused explicitly on helping Flex developers build applications for mobile devices like iOS, Android, and the PlayBook. There is obviously huge demand for mobile applications, and because of scarce supply and experience, existing developers are going to have to think about how they can gain mobile experience. Flex mobile does a fantastic job of making that learning curve smaller by letting Flex developers use what they already know and providing some key mobile features (like ViewNavigator for managing views, the ActionBar for managing global navigation, etc) so that they can quickly turn out Flex applications for these mobile devices.

If you’re a Flash or Flex developer, then with AIR for mobile devices and Flex 4.5, you’ve got the skills to go out and build applications for the biggest platforms. That’s a huge advantage to you as a developer and for any developer who doesn’t want to get locked into a specific platform. As the chart below shows, there are a lot of people looking for experts in iPhone, Android, and BlackBerry, but there are a lot more people who are looking for Flash experts. With this release, you get the best of both worlds.

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.

Adobe Refresh – Australia, Seoul, Hong Kong and Singapore

I’m really excited to be joining up with the crew for Adobe Refresh, which will cover some of the latest and greatest features for the Flash Platform as well as show off Adobe’s HTML story over a couple of weeks in March. We’re hitting a total of 6 cities in Australia, Hong Kong, Seoul, and Singapore. I’ll be tagging along with Richard Galvan, Paul Burnett, and Michael Stoddart.

The sessions are going to cover Flash Pro, HTML5, Flex/Flash Builder, Digital Publishing, and a Q&A for answering any questions you might have about the Adobe stack. Plus we’ll be showing off some things that most people won’t have seen live yet, so if you come, you’ll get a first crack at seeing some very cool stuff coming up from Adobe.

Here’s the full list of cities:

I’m looking forward to being able to see all of the community members in those countries as well as getting to show off the latest and greatest with the Flash Platform. I’ll be talking about multiscreen development with Flex and Flash Builder and there is some VERY good stuff for developers on the horizon when it comes to building apps across devices. This will be a great event to see it all come together.

Using The PlayBook AIR SDK with Flex “Hero” Mobile Projects

When I first started doing PlayBook development with Flash Builder “Burrito” I found it kind of confusing how the PlayBook SDK installed itself. During the install process, the PlayBook AIR SDK installer prompts you for two locations; one is the location of your Flash Builder installation and the second is an existing AIR 2.5 SDK installation. If you’re using Burrito, it drops a new SDK into the sdk folder of the Burrito install (blackberry-tablet-sdk-0.9.x) and then defaults to the 4.5.0 sdk in the next screen as the AIR 2.5 SDK.

If you’re used to doing Android development then the first thing you probably did was go to create a new Flex Mobile or ActionScript Mobile Project using the PlayBook SDK. Unfortunately you’ll get this screen:

Luckily, when you selected the existing AIR 2.5 SDK (assuming you put it in the default 4.5.0 folder or any version of the “Hero” SDK with mobile components) the PlayBook installer has already linked the PlayBook SDK to that sdk. So when you create a Flex Mobile project with the Flex Hero SDK set you’ll see the required QNX .swc files already linked:

But I ran into an issue when I tried to upgrade. My colleague Mihai has a great runthrough of how to migrate from the 0.9.1 PlayBook SDK to the 0.9.2 SDK. But after I tried to upgrade all of my Flex Hero projects kept referring to the old 0.9.1 SDK. And any new Flex Hero projects I created would reference the old 0.9.1 installation. What I discovered was that something during the install process wasn’t overwriting the link in the 4.5.0 folder to the old SDK.

It seems like there is a bug that won’t overwrite the reference to the 0.9.1 correctly but the SDK is referenced in the Flash Builder preferences under Target Platforms. I just had to change it from 0.9.1 to 0.9.2 and all of my old projects as well as all of the new projects started using the new version.

Presenting Node.js and Flash at FITC Amsterdam – Early Bird Ends Friday

FITC Amsterdam 2011If you’re on the fence for going to FITC Amsterdam you’re running out of time to lock in the early bird pricing. It ends on Friday, so register now!

I’m going to be presenting on Using Node.js and Flash together to create real-time, scalable applications using a combination of Flash and JavaScript. Node.js has been getting a lot of attention and so it should be a fun session. One of the coolest things this year about FITC is that there will be a ton of diversity in the talks. We’ve got everything from multi-screen, Flash, HTML5, 3D, gaming, so there really is something for everyone.

It’s going to be a monumental year for Flash developers with a lot of open questions and some great new technology to play with. FITC Amsterdam will give you some insight into how those technologies can be used and how to navigate the web application landscape.

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

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.

Android, Flash, Free Beer, Pizza

If you’re in San Francisco this week you’ll want to swing by the free Droid Does Flash event that we’re putting on with Motorola on Thursday, September 16th, at the Adobe offices in San Francisco.

Droid Does Flash

It’s going to be a chance to check out some of Motorola’s new devices, win some software, drink free beer, and hear about optimizing content for Flash and devices. I’ll be talking about where Flash is in the mobile world and how we got here including some of the things we did to make the runtime sing on Android. After that, it’s all about networking, hanging out with some of the folks from Adobe, and drinking free beer. We’ve also got a pretty cool beer mug for all of the attendees that lets you show your malty-enthusiasm for Flash and Android.

Registrations are starting to fill up, so I encourage you to hit the Eventbrite site and sign yourself up.