Chapter 11

Beyond Mobile: Tablets and TV

Google and Adobe are working hard to extend the reach of the Android platform and the AIR runtime, respectively. Android has expanded onto tablets like the Motorola XOOM and the Samsung Galaxy Tab, and even into your living room with Google TV. This opens still more potential platforms for your AIR for Android applications! In addition, Research In Motion, known for its BlackBerry phones, has released its own tablet, called the PlayBook. The PlayBook is fully Flash-compatible and therefore provides yet another opportunity for your Flex and Flash applications to reach a new audience.

This chapter will look at some of the special considerations that are required to take your mobile application and move it to the larger screens of tablets and TVs.

Scaling the Screen

With a larger screen comes more freedom in the design of your interfaces. With more freedom comes more responsibility. Tablet users expect your application to make good use of the extra space that the larger screen provides. Figure 11–1 shows the MusicPlayer application from Chapter 8 running on a Motorola XOOM with a 10.1-inch screen. While the application is usable, the combination of the low pixel density and the large screen leads to small, stretched-out controls and a lot of wasted space. We can, and will, do better.

The motivation to do so comes from the fact that the Android tablet space is exploding since the introduction of Android 3.0, a version of Android specifically made for the larger screens of tablets and TVs. In addition to the existing Android 2.2 tablets—the Dell Streak and Samsung Galaxy Tab—there are now the Motorola XOOM and the Samsung Galaxy Tab 10.1, both of which run the latest versions of Honeycomb (the code name for Android 3.x). In addition, Toshiba, Sony, ASUS, and Amazon are all expected to release Honeycomb tablets in 2011.

Clearly this is a market segment that any application developer will want to take seriously. Applications that have been modified specifically to support these larger tablet screens will have a considerable advantage over those that haven't.

images

Figure 11–1. The MusicPlayer application running on a Motorola XOOM tablet

The first step is to familiarize you with the hardware. Most tablets have more powerful processors and more memory than the average smartphone. Table 11–1 shows a comparison of the displays of the popular Android tablets currently on the market. The table shows that most tablets are around 160 dpi, with larger, higher-resolution screens. With the combination of more powerful processors and large screens, you might be tempted to assume that your application will run faster than it does on a phone. This is not a good assumption to make, especially if your application is graphics-bound rather than CPU-bound. Unless they take advantage of hardware acceleration, graphically intense applications will often run slower on tablets due to the larger number of pixel calculations that must be done for the larger screens. As always, run performance tests and optimize as required.

Images

Whenever you are considering moving your application to a new platform, you should always take the time to study existing applications to determine what design patterns and conventions are in use. Figure 11–2 shows some existing Android tablet applications. From the upper left and proceeding clockwise, we see: Flixster, Newsr and TweetComb by Locomo Labs,2 and Google's Movie Studio. What common patterns and conventions do you see?

Notice how, especially in landscape orientation (as shown), the applications all make use of the extra screen space to show multiple views? Unlike similar phone applications, Flixster and Newsr show their master and details view together on one screen rather than having to transition to a separate details view. TweetComb takes advantage of the extra space to show multiple columns of tweets, while Movie Studio gives you larger, easier-to-use controls. Also note the inclusion of more actions in the title bar (the ActionBar in a Flex application). We can make similar modifications to our MusicPlayer application and thereby transform it to a full-blown tablet interface, similar to those pictured in Figure 11–2.

When thinking about modifications that can be made to the tablet version of the MusicPlayer, one thing that comes immediately to mind is to use the extra space in the SongView to display the additional metadata that there simply wasn't room for on the phone version of the application. This sort of simple modification is an ideal candidate for the first technique that we will examine for extending your application to a new screen: state-based customization.

__________

1 Technically, the HTC Flyer runs Android 2.3 (code name Gingerbread) instead of Android 3.x, but your AIR for Android programs will run on Gingerbread as well.

images

Figure 11–2. Popular applications running on an Android tablet

State-Based Customization

We have already shown how to customize your application's UI layout using the landscape and portraitView states. This technique takes that idea and expands upon it. Instead of just portrait and landscape, you would define the four combinations of state that you need to support each orientation for phones and tablets. Therefore your hypothetical MXML code would look something like Listing 11–1.

Listing 11–1. A First Cut at Adding Separate States for Phone and Tablet

<s:states>
  <s:State name="portraitPhone"/>
  <s:State name="landscapePhone"/>
  <s:State name="portraitTablet"/>
  <s:State name="landscapeTablet"/>
</s:states>

<s:Group width="100%" height="100%">
  <s:layout.landscapePhone>
    <s:HorizontalLayout verticalAlign="middle" paddingLeft="10"/>
  </s:layout.landscapePhone>

  <s:layout.landscapeTablet>
    <s:HorizontalLayout verticalAlign="middle" paddingLeft="10"/>
  </s:layout.landscapeTablet>

  <s:layout.portraitPhone>
    <s:VerticalLayout horizontalAlign="center" paddingTop="10"/>
  </s:layout.portraitPhone>

<s:layout.portraitTablet>
  <s:VerticalLayout horizontalAlign="center" paddingTop="10"/>
</s:layout.portraitTablet>

<s:Group width.portraitPhone="{height*0.4}" height.portraitPhone="{height*0.4}"
               width.landscapePhone="{width*0.4}"
               height.landscapePhone="{width*0.4}"
               width.portraitTablet="{height*0.3}"
               height.portraitTablet="{height*0.3}"
               width.landscapeTablet="{width*0.3}"
               height.landscapeTablet="{width*0.3}">
  <!-- And so on… -->
