Using CSS for Phone and Tablet UI

In the brief time I spent pitching in on the MAX Companion app this year I learned a neat trick from the Adobe XD team. As you can see from the screenshots above, the launcher view looks mostly similar, but the layout and button skinning is noticeably different. This is an example of adapting an application based on the form-factor. One thing that isn’t obvious right away is that the MAX Companion is a single binary that adapts to phone and tablet form factors at runtime.

One way to do this is to use MXML states. You might have went to Glenn’s MAX session or read the posts from evangelists Michaël Chaize or Andrew Trice. All 3 talk about how to determine form factor based on resolution, then define states and state groups.

Watch: Best Practices for Building Flex Tablet Applications Glenn Ruehle
Read: Adaptive UI: Phone vs. Tablet Michaël Chaize
Read: Flex for Mobile – Device Form Factor Detection Andrew Trice

With the MXML states strategy, you could accomplish the same result by setting skin classes separately for skin and tablet states.

What I learned from the MAX Companion app was that this can also be done in CSS. Remember that Flex 4.5 introduces media queries that let you define styles (and of course skin classes) based on os-platform and application-dpi. However, CSS media queries won’t tell you anything about screen resolution. Therefore, with CSS, there’s no out-of-the box way to detect a phone vs. a tablet. Basically, what the XD team’s solution boiled down to was CSS code that looks like this:

s|Application views|LauncherView s|Button { skinClass: ClassReference("PhoneButtonSkin"); }
s|Application.tablet views|LauncherView s|Button {skinClass: ClassReference("TabletButtonSkin"); }

 

The first rule sets a default Button skin. The second rule sets a skin only when the Application has the “tablet” style name. In the MAX Companion app, on initialize, they compute the form factor based on the screen (Capabilities.screenResolutionX and Capabilities.screenResolutionY) and the DPI (Capabilities.screenDPI). Based on that information, they find the diagonal size of the screen and set application.styleName = “tablet”. It’s a super simple technique that gives you a lot more flexibility with skinning.

I personally find this approach easier to manage than the MXML states approach, especially when the skinning and layout between form factors are different enough that they become hard to manage in a single MXML file. Also, with the MXML technique, each View would require their own state declarations. With this CSS technique, the form factor detection can be centralized at the Application’s styleName.

I’ve attached FormFactorStyles.fxp as a demo of this CSS trick. In my Application’s initialize event handler, I call a utility method FormFactorUtil.initStyleName(this). This function determines the screen’s diagonal size. Anything less than 7 inches gets the “phone” style name, all others get “tablet”. The app also shows some other useful debug information like OS, screenDPI, and the difference between stage dimensions vs. screenResolutionX and screenResolutionY.

The snippet below shows how FormFactorUtil computes the screen diagonal and other debug info.

/**
 *  Updates all screen measurements when the stage is resized.
 */
private function updateSize(event:Event=null):void
{
    var stage:Stage = FlexGlobals.topLevelApplication.stage;

    _diagonal = Math.sqrt(Math.pow(Capabilities.screenResolutionX, 2) + Math.pow(Capabilities.screenResolutionY, 2));
    _diagonal = _diagonal / Capabilities.screenDPI;

    _width = Capabilities.screenResolutionX / Capabilities.screenDPI;
    _height = Capabilities.screenResolutionY / Capabilities.screenDPI;

    _formFactor = (_diagonal <= 7) ? PHONE : TABLET;

    _stageFullScreenResolution = new Rectangle(0, 0, stage.fullScreenWidth, stage.fullScreenHeight);
    _stageResolution = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
    _screenResolution = new Rectangle(0, 0, Capabilities.screenResolutionX, Capabilities.screenResolutionY);
    _screenSize = new Rectangle(0, 0, _width, _height);

    instance.debugData = new ArrayCollection([
        {label: "OS", value: FormFactorUtil.os},
        {label: "screenDPI", value: Capabilities.screenDPI},
        {label: "applicationDPI", value: FlexGlobals.topLevelApplication.applicationDPI},
        {label: "stageResolution (stageWidth, stageHeight)", value: _stageResolution},
        {label: "stageFullScreenResolution (fullScreenWidth, fullScreenHeight)", value: _stageFullScreenResolution},
        {label: "screenResolution (screenResolutionX, screenResolutionY) in pixels", value: _screenResolution},
        {label: "screenSize (screenResoltionX, screenResolutionY) in inches", value: _screenSize},
        {label: "diagonal in inches", value: diagonal.toFixed(1)}
    ]);
}

 

There’s a lot more in this app to talk about. Way too much for one blog post. Check out the screen shots below and stay tuned for more tips developing phone and tablet apps simultaneously as the Flex 4.6 release date gets closer.

Share on Facebook