← KlugLayout

Documentation

KlugLayout documentation

com.klug.layout

Installation, guides and the complete API for landscape on both sides, display rotation and foldables (Android Fold and Flip, iPhone Duo).

AndroidiOS Version 1.1.0 Product page · Buy

Introduction

KlugLayout gives an AIR app control over the parts of the screen layout that AIR does not handle on its own on mobile devices:

  • Landscape on both sides on Android. AIR's StageAspectRatio.LANDSCAPE fixes one landscape side; KlugLayout lets the app turn by 180 degrees with the device while it stays in landscape.
  • Display rotation in degrees, so the app can turn its own content, such as a camera picture.
  • Foldables: the app learns whether it is shown on a phone-sized, tablet-sized or cover screen, and gets an event every time this changes while it runs: Galaxy Z Fold, Pixel Fold, Galaxy Z Flip, Motorola Razr and the iPhone Duo.
  • Current window values: the size and top inset of the display the app is on now, also after a foldable moved the app to its other display.

On every other platform the extension is present but inactive: isSupported is false and every call is a no-op, so the same code can stay in a cross-platform project.

Extension IDcom.klug.layout
Version1.1.0
PlatformsAndroid (armv7, arm64, x86, x86_64), iOS (arm64). Other platforms: no-op.
Deliverycom.klug.layout.ane (the extension), KlugLayout.swc (ActionScript library for code completion and compiling) and this documentation.
Packagecom.klug.layout, one class: KlugLayout

Requirements

AIR SDK51.x (HARMAN). iPhone Duo support tested with AIR 51.4.1.
AndroidAndroid 6.0 (API 23) or later. isFoldable and the current-window size need Android 11 (API 30) or later; safeInsetTop needs Android 9 (API 28) or later.
iOSiOS 13 or later, iPhone and iPad. Foldable support: iPhone Duo.
DevicesPhones, tablets and foldables. Tested in the Android emulator with Pixel 9 Pro Fold and Galaxy Z Flip profiles, and in the Xcode 27 simulator with the iPhone Duo.
PermissionsNone.

Add the extension

Self-contained: no core library ANE and no dependency ANEs are needed. One file, one extensionID.

1 · Copy the ANE

Copy com.klug.layout.ane into the extension folder of your project (for example lib/ or extensions/) and add it to your build: in your IDE as a native extension of the project, or with -external-library-path for compiling and -extdir for packaging on the command line.

2 · Declare the extension ID

app.xml
<extensions>
    <extensionID>com.klug.layout</extensionID>
</extensions>

3 · Initial window

KlugLayout works together with AIR's own orientation handling. Let the stage follow the device; the app then decides the allowed orientation at runtime with stage.setAspectRatio() and KlugLayout:

app.xml
<initialWindow>
    <content>Main.swf</content>
    <autoOrients>true</autoOrients>
    <aspectRatio>any</aspectRatio>
    <fullScreen>false</fullScreen>
</initialWindow>

4 · Android: allow a fixed orientation on large screens

No permission, activity or service is needed. Apps that target Android 16 (API 36) must opt out of the large-screen orientation restrictions, otherwise Android ignores any orientation request on tablets and opened foldables, including setSensorLandscape() and AIR's own LANDSCAPE:

app.xml
<android>
    <manifestAdditions><![CDATA[
<manifest>
    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="36"/>
    <application android:hardwareAccelerated="true">
        <!-- needed from targetSdkVersion 36 for a fixed orientation on large screens -->
        <property android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
                  android:value="true"/>
    </application>
</manifest>
    ]]></manifestAdditions>
</android>

5 · iOS

No InfoAdditions keys and no entitlements are needed. If your iPad app should not be resizable in Split View or Stage Manager, keep UIRequiresFullScreen as you have it today; KlugLayout does not depend on it.

6 · macOS and Windows

Nothing to add. The extension contains a default implementation for these platforms, so desktop builds of the same project compile and run; isSupported is false there.

Getting started

