Creating Liquid GUIs with Flash - Part 2

With user experience expectations on the rise, developers more and more are looking for ways to bring the rich experience of a desktop application to the web. This is part two of the tutorial about developing dynamic graphical user interfaces (GUI) that intelligently mold themselves to an end-user’s expectation.

1. Identifying the parts of the GUI that resize and how they do so

My image gallery has three clearly identifiable sections, so identifying the parts that resize is easy. All three sections resize, though not in the same way. The header and footer sections resize only in the horizontal direction while the body resizes in both directions. As for relocation, only the footer relocates vertically to lock itself to the bottom of the window while the header and body sections each maintain their original x and y location.

The header section is made up of three children: the title, the button group and the background image. The background image resizes its width to match that of the window. The title and button group components are unaffected by the resize.

The body section is made up of two children: an empty MovieClip which acts as a container for the loaded image and a background fill. The background fill resizes itself to match the width of the window and the image holder runs a series of calculations to maintain the center of the background fill depending on the size of the image loaded and the actual/best-fit preference chosen by the user.

Finally, the footer section is made up of two children: the group of image thumbnails and a background fill. The background fill resizes itself to match the width of the window while the thumbnails group in unaffected. (In a real-world application, the thumbnails container might behave like the Word application and display arrow buttons should the window get too small. Since this is a tutorial, I kept the features to a minimum so that I didn’t stray too far from the focus.)

2. Set the HTML page to display the application at 100%

The point of the liquid application is to fill the browser window 100% vertically and horizontally so that it takes up the entire window.

To ensure that the application fills the entire browser window, the margin needs to be set to zero. Otherwise, a default width of 10 pixels will surround the SWF.

<style type="text/css">
  body, html {margin:0;}
</style>

Making the SWF fill the browser window is very simple, just a matter of configuring the SWF’s publish settings.

Open the Publish Settings window from the “File” menu. Make sure you have the “HTML (.html)” checkbox checked and look for a tab labeled “HTML”. Click on this tab and simply select “Percent” from the “Dimensions” drop down list then enter 100 for both the width and height properties. This setting will make the SWF fill the entire browser window.

The next two publish settings can be done through parameters of the object and embed tag but it’s not my preference to do so. They are:

  1. Look down a little further on the same HTML tab for a drop down list labeled “Scale” and select “noScale”.
  2. Go to the last set of drop down lists labeled “Flash Alignment” and choose “Left” for horizontal and “Top” for vertical alignment. This pins the (0,0) coordinates of the Stage to the top left most pixel of the browser window.

My preference for these last two settings is to do it in ActionScript so that the values are used by the player when I test my movie inside of the Flash authoring environment. Otherwise, you can’t properly test your movie.

3. Set the Flash movie to not automatically scale its contents

To handle the last two settings with ActionScript I, set two of the Stage object’s properties to equivalent values of that found on the HTML settings tab.

The first property is Stage.scaleMode which I set to “noScale” and the second property is Stage.align which I set to “TL”.

Stage.scaleMode = "noScale";
Stage.align = "TL";

These two ActionScript lines, accompanied by the object and embed parameters for 100% width and height, are all the settings needed to start writing liquid GUIs.

4. Add intelligence to the application so that it knows how to scale when the browser window resizes

Using ActionScript to prevent automatic scaling of the SWF means that the SWF will not scale at all. Unless, of course, I manually scale every MovieClip, which is exactly what I will do.

The remaining portion of this tutorial will take you through the process of building the gallery application. I have broken up the gallery into four progressive versions that add features and complexity to the application.

  1. Version 1: a bare bones shell which demonstrates how to listen for resize events and manually scale the movie’s parts accordingly
  2. Version 2: I show you how to create a container for your GUI and how to extend the MovieClip class with a custom GUI class that handles resizing
  3. Version 3: I show you how to make the app scalable using polymorphism by implementing a custom IResizable interface for all sections
  4. Version 4: I briefly talk about the other features implemented in the gallery and how they were implemented

Version 1

I start off by creating a MovieClip for each of my three sections: header, body and footer. I place each of those clips in separate layers with header the top most, footer the second and body the last. I place the body section as the bottom most layer so that images loaded larger than the available body size will be clipped by the header and footer sections. These MovieClips are each given an instance name by selecting the MovieClip while it’s on the stage and typing a name into the box labeled “” in the Properties panel so that I can refer to them from ActionScript. I use the names header_mc, body_mc and footer_mc, as they clearly identify the sections as well as provide code insight by using the suffix _mc.

On a fourth layer named “Actions” (so named purely for readability), I add the following ActionScript:

// Ensure Flash doesn’t scale it’s content
Stage.scaleMode = “noScale”;
// Ensure Flash always puts the stage in top left corner
Stage.align = “TL”;
// Create a listener object to listen for resizing
var myListener:Object = new Object();
myListener.onResize = function (e:Object):Void {
  // store the current width and height as local vars
  var sw:Number = Stage.width;
  var sh:Number = Stage.height;
  // Resize any widths
  header_mc._width = body_mc._width = footer_mc._width = sw;
  // Resize any heights
  body_mc._height = sh-header_mc._height-footer_mc._height;
  // Relocate any clips
  footer_mc._y = sh-footer_mc._height;
};
// Add the object as a listener to the stage
Stage.addListener(myListener);

In the above code I do a couple of things. First, I make sure Flash isn’t going to scale the SWF as the browser resizes because I want to handle all resizing with my own code. Second, I make sure the coordinate system remains intact by pinning the stage to the upper left corner of the movie. By default, Flash centers the stage. Next, I create a listener object that will execute some code every time the stage is resized. It knows that the stage is resized because I add it as a listener to the Stage object’s onResize event.

The onResize event can happen many times per second as the user resizes the window. I want to make sure that the event handler runs as efficiently as possible to make the redraws as smooth as possible by keeping the frame rate high. Inside the onResize event handler I store the Stage object’s width and height as local variables to speed up access to them should I need them often. Since all three of the sections are supposed to match the width of the browser at all times, I simply assign the Stage object’s width to all three sections. I chain them in one long assignment because this executes faster than if it were three separate assignments. Next, I figure out the height of the body section by subtracting the heights of the header and footer sections. Finally, I move the footer section to always be located at the height of the Stage object minus the height of itself, giving it the appearance of being docked to the bottom of the window.

[ Download all source code ]

Version 2

In this version, we are going to create a container for our GUI so that we can begin to add more complex functionality. By adding a container, I gain the benefit of having a MovieClip that owns all of the sections of my user interface. This MovieClip can be extended so that I can keep all of my code in an external ActionScript class file and reduce the amount of ActionScript found in the FLA.

Two things to note:

  1. An alternative to extending MovieClips is creating classes that composite one or more MovieClips at run-time and control them externally. I use this method from time to time. In this case, I choose to extend MovieClip because it’s both my preferred way of working and I think it makes more sense for this situation. Feel free to inherit or compose as you see fit.
  2. Separating the business layer from the presentation layer is always a good thing. By keeping all, or essentially all, of your ActionScript in external class files and your visual assets in your FLA you achieve this effect. The main reason is that you can take either layer and apply it to a different version of the other. For example, when AS3 comes out, you can rewrite all of your classes to take advantage of AS3 and use it in place of your AS2 set of classes with no huge impacts on the FLA. Vice versa, take the same set of classes and apply different presentation layers to customize an interface for different uses without making copies of your code.

The first thing I do to this new version is create a series of folders that will hold my ActionScript classes. The folders define the package (namespace) the classes live in so they need to be exact. Packages are used to avoid naming conflicts between classes of the same name. If you and I were to create two classes named CustomButton we would have a problem using them in the same project unless they were located in separate packages. Since classes must have a filename the same as the class name (plus the .as extension), it will not be able to put two classes with the same name into the same folder anyway, so that aids with preemptive conflict resolving.

The package structure I always use is “com.jor”. “com” for commercial is the standard base namespace, so I start with that. Inside the folder “com” I create the folder “jor”… short for, as you might have guessed, James O’Reilly. From there I create a series of different folders for different things. For this project I use two specific folders: “controls” which you will see much later and “examples”. Inside “examples” I create a folder for this project called “liquidgui” and that is the folder I will save all of the classes specific to this project in.

For my first class, I want to create a class to use with the GUI container and, since it’s a MovieClip, I will extend the MovieClip class to inherit its functionality. [In AS3 I would have extended Symbol for all the classes I used in this app as I don’t use the timeline, but since this is AS2 I stick with what’s available.] I create a file named mcGUI.as inside the liquidgui folder, open the file in Flash and add the following code:

dynamic class com.jor.examples.liquidgui.mcGUI
  extends MovieClip
{
  // Constants are used to speed up processing
  public static var ow:Number = 550;  // Original Stage Height
  public static var oh:Number = 400;  // Original Stage Width
  public static var hh:Number = 54;   // Static Header Height
  public static var fh:Number = 75;   // Static Footer Height
  public static var hfh:Number = 129; // Static Header + Footer Height
  // Private references to our children objects
  private var header_mc:MovieClip;
  private var body_mc:MovieClip;
  private var footer_mc:MovieClip;
  /**
   * onResize
   * Event handler listening to the Stage for resizing
   * @param   e   Event Object passed by the event
   */
  public function onResize (e:Object):Void
  {
    // Get the new stage size
    var sw:Number = Stage.width;
    var sh:Number = Stage.height;
    // Then update the children with this new size
    header_mc._width = body_mc._width = footer_mc._width = sw;
    body_mc._height = sh-hfh;
    footer_mc._y = sh-fh;
  }
}