</s:Group>

We now have four states in our View: a landscape and a portrait version for a phone and a tablet. These are each enumerated in the <s:states> section using the <s:State> element. Once the states are defined, you can use Flex's state-specific attribute declarations, such as width.portraitPhone, to customize the layouts, spacing, and even the visibility of any component in your View's user interface. As an example, the Group defined in our hypothetical code listing includes a customized width and height for each of our possible states.

As you can see, the major drawback of this technique is the proliferation of state-specific attribute declarations. You now need four of everything! Luckily there is a way to mitigate this problem.

Using State Groups

State groups are a way to assign multiple states—a group of states—to just one state declaration. Take the following state declaration:

<s:State name="portraitPhone" stateGroups="portrait,phone"/>

This says that when we set the currentState of our View to be portraitPhone, we will activate any attribute declarations we have that are modified by the portraitPhone, portrait, or phone states. This allows us to define MXML attributes using combinations of these states:

  • attributeName.portraitPhone: This will apply only to phones in portrait orientation.
  • attributeName.portrait: This will apply to phones or tablets in portrait orientation.
  • attributeName.phone: This will apply to phones in landscape or portrait orientation.

This gives you much more flexibility in declaring your attributes and eliminates a lot of code duplication. Now that we no longer have the standard landscape and portrait states defined, Flex will no longer automatically set our View state. This is something we will take care of manually by overriding the getCurrentViewState method to return one of our new states based on the size and current orientation of the screen, as shown in Listing 11–2.

Listing 11–2. Returning Customized View States

override public function getCurrentViewState():String {
  var isPortrait:Boolean = height > width;
  var isTablet:Boolean = … // A calculation based on screen size or resolution.

  var newState:String = (isPortrait ? "portrait" : "landscape") +
            (isTablet ? "Tablet" : "Phone");

  return hasState(newState) ? newState : currentState;
}

The new state is determined by two Boolean variables. The isPortrait variable is determined easily by comparing the View's width and height. The isTablet variable is a little more complex. You can use the resolution of the screen by testing to see if the x or y dimension is larger than 960, which is the largest resolution currently in use on a phone. A more reliable method is to use the screen resolution and pixel density to determine the physical size of the screen. Then you can assume that anything over 5.5 inches is a tablet device. An example of this calculation is shown in the onViewActivate function in Listing 11–4.

Now we can get back to the idea of adding more information from the song's metadata to the UI. There are four things that would be nice to add to the tablet interface: the album's title, the artist's name, the year the album was published, and the genres to which the album belongs. We already have albumTitle and artistName defined as properties in the SongViewModel class. This means we just need to add the year and genres properties. Listing 11–3 shows the code to accomplish this.

Listing 11–3. Adding year and genre Properties to the SongViewModel

package viewmodels
{
  [Bindable]
  public class SongViewModel extends EventDispatcher {
    public var albumCover:BitmapData;
    public var albumTitle:String = "";
    public var songTitle:String = "";
    public var artistName:String = "";    
    public var year:String = "";
    public var genres:String = "";

    // …

    /**
     * Called when the song's metadata has been loaded by the Metaphile
     * library.
     */
    privatefunction onMetaData(metaData:IMetaData):void {
      var songFile:MusicEntry = songList[currentIndex];
      var id3:ID3Data = ID3Data(metaData);
      artistName = id3.performer ? id3.performer.text : "Unknown";
      albumTitle = id3.albumTitle ? id3.albumTitle.text : "Album by " +
          artistName;
      songTitle = id3.songTitle ? id3.songTitle.text : songFile.name;
      year = id3.year ? id3.year.text : "Unknown";
      genres = id3.genres ? id3.genres.text : "Unknown";

      if (id3.image) {
        var loader:Loader = new Loader();
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE,
                                                  onLoadComplete)
        loader.loadBytes(id3.image);
      } else {
        albumCover = null;
      }
    }

    // …
  }
}

The code highlighted in bold shows the changes that need to be made: declare new bindable variables to hold the year and genres strings and then load them from the ID3Data returned by the Metaphile library.

Our attention now turns to the question of how to add this information to our interface. Figure 11–3 shows two mockups for the new interface, one in landscape orientation and one in portrait orientation. The phone interface will stay exactly the same, but when we detect that we're running on a tablet, we will make the following changes:

  • The song title in the ActionBar will be replaced with the album title.
  • In portrait orientation, the four new pieces of metadata will be placed between the album cover and the playback controls.
  • In landscape orientation, the new metadata will be placed on the left side of the screen with the album cover in the middle and the playback controls on the right side.

The new song information appears in different places depending on the orientation of the device, but that can be easily implemented using our custom state names and the includeIn property of the components.

images

Figure 11–3. A design mockup showing the additional information to display in the tablet interface

The code in Listing 11–4 shows the first modifications that will need to be made to the original View code to achieve the new design shown in Figure 11–3.

Listing 11–4. The Beginning of the Modified SongView MXML