KlugLayout is a singleton. Get it with getInstance(), check isSupported if you need to know, and call the methods once the stage exists (for example in applicationComplete or on Event.ADDED_TO_STAGE). Every call returns at once. Getters answer synchronously; changes of the screen class arrive later as a StatusEvent.

Main.as
import com.klug.layout.KlugLayout;
import flash.display.StageAspectRatio;
import flash.events.StatusEvent;

var layout:KlugLayout = KlugLayout.getInstance();
trace( "KlugLayout active: " + layout.isSupported );   // false on desktop: all calls are no-ops

// 1. landscape, both sides
stage.setAspectRatio( StageAspectRatio.LANDSCAPE );
layout.setSensorLandscape();

// 2. foldables: listen first, then start watching
layout.addEventListener( StatusEvent.STATUS, onLayoutStatus );
layout.watchFold( stage );

// 3. events report changes only: read the current class once
trace( "screen class: " + layout.deviceClass );       // "phone", "tablet", "cover" or ""
trace( "rotation: " + layout.displayRotation );       // 0, 90, 180, 270 or -1

function onLayoutStatus( e:StatusEvent ):void
{
    if ( e.code == KlugLayout.DEVICE_TYPE_CHANGED )
        trace( "now on a " + e.level + " screen" );
}

Guides

Landscape on both sides

For apps that only make sense in landscape, such as a camera station on a tablet, users still expect the picture the right way up whichever way they put the device down. Call setSensorLandscape() directly after stage.setAspectRatio(StageAspectRatio.LANDSCAPE): AIR first switches to landscape, then KlugLayout allows both landscape sides on Android.

Android: the app stays in landscape and turns with the device. KlugLayout keeps the lock in place while the app runs, also when AIR itself asks for a different orientation later (for example after stage.autoOrients is set again). iOS: the call is a no-op, because LANDSCAPE already allows both landscape sides there.

StationScreen.as
import com.klug.layout.KlugLayout;
import flash.display.StageAspectRatio;

public function enterLandscapeScreen():void
{
    stage.autoOrients = true;
    stage.setAspectRatio( StageAspectRatio.LANDSCAPE );   // AIR: landscape (one side on Android)
    KlugLayout.getInstance().setSensorLandscape();        // both sides, turning with the device
}

Follow the display rotation

displayRotation returns how the display is turned right now, in degrees: 0, 90, 180 or 270, or -1 when it cannot be read. Use it for content the stage does not turn for you, for example a camera picture or a native video view. Read it at start and again on every StageOrientationEvent.ORIENTATION_CHANGE.

ValueAndroid
0 · 90 · 180 · 270The display's rotation from its natural orientation (portrait on phones, landscape on most tablets).
ValueiOS
0Portrait
90Landscape, home indicator / button on the right
180Portrait upside down
270Landscape, home indicator / button on the left
-1Unknown (no active window yet, or not supported)
Rotation.as
import com.klug.layout.KlugLayout;
import flash.events.StageOrientationEvent;

var layout:KlugLayout = KlugLayout.getInstance();

applyRotation();
stage.addEventListener( StageOrientationEvent.ORIENTATION_CHANGE, function( e:StageOrientationEvent ):void {
    applyRotation();
});

function applyRotation():void
{
    var deg:int = layout.displayRotation;
    if ( deg == -1 ) return;              // unknown: keep the last value
    rotateCameraPicture( deg );           // your app function
}

Leave the landscape lock

When the app moves from a landscape-only screen to a screen that may be portrait, remove the lock with release() and let AIR decide again with stage.setAspectRatio(). Fold watching continues after release(). Call setSensorLandscape() again to return to both landscape sides. On iOS release() is a no-op; stage.setAspectRatio() alone is enough there.

Screens.as
function openSettingsScreen():void
{
    KlugLayout.getInstance().release();                 // lock off (Android)
    stage.setAspectRatio( StageAspectRatio.PORTRAIT );  // AIR decides again
    showSettings();                                     // your app function
}

function backToStation():void
{
    stage.setAspectRatio( StageAspectRatio.LANDSCAPE );
    KlugLayout.getInstance().setSensorLandscape();      // both landscape sides again
}