Since we are extending MovieClip, which is a dynamic class, our custom class needs to be dynamic also. A dynamic class allows properties and methods to be added and deleted at run-time. By default, classes are closed, which is the opposite of dynamic.

My class name is mcGUI which resides in the com.jor.examples.liquidgui package and extends MovieClip, which can be seen on the first two lines.

Inside the class, I define some constants that I use throughout the lifespan of the application. These values never change, so I create them as static variables to improve performance over accessing the necessary properties of objects to get the same answer.

The next three variables are references to my three sections. You need to declare all visual objects of the MovieClip that are placed on the timeline of the clip at design-time or you will be unable to access them. The variable names must be the instance names you assigned to the clips.

Finally, you implement a public method named onResize which accepts an event object as a parameter. All of the code inside this method should look very familiar to you as it’s nearly identical to the code I used in version one, with the slight difference being that I use my constants rather then object properties to improve performance.

To actually use this class, you need to link it to a MovieClip. To do that, create a new MovieClip in the libray named mcGUI and place it in on the timeline in a new layer named GUI and give it an instance name of gui_mc. Then highlight the Header, Body and Footer frames on the timeline and select “copy frames” from the right-click context menu on the PC or option-click on the Mac. Paste the three layers onto the single layer of gui_mc’s timeline. It will create all three layers, preserving the layer names and order. The end result is two layers on the main timeline named Actions and GUI. The GUI layer contains a MovieClip with an instance name of gui_mc. gui_mc’s timeline contains three layers: one for header_mc, one for footer_mc and one for body_mc.

Next, to link the class mcGUI to the mcGUI in the library you right-click the symbol in the library and choose “Linkage” from the context menu. In the dialong that opens check the box that says “Export for ActionScript” and enter com.jor.examples.liquidgui.mcGUI as the AS2 Class name.

Lastly, remove most of the code from the Actions layer and simplify it with the following.

Stage.scaleMode = “noScale”;
Stage.align = “TL”;
Stage.addListener(gui_mc);

Now the gui_mc will listen for the onResize event instead of the myListener object I previously used.

[ Download all source code ]

to be continued…

8 Responses to “Creating Liquid GUIs with Flash - Part 2”

  1. James O'Reilly Says:

    Creating Liquid GUIs with Flash - Part 3

    With user experience expectations on the rise, developers more and more are looking for ways to bring the rich experience of a desktop application to the web. This is part three of the tutorial about developing dynamic graphical user interfaces (GUI) …

  2. armelle Says:

    Hi James,

    I’m following this tutorial and trying to use the script for my own website and its not working at the moment.
    I am new to the idea of liquid GUI’s but would love to achieve this touch to my site as I think its essential for user experience. I’ve followed well what you say up to ‘version 1′ I can get that working but when I try the code out on my site its not right. I’ve used the code and split my site up exactly as your version1 but in my site I loose my header and footer once I resize my browser window and also I will need to fix the size of the body … !!
    I’ve loaded up the result on my test space:
    http://www.armelle.co.uk/test/startagain12.html -
    I wonder if you can help me. It may just be too advance for me at the moment!! But if it’s something simple then perhaps I can fix it.
    Any advice would be most appreciated,
    armelle x

  3. James O'Reilly Says:

    Without seeing the code it’s hard to say. Try setting your body to invisible to see if the header and footer are there but just being covered up by the body. Also, take a look at part three of the tutorial and download the source code.

  4. armelle Says:

    Hi James,
    thanks for your reply to this and sorry its taken me time to get back to you …
    Im working on getting my head round the next part of your tutorial.
    thanks again
    armelle

  5. Corey Says:

    I’m working on something similar to this. I want the .swf to be centered and resize with the browser window, but at the same time reveal a tiled background image which is linked through Dreamweaver.

    I curently have the .swf set to 100% height & width and the scale is set to (Show All). This allows it to work correctly in terms of resizing with the browser window, however the background color of the .swf stretches to the left and right, so the tiled background pattern isn’t exposed.

    Do you know how I can reveal the tiled background on the left and right side while still having the centered .swf resize with the browser window?

    Thanks for any assistance you can offer.

  6. Martin Says:

    I’m an illustrator not a coder, and was looking for a simple way to achieve some image placement on a fullscreen page - and this was perfect & so well explained & learned some coding too!

    However, I’ve noticed that the GUI only stretches on a resize - not on the initial page load.

  7. James O'Reilly Says:

    Simply call your resize function manually either in the onLoad or frame one of the timeline.

  8. michélé Says:

    Hi, I’m having some problems with the resizing of the website that I’m making.
    I’ve set the publish settings tot 100×100 percent and exact fit so it would adjust to all screens.
    But just as in your tutorial it won’t fit on a 1600×1200 screen (which is not widescreen).
    I tried to follow your tutorial, but it won’t work.

    I’m not a big coder yet :)
    Pleas can you help me?

    Thanks Michélé
    Belgium

Leave a Reply


Bad Behavior has blocked 165 access attempts in the last 7 days.