<?xml version="1.0" encoding="utf-8"?>
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:assets="assets.*"
        xmlns:views="views.*"
        initialize="onInitialize()"
        viewActivate="onViewActivate()"
        viewDeactivate="onViewDeactivate()"
        resize="onResize()"
        title="{isTablet ? model.albumTitle : model.songTitle}">

  <s:states>
    <s:State name="portraitPhone" stateGroups="portrait,phone"/>
    <s:State name="landscapePhone" stateGroups="landscape,phone"/>
    <s:State name="portraitTablet" stateGroups="portrait,tablet"/>
    <s:State name="landscapeTablet" stateGroups="landscape,tablet"/>
  </s:states>

  <fx:Script>
    <![CDATA[
      import viewmodels.SongViewModel;

      [Bindable]
      private var isTablet:Boolean;

      [Bindable]
      private var model:SongViewModel;

      override public function getCurrentViewState():String {
        var isPortrait:Boolean = height > width;
        var newState:String = (isPortrait ? "portrait" : "landscape") +
            (isTablet ? "Tablet" : "Phone");

        return hasState(newState) ? newState : currentState;
      }

      private function onViewActivate():void {
        var w:Number = Capabilities.screenResolutionX/Capabilities.screenDPI;
        var h:Number = Capabilities.screenResolutionY/Capabilities.screenDPI;
        isTablet = Math.max(w, h) > 5.5;

        setCurrentState(getCurrentViewState());
      }

      privatefunction onResize():void {
        setCurrentState(getCurrentViewState());
      }

      private function onInitialize():void { /* same as before */ }
      private function onViewDeactivate():void { /* same as before */ }
      private function onSongEnded(event:Event):void { /* same as before */ }
    ]]>
  </fx:Script>

The View's title attribute uses a binding to the isTablet variable to determine whether to display the song title or the album title in the ActionBar. Remember that on the smaller phone screens we display the song titlein the ActionBar's title area to avoid overcrowding the SongView interface. If a larger tablet screen is being used, it makes more sense to put the album title in the ActionBar and change the song information when moving from one song to the next.

Each of our states has been defined with its associated state groups as described previously in this section. The overridden getCurrentViewState function that appears at the top of the <fx:Script> section is responsible for determining which state the View should be in based upon screen size and orientation. If the View's height is greater than its width, then the device is marked as being in portrait orientation. Otherwise we know we are in landscape mode. Using this information along with the isTablet flag, the function builds and returns a string that describes the current state of the View.

The isTablet flag is set in the handler for the View's viewActivate event. When the View becomes active, the onViewActivate handler calculates the width and height of the device's screen in inches. If either of these dimensions is over 5.5 inches, then we can assume that the application is running on a tablet device. The function then calls our overridden getCurrentViewState method to get the initial state for the View and passes the result to the setCurrentState function.

We also attach a handler to the View's resize event to detect orientation changes. The onResize handler will set the current state of the View by calling our getCurrentViewState function and using the returned value to set the current View state.

NOTE: Overriding the getCurrentViewState function to provide custom states does have the drawback that it makes Flash Builder's design view virtually useless.

It is time to put this state management code to use in our MXML declarations. Listing 11–5 shows the root Group container along with the group of labels that make up the song information section in the landscape orientation.

Listing 11–5. The View's Root Container Group and the Landscape Metadata Display

<s:Group width="100%" height="100%">
  <s:layout.portrait>
    <s:VerticalLayout paddingTop="10" horizontalAlign="center"/>
  </s:layout.portrait>

  <s:layout.landscape>
    <s:HorizontalLayout verticalAlign="middle" paddingLeft="10"/>
  </s:layout.landscape>

  <s:VGroup width="30%" horizontalCenter="0" gap="20" paddingTop="40"
            paddingBottom="40" includeIn="landscapeTablet">
    <s:VGroup width="100%">
      <s:Label styleName="albumInfoLabel" text="Song"/>
      <s:Label styleName="albumInfo" text="{model.songTitle}"
               maxWidth="{width*.3}" maxDisplayedLines="1"/>
    </s:VGroup>
    <!-- repeated for artist, year, and genres -->
  </s:VGroup>

  <s:Group width.portrait="{height*0.4}" height.portrait="{height*0.4}"
           width.landscape="{width*0.4}" height.landscape="{width*0.4}">
    <s:BitmapImage width="100%" height="100%" source="{model.albumCover}"
                   visible="{model.albumCover != null}"/>
    <assets:DefaultAlbum id="placeHolder" width="100%" height="100%"                          visible="{!model.albumCover}" />
  </s:Group>

As in Chapter 8, we use a VerticalLayout for the root Group when in portrait mode and a HorizontalLayout in landscape mode. Thanks to the state groups that were declared previously, these layouts will be used for both the phone and tablet versions of the interface. The first child of the root Group container is the VGroup that contains the song information—recall from the mockup that it is on the far left of the screen—for the landscape version of the interface. Furthermore, this group should appear only on tablet displays. That is the reason for using the fully specified landscapeTablet state in its includeIn attribute. The next Group is the container for the album cover image. Since the previous VGroup is included only in the landscapeTablet state, the album cover Group will appear first in the layout on phones in any orientation and on tablets in portrait mode.

Listing 11–6 shows the portrait mode version of the song information display along with the rest of the controls.

Listing 11–6. The Portrait Song Information Group and the Playback Controls