Watch for screen changes

watchFold(stage) starts watching the screen class without changing the orientation. From then on every change is sent as a StatusEvent with code == KlugLayout.DEVICE_TYPE_CHANGED and the new class in level. On Android, setSensorLandscape() starts the same watching, so a landscape app that already calls it receives the events too.

  • Add the listener before you call watchFold().
  • Events report changes only. The class at start is read with the deviceClass getter.
  • Always pass the stage. Android does not need it, iOS does (iPhone Duo); with the stage the same line works on both.
  • Call it once, early, when the stage exists. On iOS only the first call with a stage takes effect; further calls are harmless.
  • On devices that never change their screen (normal phones and tablets) no event is sent.
ScreenClass.as
import com.klug.layout.KlugLayout;
import flash.events.StatusEvent;

var layout:KlugLayout = KlugLayout.getInstance();
var currentClass:String = "";

layout.addEventListener( StatusEvent.STATUS, onLayoutStatus );
layout.watchFold( stage );

// start value; "" = unknown: use your own phone / tablet decision
currentClass = layout.deviceClass;
if ( currentClass == "" ) currentClass = myOwnPhoneOrTablet();   // your app function
buildScreen( currentClass );                                          // your app function

function onLayoutStatus( e:StatusEvent ):void
{
    if ( e.code != KlugLayout.DEVICE_TYPE_CHANGED ) return;
    if ( e.level == currentClass ) return;
    currentClass = e.level;               // "phone", "tablet" or "cover"
    buildScreen( currentClass );
}

Lay out for the current display

After a fold change, AIR's start-up values (screen resolution, cutout, sometimes even the stage size for a moment) can still describe the previous display. Read the values of the display the app is on now from KlugLayout:

  • windowWidth / windowHeight: size of the app window in pixels. Android 11 and later: the current window; Android 10 and earlier: the full display. iOS: the app window. -1 when unknown.
  • safeInsetTop: top inset in pixels. Android 9 and later: the camera cutout of the current display, 0 if it has none; earlier versions: -1. iOS: the top safe area of the app window.
Layout.as
function buildScreen( cls:String ):void
{
    var layout:KlugLayout = KlugLayout.getInstance();
    var w:int = layout.windowWidth  > 0 ? layout.windowWidth  : stage.stageWidth;
    var h:int = layout.windowHeight > 0 ? layout.windowHeight : stage.stageHeight;
    var top:int = Math.max( 0, layout.safeInsetTop );   // -1 = unknown

    removeAllViews();                                   // your app functions from here on
    if ( cls == "tablet" )
        buildTabletViews( w, h, top );
    else
    {
        buildPhoneViews( w, h, top );
        if ( cls == "cover" ) scaleToFit( w, h );   // phone layout, scaled down
    }
}

Android foldables: Galaxy Z Fold and Flip, Pixel Fold, Razr

A foldable moves the running app from one display to another. The app stays alive, but its screen changes completely, and AIR does not always send a resize event. KlugLayout reports the change as one of three classes:

DeviceClosed · Opened
Galaxy Z Fold, Pixel Fold"phone" on the outer display · "tablet" on the inner display
Galaxy Z Flip"cover" on the small outer screen · "phone" on the main display
Motorola Razrouter screen: "cover" or "phone", depending on the model's outer display · "phone" on the main display

What your app should do

  • Start right. Call watchFold(stage) early and choose the first layout from deviceClass. A Fold can be started closed or opened.
  • Rebuild on every event. Tear the current screen down and build the variant for the new class from windowWidth, windowHeight and safeInsetTop. Do not wait for Event.RESIZE.
  • Restart what is live. Camera, video playback, native views and timers that were sized for the old display are best stopped and started again on the new one. A clean pattern for apps with a login is: on a fold change while signed in, sign out, show the start screen and sign in again automatically.
  • Landscape apps. If setSensorLandscape() is active, KlugLayout suspends the landscape lock while the device is on a phone-sized display ("phone" or "cover"), so the narrow outer screen can use portrait. When the device is opened again ("tablet"), the lock comes back by itself. Set the stage's aspect ratio that suits each layout in your event handler.
  • The Flip cover screen. It is small and almost square. Lay out the phone variant for the window size and scale it down to fit, or show a reduced screen. Treat "cover" like "phone" everywhere except in sizing, then every flip-style device works.
  • Fold-only behaviour. isFoldable is true on foldable hardware (Android 11 or later), so hints such as "open the device for the large view" never appear on normal phones and tablets.
  • Android 16. Add the manifest property from Add the extension; without it, opened foldables ignore any fixed orientation.
