-
Notifications
You must be signed in to change notification settings - Fork 0
ScreenLists and Routing
One of the biggest guiding principles behind Kestrel's design (and, in fact, the reason we created it in the first place), was the concept of "Screens", and how their construction is approached in Flash.
A "Screen" is a top-level View, that can contain any number of interactive Assets, as well as other, non-interactive Flash DisplayObjects. Screens are created in the Flash IDE, and can be built and designed by non-programmers who are familiar with the Flash interface.
Only one Screen can be seen at a time, except in the moments that a Screen may be transitioning to another Screen (depending on the kind of Transition you create). During those moments screens are "frozen", and aren't really available to your app anyway, so for the most part you should think of only one Screen being active at a time.
How you handle Screens is up to you and how you design your app flow. Kestrel is most useful to apps that have many screens, that the user moves through linearly or interactively, and much of the payload of the framework is dedicated to managing the transition between screens, their initialization, activation and deactivation.
Screens are created initially in Flash and brought into your app either in the main FLA that you compile, or in additional pre-compiled SWFs (called ScreenBundles) that are loaded in at runtime by the AssetLoader. A ScreenBundle is an FLA comprised of one or more Screens that is compiled to an SWF. Your app can leverage any number of ScreenBundles, or can simply consist of the main FLA.
Each Screen is contained in its own MovieClip that sits on the stage of its FLA on Frame 1. Generally, it is a best practice to create one Layer per Screen. That way you can show/hide your layers (screens) and lock them. If you do choose to hide your layers, make sure that when you publish your FLA, you leave "Include Hidden Layers" checked in your Publish Settings!
The screenshot above shows an example of a Timeline in an FLA that defines 9 screens, each on its own layer, with each layer conveniently named.
Screens are identified by their MovieClip's instance name. Within a ScreenBundle, each Screen must have a unique instance name in order to be able to be associated with the correct MovieClip. To give a Screen an instance name in Flash, select the MovieClip, open the Properties tab and type a unique name in the "" input area.
It's a best practice to not use spaces or special characters in your Screen names. To further distinguish them from ActionScript objects or method names, use underscores ("_") to separate words, as opposed to "camelCase". Some examples of screen names are: "loading_screen", "home", "options", "about_this_app".
Screens are organized into ScreenBundles. Each ScreenBundle corresponds exactly to an FLA that has been compiled into an SWF (either the main FLA, or additional SWFs that are packaged with the app and loaded some time during the running of the app).
Take some time to plan the organization of your ScreenBundles. The Screens in a ScreenBundle should be logically related in a workflow. For example, you might have your initial ScreenBundle that is part of the main FLA, and contains your loader screen, a "welcome" or "start" screen, and maybe an "options" screen. Then, if your App has a number of distinct sections to it, each with its own workflow, each of those sections might warrant a ScreenBundle of its own.
The main FLA ScreenBundle is a special case - it is part of the compiled app code that is loaded upon launching of the app and gets stored in memory before any of the App logic even starts up. The bigger the payload, the longer it takes before you app becomes responsive. On iOS, your app will just show the default Startup PNG until the entire FLA has been loaded, so the bigger the FLA, the longer your users will have to wait before they see anything happening at all. If this load time gets too long, users might think the app has crashed.
Unless your app has a small number of screens, it is recommended to limit the number of screens in the main FLA to a loader screen, a welcome screen, and maybe your options screen(s). This ensures a snappy app launch, and allows you to leverage the AssetLoader to full effect, possibly adding a progress bar to indicate loading progress, or even deferring the loading of other ScreenBundles until they become necessary later in the app workflow.
The ScreenList is a special Class that you create and pass into your App by assigning it to the special screenList
property (which is actually a setter
, and does a number of things as soon as you assign to it). The ScreenList defines the complete list of all Screens you would like your App's Router to have access to.
The ScreenList also kicks off the loading of any additional ScreenBundle SWFs, adding them to the AssetLoader's queue to be loaded one at a time prior to your App being "ready" for action (the appReady()
hook).
Your ScreenList must implement the IScreenList
interface, which currently only requires a single public function: getScreenBundles()
that returns a Vector of type <ScreenBundle>
. If you're not familiar with Vectors, look them up in the ActionScript reference, but they're essentially a typed Array.
Here's a basic scaffolding for a ScreenList class that implements IScreenList. Use this as the starting point for your own ScreenList.
package {
import com.zeitguys.mobile.app.model.IScreenList;
import com.zeitguys.mobile.app.model.ScreenBundle;
/**
* The complete list of screens that are available to the App.
*
* @author Tom Auger
*/
public class ScreenList implements IScreenList {
public function ScreenList() { }
/* INTERFACE com.zeitguys.mobile.app.model.IScreenList */
public function getScreenBundles():Vector.<ScreenBundle> {
return new <ScreenBundle>[ ];
}
}
}
This scaffolding implements getScreenbundles()
in accordance with the IScreenList
interface "contract", and returns a (empty) Vector of <ScreenBundle>
s. At the moment, not terribly useful, other than to keep things clean and simple for the purpose of illustrating the concept.
Once you have created (or started to create) your ScreenList, you need to tell your App to use it in order to:
- Hide any screens in the main FLA so that only one Screen (usually your "loader" screen) is showing at a time
- Kick off the loading of any additional Screens that might be located in external SWFs (see "ScreenBundles", below)
- Let the ScreenRouter know the names of all of these screens so that you can easily transition to any screen from pretty much anywhere in your code using
ScreenRouter.setScreen()
.
This needs to be done very early on in the App's startup cycle. The best place to do this is within your App's initialize()
method.
override protected function initialize():void {
screenList = new ScreenList();
}
ScreenBundles are...
The Main Screen Bundle (curiously named MainScreenBundle
) is only defined once, and is used to define all the screens that are contained in the main FLA that compiles into your app. If your main FLA contains nothing other than a loader, then you don't need to instantiate a MainScreenBundle
at all.
MainScreenBundle
extends ScreenBundle
, but it does a couple of things differently:
-
it already contains a reference to its SWF (your App, in this case), and it does not add its SWF path to the AssetLoader's queue for loading.
-
it strips out all direct children of the main SWF's root, namely all your Screens that you have on the Stage in your main FLA.
This last point is important to remember if you add anything to "the Stage" before you've defined your ScreenList - it will be stripped out and will disappear, much to your consternation. Lesson learned: wait until after you've called
set screenList()
before doing anything at all with the Stage.AppBase runs into this very issue with its
set transitionManager()
(called byinitializeTransitionManager()
) - which needs to run after the ScreenList has been defined. For that reason,initializeTransitionmanager()
is actually called withinset screenList()
and after the Router has processed the ScreenList.
In the following code, we've added to our ScreenList
class, and actually defined our MainScreenBundle
. As defined by our ScreenList, our MainScreenBundle defines three Screens (ScreenView
instances) that correspond to three MovieClips we have created on our main timeline in our main FLA, with the following instance names: "first_run", "settings" and "home".
package {
import com.zeitguys.mobile.app.model.IScreenList;
import com.zeitguys.mobile.app.model.MainScreenBundle;
import com.zeitguys.mobile.app.model.ScreenBundle;
import com.zeitguys.mobile.app.view.ScreenView;
/**
* The complete list of screens that are available to the App.
*
* @author Tom Auger
*/
public class ScreenList implements IScreenList {
public function ScreenList() { }
/* INTERFACE com.zeitguys.mobile.app.model.IScreenList */
public function getScreenBundles():Vector.<ScreenBundle> {
return new <ScreenBundle>[
new MainScreenBundle("main", new <ScreenView>[
new ScreenView("first_run"),
new ScreenView("settings"),
new ScreenView("home")
])
];
}
}
}
The ScreenView
class, defined in com.zeitguys.mobile.app.view
inherits from ViewBase
and is the base class for all Screens in your app. ScreenViews are very powerful an represent a huge proportion of the business logic of the Kestrel framework. ScreenViews:
- can dispatch all sort of events at different times, such as when they are loaded, when the screen is ready, when the screen is activated or deactivated
- can define any number of Assets (
AssetView
instances) that get automatically activated and deactivated when the Screen gets activated or deactivated - have a reference to the Router, so a screen can trigger a switch to some other screen elsewhere in the App, depending on user interactions on that Screen
- can define a default Transition for when you leave the screen
- can access
screenArgs
- arguments that allow one Screen to communicate with the Screen that it transitions to so you can get a Screen to behave differently depending on which Screen it's coming from - will automatically localize all its assets
In the code sample above, we define 3 ScreenViews. The constructor's only argument is the instance name of the MovieClip on the main timeline of the ScreenBundle's FLA. After the ScreenBundle has been loaded (or right away, in the case of the MainScreenBundle), the ScreenView looks through its ScreenBundle's SWF and attempts to locate the MovieClip that it corresponds to. If a MovieClip with that name is not found, you'll get a FlashConstructionError
(a custom error type unique to Kestrel that tells you there's something wrong with the way your FLA is constructed: some required DisplayObject is missing, or is not parented properly).
To learn how to define custom Screens, check out Creating Screens and defining Assets elsewhere in this wiki.