<s:VGroup width="80%" horizontalCenter="0" gap="40" paddingTop="40"
              paddingBottom="40" includeIn="portraitTablet">
      <s:HGroup width="100%">
        <s:VGroup width="50%">
          <s:Label styleName="albumInfoLabel" text="Song"/>
          <s:Label styleName="albumInfo" text="{model.songTitle}"
                   maxWidth="{width*.4}" maxDisplayedLines="1"/>
        </s:VGroup>
        <s:VGroup horizontalAlign="right" width="50%">
          <s:Label styleName="albumInfoLabel" text="Artist"/>
          <s:Label styleName="albumInfo" text="{model.artistName}"                    maxWidth="{width*.4}" maxDisplayedLines="1"/>
        </s:VGroup>
      </s:HGroup>
      <!-- repeated for year and genres -->
    </s:VGroup>

    <s:VGroup horizontalAlign="center" paddingTop="20" gap="40"
              width.portrait="100%" width.landscape="50%">
      <s:HGroup width="90%">
        <s:Button label="&lt;&lt;" height="40" click="model.previousSong()"/>
        <views:ProgressButton id="progressButton" width="100%" height="40"
                              click="model.onPlayPause()"
                              percentComplete="@{model.percentComplete}"                               skinClass="views.ProgressButtonSkin"/>
        <s:Button label="&gt;&gt;" height="40" click="model.nextSong()"/>
      </s:HGroup>

      <s:HGroup verticalAlign="middle" width="90%">
        <assets:VolLow id="volLow" width="32" height="32"/>
        <s:HSlider width="100%" maximum="1.0" minimum="0.0" stepSize="0.01"
                 snapInterval="0.01" value="@{model.volume}" showDataTip="false"/>
        <assets:VolHigh id="volHigh" width="32" height="32"/>
      </s:HGroup>

      <s:HGroup verticalAlign="middle" width="90%" >
        <s:Label text="L" width="32" height="32" verticalAlign="middle"
                 textAlign="center"/>
        <s:HSlider width="100%" maximum="1.0" minimum="-1.0" stepSize="0.01"
                 snapInterval="0.01" value="@{model.pan}" showDataTip="false"/>
        <s:Label text="R" width="32" height="32" verticalAlign="middle"
                 textAlign="center"/>
      </s:HGroup>
    </s:VGroup>
  </s:Group>
</s:View>

In portrait mode, the song information VGroup is displayed between the album cover and the playback controls—hence its placement at this point in the MXML file with its includeIn attribute specifying the portraitTablet state.

As a finishing touch, we have added a little CSS styling in the ViewNavigatorApplication MXML file for the song information Label components. We now arrive at the application shown in Figure 11–4. Our application is now capable of adapting itself to run on the smallest and the largest of mobile devices. This is a simple example of the customization that is possible through the judicious use of states. The code for this application can be found in the MusicPlayerWithStates project located in the examples/chapter-11 directory of the book's sample code.

images

Figure 11–4. The MusicPlayerWithStates application running on both small and large screens

The main advantage of this state-based customization technique is that it allows you to keep all of your application code in one project. That makes it easier to maintain the code and simplifies the build process. The disadvantages become evident, however, when you consider what needs to be done when you want to start supporting other platforms. If you want to expand your market to include the iPhone, iPad, and PlayBook, then you will need to start performing UI tweaks to accommodate all of the different conventions in use for these platforms. You will suddenly be facing a combinatorial explosion of states. You will also run into problems if your interfaces for the different device classes or platforms start to diverge from each other too much. States will take you only so far before you have a long, difficult-to-read, and difficult-to-maintain MXML file.

If you find yourself in this position, you can turn to the second option for interface customization: project-based customization.

Project-Based Customization

The idea behind project-based customization is to put all of your application's shared code into a library project and then create separate projects that implement the customized user interface of each different platform or device class (phone vs. tablet, for example) that you target. Creating a separate project for each version of your application that is meant for a different class of device or a different platform affords you the ultimate flexibility in configuring your interface. This kind of setup is very common for projects that span two or more of web, desktop, phones, tablets, and TV. To avoid unnecessary code duplication, a library project is created to contain all shared source files and graphical assets.

Let's pretend that our designers have taken a look at some of the applications shown in Figure 11–2 and have decided to try a new look for our music player. They have come up with a new approach to the tablet interface in landscape mode that looks something like Figure 11–5. They want to move the song information to the right side of the screen, place the playback controls under the album cover, and add a list of songs to the left side of the screen. Selecting a song from the list should skip to that song. The list's selection highlight should always reflect the song that is currently playing. We'll also pretend that we've started hearing whispers from marketing about expanding to support other mobile platforms. Put all that together, and we will decide that it is time we opt for full customization ability by splitting our code base into separate projects with one common library project that the rest will share.

images

Figure 11–5. A new interface prototype for MusicPlayer running on a tablet in landscape mode

Creating the Library Project

The first thing to do is to create the shared library project. In Flash Builder 4.5 (or above), use the application menu and click File image New image Flex Library Project. Flash Builder will display the dialog box shown in Figure 11–6.

images

Figure 11–6. Creating a new library project in Flash Builder 4.5

You must specify a name for the library project (such as MusicPlayerLib) as we did in Figure 11–6. Since we are not concerned with supporting web and desktop in this project (yet!), we also selected the “Mobile library” option in the Configuration section.

We know our presentation models will be placed into this project. We also know that one of them depends on the Metaphile library. Therefore we will have to add the Metaphile.swc file to this project in order for it to compile. We created a libs directory and placed Metaphile.swc inside. We then added the libs directory to the build path by right-clicking the project and selecting Properties. The project's Properties dialog will be displayed, and it will look something like the one shown in Figure 11–7. Click Flex Library Build Path, and click the “Add SWC Folder…” button. Type the directory name “libs” into the text field of the dialog that comes up, and click OK. Your dialog should now look like the one in Figure 11–7, which shows that the Metaphile.swc file has been added to your build path.

images

Figure 11–7. Adding the Metaphile.swc file to our library project