Foldables.as
import com.klug.layout.KlugLayout;
import flash.display.StageAspectRatio;
import flash.events.StatusEvent;

var layout:KlugLayout = KlugLayout.getInstance();
var screenClass:String = "";

function initScreens():void   // call from applicationComplete
{
    layout.addEventListener( StatusEvent.STATUS, onLayoutStatus );
    layout.watchFold( stage );
    screenClass = layout.deviceClass != "" ? layout.deviceClass : "phone";
    applyClass();
    if ( layout.isFoldable ) showFoldHint();   // your app function
}

function onLayoutStatus( e:StatusEvent ):void
{
    if ( e.code != KlugLayout.DEVICE_TYPE_CHANGED || e.level == screenClass ) return;
    screenClass = e.level;
    stopLiveMedia();     // your app function: camera, video, native views
    applyClass();
}

function applyClass():void
{
    if ( screenClass == "tablet" )
    {
        stage.setAspectRatio( StageAspectRatio.LANDSCAPE );
        layout.setSensorLandscape();
    }
    else
    {
        stage.setAspectRatio( StageAspectRatio.PORTRAIT );   // phone and cover
    }
    buildScreen( screenClass );   // see "Lay out for the current display"
}

Test fold changes in the Android emulator with the foldable device profiles (for example Pixel 9 Pro Fold and Galaxy Z Flip): the emulator can fold and unfold the device while your app runs.

iPhone Duo

The iPhone Duo is a phone when closed and a small tablet when opened. AIR does not follow this on its own: the app keeps the layout and the start-up screen values of the display it was launched on. With KlugLayout the app receives the same event as on Android:

Duo closedDEVICE_TYPE_CHANGED, level "phone"
Duo openedDEVICE_TYPE_CHANGED, level "tablet"

What your app should do

  • Pass the stage. On iOS watchFold(stage) needs the stage; without it no event is sent and deviceClass stays "".
  • Call it once at launch, in applicationComplete when the stage has its size, and read deviceClass right after it for the first layout. Without this, an app launched on the opened Duo would treat it as a phone.
  • Switch orientation with the class. Typical: "phone" → StageAspectRatio.PORTRAIT and the phone layout, "tablet" → StageAspectRatio.LANDSCAPE (or ANY) and the tablet layout. A portrait-only app on the opened Duo stays in a phone-sized window, so allow the tablet orientation when you receive "tablet".
  • Choose sizes by class, not by screen density. The opened Duo has the pixel density of an iPhone but the size of a small iPad; use your tablet font sizes for "tablet".
  • Re-apply the stage size after rotation. When the opened Duo is turned, size your root view to stage.stageWidth × stage.stageHeight again on Event.RESIZE and ORIENTATION_CHANGE, so frameworks with their own layout pass (such as Flex views) do not keep the portrait size.
  • Restart what is live, exactly as on Android.
  • isFoldable is true on the iPhone Duo and false on every other iPhone and iPad. iOS never reports "cover".
Duo.as
import com.klug.layout.KlugLayout;
import flash.display.StageAspectRatio;
import flash.events.Event;
import flash.events.StatusEvent;

var layout:KlugLayout = KlugLayout.getInstance();
var opened:Boolean = false;

function onApplicationComplete( e:Event ):void
{
    layout.addEventListener( StatusEvent.STATUS, onLayoutStatus );
    layout.watchFold( stage );                         // the stage is required on iOS
    if ( layout.isFoldable )
        applyDuo( layout.deviceClass == "tablet" );   // launched opened or closed
    else
        buildDefaultLayout();                          // your app function: other iPhones and iPads
}