The final step in creating our library project is to replicate the necessary package structure from the original MusicPlayer application and copy the source code and graphical assets into the correct locations. Table 11–2 shows the packages that have been added and the files that go inside each package.

Images

Notice that we have taken the custom ProgressButton control from the views package in the original MusicPlayer project and placed it into a new components package in the shared library project. The library project should now compile, and we are ready to create the new projects that we will use to build the versions of the application that will run on phones and tablets.

Creating the Phone and Tablet Projects

We will create a new Flex mobile project by using the application menu and clicking File Images New Images Flex Mobile Project. When the New Flex Mobile Project dialog appears, name the project MusicPlayerPhone, click the Next button, select a View-Based Application, and click Finish. The following steps must be performed to populate the new project:

  1. Copy the graphical assets from the assets package in the original MusicPlayer project to an assets package in the new project. This includes the splash screen, volume icons, and the default album cover.
  2. Copy the source code from the views package of the original MusicPlayer project, and place them into the views package of the new project. This will include the SongListView.mxml and SongView.mxml files.
  3. Modify the code in SongView.mxml to take into account the new package for the ProgressButton control.
  4. Copy the code from the main ViewNavigatorApplication MXML file in the original project's default package to the new project's main MXML file.
  5. Add the MusicPlayerLib project to this project's build path by right-clicking the project and selecting Properties, clicking Flex Build Path, clicking the Add Project… button, and selecting the MusicPlayerLib project.

The new project should now compile and run, with the result looking exactly like the original MusicPlayer from Chapter 8. If you have any questions, you can review the source code in the MusicPlayerPhone project found in the examples/chapter-11 directory of the sample code for this book. By repeating these steps to create a MusicPlayerTablet project, you will be ready to start on the new custom tablet interface for the MusicPlayer application.

But before we get started, this is a good time to introduce you to Eclipse's Working Sets feature, if you don't already know it. Defining a working set will allow you to limit the number of projects listed in the Package Explorer to just the ones you are working on at any given time. And once you have working sets defined, you can easily switch between them. You access the Working Sets feature by using the View Menu to the right of the Package Explorer tab. The icon for the View Menu is the upside-down triangle. Figure 11–8 shows its location.

images

Figure 11–8. The Package Explorer's View Menu icon

You define a new working set by clicking the View Menu icon and choosing the “Select Working Set…” option. The Select Working Set dialog box will be displayed. Clicking the New button will display the New Working Set dialog box. Select Resource as your working set type, and click Next. In the final dialog box, type a name for your working set and select the projects that you want to be a part of the working set. Then click Finish. Figure 11–9 shows this sequence of dialog boxes.

images

Figure 11–9. Creating a new working set

To select a working set, click the View Menu and Select Working Set again. The working sets you have defined will appear in the list. Select the check box next to the working set you want to activate, and click OK. Once you have selected a working set, its name will appear directly on the View Menu, allowing you to switch between working sets with only two clicks. When your Package Explorer view starts to get crowded with all of the different projects you are working on, being able to quickly define and switch between working sets is a huge benefit.

Implementing the Custom Tablet Interface

In the new SongView interface, the list of songs will appear on the left side of the screen. The current selection in the list should reflect the song that is currently playing. Tapping a new entry in the list should switch to that song. What we are describing here is two bindings: one between the song list in the model and the items in the list, and another between the list's current selection and the current song index in the model.

We shall start with the modifications that need to be made to the model. A new songListArrayCollection will be created to serve as the source of the binding for the List in the UI. We will also need to make the model's currentIndex variable bindable to serve as the source of the List's selectedIndex property, as well as settable so that a new list selection will cause the model to take action to play a new song. Listing 11–7 shows the first of these changes to the model.

Listing 11–7. Changes to the SongViewModel Because Six Pages with No Code Is Just Too Long!

[Bindable]
public class SongViewModel extends EventDispatcher {
    // Some variables removed for brevity…

    public var year:String = "";
    public var genres:String = "";
    public var songList:ArrayCollection;

    private var _currentIndex:Number = 0;

    /** A collection of MusicEntry objects. */
    private var musicEntries:ArrayCollection;

    public function SongViewModel(entries:ArrayCollection, index:Number) {
      this.musicEntries = entries;
      this.currentIndex = index;

      timer = new Timer(500, 0);
      timer.addEventListener(TimerEvent.TIMER, onTimer);

      loadCurrentSong();
      filterEntriesBySongs();
    }

    /**
     * Takes all songs in musicEntries and puts them in songList.
     */
    private function filterEntriesBySongs():void {
      songList = new ArrayCollection();

      for (var i:int = 0; i<musicEntries.length; ++i) {
        var entry:MusicEntry = MusicEntry(musicEntries.getItemAt(i));
        if (entry.isSong)
          songList.addItem(entry);
      }
    }

In Listing 11–7, we have added the new ArrayCollection named songList and renamed the currentIndex variable to _currentIndex to indicate that it will now have publicget and set functions associated with it. The songList collection is initialized in the filterEntriesBySong function that is called at the end of the class's constructor. This function loops through the musicEntries collection and copies each song entry to the songList collection.

Listing 11–8 shows the code in the model class that provides access to the currentIndex property and handles playing the song that corresponds to the currentIndex. The currentIndex's get function provides the View with access to the property's value. The set function stores the new value and calls the playSongAtCurrentIndex function.

Listing 11–8. The SongViewModel Code Relating to Playing the Song at the Current Index

    public function get currentIndex():Number {
      return _currentIndex;
    }