function onLayoutStatus( e:StatusEvent ):void
{
    if ( e.code != KlugLayout.DEVICE_TYPE_CHANGED ) return;
    var nowOpened:Boolean = ( e.level == "tablet" );
    if ( nowOpened == opened ) return;
    stopLiveMedia();                                   // your app function
    applyDuo( nowOpened );
}

function applyDuo( isOpened:Boolean ):void
{
    opened = isOpened;
    stage.setAspectRatio( opened ? StageAspectRatio.LANDSCAPE : StageAspectRatio.PORTRAIT );
    useFontSizes( opened ? "tablet" : "phone" );     // your app functions
    buildScreen( opened ? "tablet" : "phone" );
}

Test in the Xcode simulator with the iPhone Duo device: fold and unfold it while the app runs, and rotate the opened device.

Background and return

Nothing needs to be stopped when the app goes to the background. The landscape lock and the fold watching stay in place. If the device was folded or unfolded while the app was in the background, the change is reported at the latest when the app is active again. Re-read displayRotation on Event.ACTIVATE if your content depends on it.

Lifecycle.as
import flash.desktop.NativeApplication;
import flash.events.Event;

NativeApplication.nativeApplication.addEventListener( Event.ACTIVATE, function( e:Event ):void {
    applyRotation();   // see "Follow the display rotation"
});

Cross-platform code and errors

KlugLayout never throws from its methods and getters: native errors are caught and the getters return their "unknown" value (-1, "" or false). The only error is the singleton guard: calling new KlugLayout() a second time throws an Error; always use getInstance(). Code that calls KlugLayout can therefore stay unchanged in desktop builds.

Safe.as
var layout:KlugLayout = KlugLayout.getInstance();

var w:int = layout.windowWidth;
if ( w == -1 ) w = stage.stageWidth;          // desktop, or value not available

var cls:String = layout.deviceClass;
if ( cls == "" ) cls = myOwnPhoneOrTablet();   // your app function

API reference

class KlugLayout

Package com.klug.layout · extends flash.events.EventDispatcher · singleton.

MemberDescription
DEVICE_TYPE_CHANGED:String = "deviceTypeChanged"Static constant. The code of the StatusEvent sent when the screen class changes; level is the new class.
getInstance():KlugLayoutStatic. Returns the single instance and creates it on first use.
KlugLayout()Constructor. Do not call it directly: a second call throws an Error. Use getInstance().
isSupported:BooleanRead-only. True when the native part is available: Android and iOS. False on other platforms, where every call is a no-op.
setSensorLandscape():voidLandscape only, both sides, turning with the device; the lock is held while the app runs. Call it right after stage.setAspectRatio(StageAspectRatio.LANDSCAPE). Also starts screen-class watching. Android; no-op on iOS.
release():voidRemoves the landscape lock; stage.setAspectRatio() decides again. Screen-class watching continues. Android; no-op on iOS.
watchFold(stage:Stage = null):voidStarts watching the screen class without changing the orientation; changes arrive as DEVICE_TYPE_CHANGED. stage: the application stage, required on iOS for the iPhone Duo, ignored on Android. Android and iOS.
deviceClass:StringRead-only. The current screen class: "phone", "tablet", "cover" (small outer screen of a flip phone, Android only), or "" when unknown. Android: always available. iOS: available after watchFold(stage) on the iPhone Duo; "" otherwise.
isFoldable:BooleanRead-only. True on foldable hardware: Android 11 or later foldables, and the iPhone Duo. False elsewhere and on older Android versions.
displayRotation:intRead-only. The current display rotation in degrees: 0, 90, 180 or 270; -1 when unknown. Android and iOS.
windowWidth:intRead-only. Width of the current app window in pixels; -1 when unknown. Android 11+: current window; Android 10 and earlier: full display. iOS: app window.
windowHeight:intRead-only. Height of the current app window in pixels; -1 when unknown. Same platform rules as windowWidth.
safeInsetTop:intRead-only. Top inset of the current display in pixels. Android 9+: camera cutout, 0 without cutout; earlier Android: -1. iOS: top safe area of the app window. -1 when unknown.