    public function set currentIndex(value:Number):void {
      _currentIndex = value;
      playSongAtCurrentIndex();
    }

   /**
     * Jump to the beginning of the next song in the list.  Will wrap to
     * the beginning of the song list if needed.
     */
    publicfunction nextSong():void {
      incrementCurrentSongIndex();
      playSongAtCurrentIndex();
    }

    /**
     * Moves the play position back to the beginning of the current song
     * unless we are within 3 seconds of the beginning already.  In that
     * case, we jump back to the beginning of the previous song.  Will
     * wrap to the end of the song list if needed.
     */
    publicfunction previousSong():void {
      if (channel && channel.position < 3000) {
        decrementCurrentSongIndex();
        playSongAtCurrentIndex();
      } else {
        percentComplete = 0;
      }
    }

    /**
     * Will load and play the song indicated by the currentIndex variable.
     */
    public function playSongAtCurrentIndex():void {
      loadCurrentSong();

      if (isPlaying) {
        pauseSong();
        playSong();
      } else {
        percentComplete = 0;
      }
    }

The playSongAtCurrentIndex function loads the song into memory and, if the model is in “play” mode, stops the current song and causes this new song to play. If the model is paused, then the percentComplete variable is just reset, so that playback will resume from the start of the song the next time the model's onPlayPause function is called. We have also gone back to the model's previousSong and nextSong functions and changed them to use the new playSongAtCurrentIndex function in order to eliminate unnecessary code duplication. Clean as you go!

Switching to the view, we know that the portrait mode UI should stay the same while we add the song list to the left side of the screen in landscape mode. At the same time, the song information migrates from the left side of the screen in the last incarnation of the interface to the right side of the screen in the latest design. Since we no longer need the extra states, this being a tablet-specific UI now, the beginning of the MXML file is now back to its original form with the exception that the ActionBar displays the album title rather than the song title as it does on the phone interface. All of the extra state declarations and the functions to set and get the View states are gone.

We need to add the declaration for the List as the first child of our View's root Group container and make sure it is included only in the landscape state. We will also enclose the album cover, portrait mode song information, and playback controls into one VGroup now, since those sections always appear as a vertical group in both the portrait and landscape states. Finally, a VGroup of labels will be added to the landscape state to show the song information on the right side of the screen in that orientation. Listing 11–9 shows these changes to the SongView MXML file.

Listing 11–9. Changes to the SongView MXML to Support the New Landscape Interface Design

<s:Group width="100%" height="100%">
    <s:layout.portrait>
      <s:VerticalLayout paddingTop="10" horizontalAlign="center"/>
    </s:layout.portrait>

    <s:layout.landscape>
      <s:HorizontalLayout verticalAlign="top"/>
    </s:layout.landscape>
    <s:List id="songList" styleName="songList" includeIn="landscape" width="30%"
            height="100%" dataProvider="{model.songList}" labelField="name"
            selectedIndex="{model.currentIndex}"
            change="model.currentIndex = songList.selectedIndex"/>

    <s:VGroup horizontalAlign="center" width.portrait="100%"
              width.landscape="40%" paddingTop="20 ">
      <s:Group width.portrait="{height*0.4}" height.portrait="{height*0.4}"
               width.landscape="{width*0.35}" height.landscape="{width*0.35}">
        <s:BitmapImage width="100%" height="100%" source="{model.albumCover}"
                       visible="{model.albumCover != null}"/>

        <assets:DefaultAlbum id="placeHolder" width="100%" height="100%"
                             visible="{!model.albumCover}" />
      </s:Group>

      <!-- The groups defining the portrait mode song info and controls are unchanged --
>
    </s:VGroup>

    <s:VGroup width="30%" gap="60" includeIn="landscape" paddingRight="10"
              paddingTop="20">
      <s:VGroup width="100%" horizontalAlign="right">
        <s:Label styleName="albumInfoLabel" text="Song"/>
        <s:Label styleName="albumInfo" text="{model.songTitle}"
                 maxWidth="{width*.3}" maxDisplayedLines="2"/>
      </s:VGroup>
      <!-- Repeated for the other song information items -->
    </s:VGroup>

The List uses the model's new songList as its dataProvider and uses it to display the song names. Its selectedIndex property is bound to the model's currentIndex property to ensure that whichever song is currently playing is also the one highlighted in the list. Whenever the List's selection changes, the new selectedIndex is used to set the model's currentIndex property. This allows the user to tap an item in the list to change the song that is currently playing.

After implementing these changes, the application now appears as in Figure 11–10. The figure shows the application running in landscape orientation on the Motorola XOOM and showing off the new song list on the left side of the screen. The image on the right side of the figure shows the application running in portrait mode on a Samsung Galaxy Tab. Rotating the tablet from portrait to landscape will cause the song list to appear seamlessly. And, of course, we have our original phone version of the interface tucked safely away in the MusicPlayerPhone project, which remains unaffected by these new features in the tablet version. The updates to the SongViewModel in the shared library will be present in the phone version, of course, but they remain unused in that application and therefore have no effect.

In some ways, having separate projects for each platform simplifies the build process, especially once you start dealing with multiple platforms, because you can have one application XML descriptor file per project instead of swapping them in and out at build time.

images

Figure 11–10. The new tablet interface running on a Motorola XOOM in landscape mode and a Samsung Galaxy Tab in portrait mode

Transitioning to TV

This is an exciting time to be a part of the Adobe Flash ecosystem. In addition to the web, desktop, and Android platforms, AIR is also becoming a viable programming environment for iOS devices, BlackBerry tablets, and even television sets, Blu-ray players, and set-top boxes! It is the one environment that truly lets you leverage your existing programming and design skills across all the screens of your life—even the big screens.

At Google I/O in May of 2011, Google announced that it was bringing Android 3.1, the so-called Honeycomb release, to its Google TV platform. With this update, the Android market will become available to Google TV users. With a few restrictions, your existing AIR for Android applications should port fairly easily to the Google TV platform. In addition, all new Google TV devices sold at retail will include the Android debugger, which means that you should be able to run and test your applications right on the Google TV in your living room.

Another path to the living room lies in the AIR for TV platform from Adobe. This is a runtime for TVs, set-top boxes, and Blu-ray players. It is currently in pre-release and runs on AIR 2.5. One thing to be aware of when developing for TV platforms is that they typically lie on the low end of the CPU horsepower spectrum. The CPUs found in TVs are often significantly slower than even those found in your average smartphone. This does not necessarily mean that your AIR for TV applications will be slow, but it does mean that you should pay attention to performance. Many of the tips given in Chapter 10 will also apply to TV platforms. Given the slower CPUs commonly found in TVs, you should pay particular attention to the advice given in the “Reducing Code Execution Time” section of that chapter.

Adobe AIR for TV is expected to make its debut in Samsung's Smart TV platform that, at the time of this writing, was expected to ship in 2011.

There are some things that you need to keep in mind should you decide to develop for one of these TV platforms. First, the input method is different for a TV. Even if TVs had touchscreens, nobody wants to constantly get up and walk to the TV to touch the screen in order to interact with their applications. Therefore TVs will likely use small touchpads or directional button pads for navigation and interaction. Secondly, as Google reminds us, TVs are really a “10 foot experience.” The screens are larger, so the controls and fonts should be larger too. Tackling TV will almost certainly require a fresh design pass for your application.

Porting to PlayBook

Although Research In Motion is a newcomer to the tablet market, the BlackBerry PlayBook is an interesting entry. The PlayBook comes in a small form factor, measuring just 7.6 inches wide and 5.4 inches tall, which makes it an extremely portable device. It features a 7-inch touchscreen, a 1-GHz dual-core processor, and 1 GB of RAM. It is paired with the QNX Neutrino real-time operating system. This microkernel architecture-based OS is known for its use in mission-critical systems.

One thing to like about the PlayBook is that it is very developer-friendly. It offers developers a choice of no less than four environments in which to develop their applications: native C/C++, Java, HTML5 and related technologies, and (of course) Adobe AIR. Furthermore, AIR is not a second-class citizen on this platform. AIR apps can take advantage of hardware acceleration for video and graphics. Although the usual Flash platform components are present, there are special packages available to AIR programmers that give ActionScript programs the ability to use native, high-performance QNX components in their user interfaces. AIR applications can even access the platform's native notification features. In short, AIR programs are very well supported and integrate nicely into the platform. The only real drawback to the tablet is that since it is a brand-new platform, its market penetration is fairly low.

So as a Flash/Flex/AIR developer, how can you jump into this new market? A good place to start is the BlackBerry Tablet OS SDK for Adobe Air Development Resources web site.3 From there you will find links to the “Getting Started Guide”4 and steps for installing the development environment. You will first need to download and unzip the SDK installer program. The installer will create a new PlayBook directory in the sdks directory of your current Flash Builder 4.5 installation. This directory will contain everything you need to develop PlayBook applications. The PlayBook simulator is a VMware-compatible virtual machine image that runs a PlayBook runtime environment right on your Windows, Mac, or Linux desktop. This image is included with the PlayBook SDK files that get placed in your Flash Builder installation directory. Just open this VM image in VMware, and the PlayBook environment will boot up. When it starts, it will ask for a password. Type in “playbook” and you should see the PlayBook UI appear.

__________

You create a new project for your PlayBook application in Flash Builder 4.5 by selecting File Images New Images ActionScript Mobile Project from the application's menu. You use the default SDK, and select BlackBerry Tablet OS as the target platform.

NOTE: At the time of this writing, official BlackBerry Tablet OS support is expected to ship in a Flash Builder update in the summer of 2011. That may change the way you create a mobile application project for this platform.

You can run and test your AIR applications right in the simulator on your desktop. You will just need to create a run configuration for your project in Flash Builder using the IP address of the PlayBook environment running in the virtual machine. You can get the PlayBook's IP address by clicking the icon of the person with a gear on his chest, located at the top right of the PlayBook screen. The “Getting Started Guide” just mentioned provides simple and easy-to-follow instructions for all of these steps, which will allow you to get started developing on the simulator in under an hour.

That is enough of a preamble; Listing 11–10 shows what a simple Hello World program looks like for the BlackBerry PlayBook.

Listing 11–10. A Hello World ActionScript Program for the BlackBerry PlayBook

  import flash.display.Bitmap;
  import flash.display.GradientType;
  import flash.display.Graphics;
  import flash.display.SpreadMethod;
  import flash.display.Sprite;
  import flash.events.MouseEvent;
  import flash.geom.Matrix;
  import flash.text.TextFormat;
  import qnx.ui.buttons.LabelButton;
  import qnx.ui.text.Label;