KlugLayout also inherits addEventListener(), removeEventListener(), hasEventListener() and dispatchEvent() from EventDispatcher.

Events

KlugLayout dispatches one event type: flash.events.StatusEvent.STATUS, on the KlugLayout instance.

codeWhen · level
deviceTypeChangedThe screen class changed. level: "phone", "tablet" or "cover" (Android only). Android: after watchFold() or setSensorLandscape(). iOS: after watchFold(stage) on the iPhone Duo. Sent on changes only, never for the start value; delivered asynchronously.

All other information is returned by the getters.

Troubleshooting

I never receive deviceTypeChanged at start.

That is by design: the event reports changes only. Read deviceClass right after watchFold(stage) for the first layout.

deviceClass is "" on iOS.

On iOS the class is only known on the iPhone Duo and only after watchFold(stage) was called with the stage. On other iPhones and iPads it stays ""; use your own phone or tablet decision there.

The Duo is treated as a phone although it is open.

watchFold() was called without the stage, too late, or before the stage had its size. Call watchFold(stage) once in applicationComplete and read deviceClass after it.

The tablet starts in portrait or ignores the landscape lock (Android 16).

Apps targeting API 36 must declare PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY in the <application> of the manifest additions; see Add the extension. Without it, Android ignores orientation requests on large screens.

The app turns upside down or to one fixed side on Android.

Call setSensorLandscape() after stage.setAspectRatio(StageAspectRatio.LANDSCAPE), not before: the later call wins.

After closing a Fold the app is in portrait, although I set the landscape lock.

Intended. On a phone-sized display the lock is suspended, so the outer screen can be used normally. It comes back when the device is opened. If you do not want this, call stage.setAspectRatio(StageAspectRatio.LANDSCAPE) yourself in your "phone" handler.

setSensorLandscape() and release() do nothing on iOS.

Both are Android features. On iOS, StageAspectRatio.LANDSCAPE already allows both landscape sides, and stage.setAspectRatio() alone changes the orientation.

windowWidth, windowHeight or safeInsetTop return -1.

-1 means the value is not available: on desktop platforms, before the app window exists, or (for safeInsetTop) on Android 8.1 and earlier. Fall back to stage.stageWidth, stage.stageHeight and 0.

My native views (video, camera preview, web view) are in the wrong place after folding.

Native views from other extensions sit above the AIR stage and do not move by themselves. Reposition or restart them in your deviceTypeChanged handler, using windowWidth, windowHeight and safeInsetTop.

isFoldable is false on a foldable Android phone.

It needs Android 11 or later. Fold events and deviceClass do not depend on it and work on every supported Android version.

"KlugLayout is a singleton" error.

The class was created with new while an instance already existed. Replace every new KlugLayout() with KlugLayout.getInstance().

Changelog

1.1.0 · Oct 2026iOS support. The iPhone Duo is reported as foldable; watchFold(stage) sends "phone" when it is closed and "tablet" when it is opened. displayRotation, windowWidth, windowHeight and safeInsetTop on iOS.
1.0.0 update · Oct 2026New: isFoldable.
1.0.0 update · Oct 2026New: windowWidth and windowHeight, the size of the current window, also after a foldable changes its display.
1.0.0 update · Oct 2026New screen class "cover" for the outer screen of flip phones, and the deviceClass getter.
1.0.0 update · Oct 2026Foldables: watchFold(), the deviceTypeChanged event, release() and safeInsetTop. The landscape lock is suspended while a Fold is closed and restored when it is opened.
1.0.0 update · Sep 2026The landscape lock holds more reliably while the device is being turned, without a short flash to portrait, and uses less work while the device rests.
1.0.0 · Sep 2026First release for Android: setSensorLandscape() (landscape on both sides) and displayRotation.