  [SWF(width="1024", height="600", frameRate="30")]
  publicclass PlayBookHelloWorld extends Sprite
  {
    [Embed(source="splash.png")]
    privatevar imageClass:Class;

    publicfunction PlayBookHelloWorld()
    {
      var bitmap:Bitmap = new imageClass();
      bitmap.x = 10;
      bitmap.y = 10;

      var goodByeButton:LabelButton = new LabelButton();
      goodByeButton.label = "Good Bye";
      goodByeButton.x = stage.stageWidth - goodByeButton.width;
      goodByeButton.y = stage.stageHeight - goodByeButton.height;
      goodByeButton.addEventListener(MouseEvent.CLICK, onClick);

      var myFormat:TextFormat = new TextFormat();
      myFormat.color = 0xf0f0f0;
      myFormat.size = 48;
      myFormat.italic = true;

      var label:Label = new Label();
      label.text = "Hello Pro Android Flash!";
      label.x = bitmap.width + 20;
      label.y = 10;
      label.width = stage.stageWidth - bitmap.width - 10;
      label.height = 100;
      label.format = myFormat;

      addChild(createBackground());
      addChild(bitmap);
      addChild(goodByeButton);
      addChild(label);

      stage.nativeWindow.visible = true;
    }

    privatefunction onClick(event:MouseEvent):void{
      stage.nativeWindow.close();
    }

    privatefunction createBackground():Sprite {
      var type:String = GradientType.LINEAR;
      var colors:Array = [ 0x808080, 0x404040 ];
      var alphas:Array = [ 1, 1 ];
      var ratios:Array = [ 0, 255 ];
      var spread:String = SpreadMethod.PAD;

      var matrix:Matrix = new Matrix();
      matrix.createGradientBox( 100, 100, (90 * Math.PI/180), 0, 0 );

      var sprite:Sprite = new Sprite();
      var g:Graphics = sprite.graphics;
      g.beginGradientFill( type, colors, alphas, ratios, matrix, spread );
      g.drawRect( 0, 0, 1024, 600 );

      return sprite;      
    }
  }

As you can see, it looks pretty much like any other Flash program. We have used a couple of the basic QNX controls just to show what it looks like to include them in your program. They have a very familiar API to anyone that is used to Flash programming. Figure 11–11 shows what the PlayBook environment and the Hello World program look like when running in the simulator.

images

Figure 11–11. A simple Hello World ActionScript program running on the BlackBerry PlayBook simulator

You will need a “debug token” if you want to run your applications on actual PlayBook hardware. Getting such a token is free, but you will need to register with the PlayBook development program. You will also need to apply for a key to sign your applications if you wish to eventually deploy them into the BlackBerry app store.

If you do decide to port your Android applications to the PlayBook, you should follow the same advice we used previously when porting from phone to tablet: get to know your target platform. For example, the PlayBook does not have a hardware Back button; so, much like the iPhone or iPad, there is usually a Back button at the top left corner of most application screens. As always, a good way to get to know your target platform is to study the platform's popular applications. There is a Facebook app and plenty of pre-installed applications for you to look at on the PlayBook.

Investigating iOS

Android and iPhone devices are currently dominating the smartphone market in terms of worldwide popularity. This makes Apple's iOS attractive as a potential target when porting your applications to other platforms. But when you add the fact that Apple's iPad is the undisputed king of the tablet market, the decision suddenly becomes a very obvious one.

Getting Apple's development environment set up is somewhat similar to the process required for the PlayBook, although with Apple there is no free option for testing your software on an actual device. You will have to join its developer program5 (currently $99USD per year) to have the ability to run and test your application on real hardware.

Once you have your membership and development key in hand, however, writing ActionScript-based applications for iOS is pretty much the same as it is for Android and PlayBook.

NOTE: Like the PlayBook, at the time of this writing, updated support for iOS development with Flash Builder is expected in the summer of 2011.

Once again you will need to spend some time familiarizing yourself with the common design patterns employed on this platform. For example, just like the PlayBook, iOS devices do not have hardware Back buttons. Fortunately, the good folks on Adobe's platform teams have made life somewhat easier for developers in this respect. The defaultButtonAppearance-style property of the ActionBar can be set to “beveled” to approximate the look of the native iOS ActionBar buttons. In addition, titles in the ActionBar tend to be centered rather than right-aligned as they are on Android. The ActionBar's titleAlign property can be set to “center” to achieve this effect in your AIR application. See Listing 3-8 in Chapter 3 for an example of using these styles in your application.

You can even apply these styles dynamically at runtime by using the @media (os-platform:“ios”) CSS selector or by making sure that Capabilities.cpuArchitecture returns the string “ARM” and that Capabilities.os returns a string containing the term “iPhone”.

__________

Summary

This chapter has shown you how to take your mobile AIR applications and adapt them to Android tablets, TVs, and even Apple and BlackBerry devices. You have learned the following:

  • How to use states and state groups to customize your interface for different devices while maintaining a single code base
  • How to split your application into multiple projects that include shared library code as well as fully customized UIs for each of the different platforms that you want to target
  • What options you have for expanding your reach onto TV screens and some of the things you will need to consider when doing so
  • How to get up and running with the BlackBerry PlayBook development environment for Adobe AIR
  • Some tricks for porting your Android applications to Apple's iOS platform

You made it! Welcome to the end of the book! We hope you have enjoyed reading it as much as we enjoyed writing it. And if you've learned a thing or two to make your life easier as an AIR for Android developer, then that makes the journey worthwhile. These are exciting times in the technology world. This modern world of smartphones, tablet computers, smart TVs, and incredibly fast wireless bandwidth is opening up new opportunities and new challenges for software developers all over the world. Good luck and good programming!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.40.189