Air for Android: Screen Orientation

While developing mobile applications you may want to have your application change and move based on the orientation of the screen based on how the user has oriented their mobile device. This may seem obvious and “default” but there is some programming that goes along with implementing this.

On the highest level you just need to add a stage listener and a tag into the application descriptor – we will look at the exact code.

If you are starting with this post I recommend actually starting with a previous post introducing AIR for Android Development. There is a ANT file there that you will want to have.


Application
Here you will notice that I just listen to the stage for the StageOrientationEvent.ORIENTATION_CHANGE event. When the event fires there are five possible orientations that the stage can be in: default, rotated left, rotated right, upside down, and unknown.

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageOrientation;
    import flash.display.StageScaleMode;
    import flash.events.AccelerometerEvent;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.StageOrientationEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.net.navigateToURL;
    import flash.sensors.Accelerometer;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import flash.ui.Keyboard;

    /**
     * Screen orientation testing application.
     *
     * @author jonbcampos
     *
     * @see http://unitedmindset.com/jonbcampos/2010/09/17/air-for-android-home-menu-back-and-search-buttons
     */

    public class Screen extends Sprite
    {

        //---------------------------------------------------------------------
        //
        //  Constructor
        //
        //---------------------------------------------------------------------
        public function Screen()
        {
            //setup stage
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            //add handlers
            addEventListener(Event.ADDED_TO_STAGE, _onAddedToStage);
        }

        //---------------------------------------------------------------------
        //
        //  Private Properties
        //
        //---------------------------------------------------------------------
        private var _text:TextField;
        //---------------------------------------------------------------------
        //
        //  Protected Methods
        //
        //---------------------------------------------------------------------
        protected function createChildren():void
        {
            //text
            if(!_text)
            {
                _text = new TextField();
                _text.x = 10;
                _text.y = 10;
                _text.width = 400;
                _text.height = 600;
                _text.multiline = true;
                var tf:TextFormat = new TextFormat();
                tf.size = 24;
                _text.defaultTextFormat = tf;
                _text.text = "Create Children"
                addChild(_text);
            }
        }
        //---------------------------------------------------------------------
        //
        //  Handler Methods
        //
        //---------------------------------------------------------------------
        private function _onAddedToStage(event:Event):void
        {
            //removes listener
            removeEventListener(Event.ADDED_TO_STAGE, _onAddedToStage);
            //draws background
            _draw();
            //creates children
            createChildren();
            //add listeners
            if(stage.autoOrients)
            {
                stage.addEventListener(StageOrientationEvent.ORIENTATION_CHANGE, _onStage_OrientationChange);
                _text.appendText("\nDoesn't Auto Orients");
            } else {
                _text.appendText("\nAuto Orients");
            }
            _text.appendText("\nOrientation Changed: "+stage.deviceOrientation);
        }


        private function _onStage_OrientationChange(event:StageOrientationEvent):void
        {
            switch (event.afterOrientation)
            {
                case StageOrientation.DEFAULT:
                    _text.appendText("\nDefault orientation.");
                    break;
                case StageOrientation.ROTATED_RIGHT:
                    _text.appendText("\nRotated right.");
                    break;
                case StageOrientation.ROTATED_LEFT:
                    _text.appendText("\nRotated left.");
                    break;
                case StageOrientation.UPSIDE_DOWN:
                    _text.appendText("\nUpside down.");
                    break;
                case StageOrientation.UNKNOWN:
                    _text.appendText("\nUnknown.");
                    break;
            }
        }

        //---------------------------------------------------------------------
        //
        //  Private Methods
        //
        //---------------------------------------------------------------------
        private function _draw():void
        {
            graphics.clear();
            graphics.beginFill(0xFFFFFF,1);
            graphics.drawRect(0, 0, 480, 762);
            graphics.endFill();
        }

    }
}

Application Descriptor
If you ran this app without adjusting the application descriptor then the orientation event probably isn’t firing. This can be fixed by adding the following line in the initial window.

        <!-- States if the window auto orients to a specific direction (uses the accelerometer) Optional. Default false. -->
        <autoOrients>true</autoOrients>

And the full application descriptor:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<application xmlns="http://ns.adobe.com/air/application/2.5">

<!-- Adobe AIR Application Descriptor File Template.

    Specifies parameters for identifying, installing, and launching AIR applications.

    xmlns - The Adobe AIR namespace: http://ns.adobe.com/air/application/2.5
            The last segment of the namespace specifies the version
            of the AIR runtime required for this application to run.

    minimumPatchLevel - The minimum patch level of the AIR runtime required to run
            the application. Optional.
-->

    <!-- A universally unique application identifier. Must be unique across all AIR applications.
         Using a reverse DNS-style name as the id is recommended. (Eg. com.example.ExampleApplication.) Required. -->
    <id>com.unitedmindset.Screen</id>

    <!-- Used as the filename for the application. Required. -->
    <filename>Screen</filename>

    <!-- The name that is displayed in the AIR application installer.
         May have multiple values for each language. See samples or xsd schema file. Optional. -->
    <name>Screen</name>

    <!-- A string value of the format <0-999>.<0-999>.<0-999> that represents application version which can be used to check for application upgrade.
         Values can also be 1-part or 2-part. It is not necessary to have a 3-part value.
         An updated version of application must have a versionNumber value higher than the previous version. Required for namespace >= 2.5 . -->
    <versionNumber>1.0.0</versionNumber>

    <!-- A string value (such as "v1", "2.5", or "Alpha 1") that represents the version of the application, as it should be shown to users. Optional. -->
    <!-- <versionLabel></versionLabel> -->

    <!-- Description, displayed in the AIR application installer.
         May have multiple values for each language. See samples or xsd schema file. Optional. -->
    <!-- <description></description> -->

    <!-- Copyright information. Optional -->
    <!-- <copyright></copyright> -->

    <!-- Publisher ID. Used if you're updating an application created prior to 1.5.3 -->
    <!-- <publisherID></publisherID> -->

    <!-- Settings for the application's initial window. Required. -->
    <initialWindow>
        <!-- The main SWF or HTML file of the application. Required. -->
        <!-- Note: In Flash Builder, the SWF reference is set automatically. -->
        <content>[This value will be overwritten by Flash Builder in the output app.xml]</content>

        <!-- The title of the main window. Optional. -->
        <!-- <title></title> -->

        <!-- The type of system chrome to use (either "standard" or "none"). Optional. Default standard. -->
        <!-- <systemChrome></systemChrome> -->

        <!-- Whether the window is transparent. Only applicable when systemChrome is none. Optional. Default false. -->
        <!-- <transparent></transparent> -->

        <!-- Whether the window is initially visible. Optional. Default false. -->
        <visible>true</visible>

        <!-- Whether the user can minimize the window. Optional. Default true. -->
        <!-- <minimizable></minimizable> -->

        <!-- Whether the user can maximize the window. Optional. Default true. -->
        <!-- <maximizable></maximizable> -->

        <!-- Whether the user can resize the window. Optional. Default true. -->
        <!-- <resizable></resizable> -->

        <!-- The window's initial width in pixels. Optional. -->
        <width>480</width>

        <!-- The window's initial height in pixels. Optional. -->
        <height>762</height>

        <!-- The window's initial x position. Optional. -->
        <!-- <x></x> -->

        <!-- The window's initial y position. Optional. -->
        <!-- <y></y> -->

        <!-- The window's minimum size, specified as a width/height pair in pixels, such as "400 200". Optional. -->
        <!-- <minSize></minSize> -->

        <!-- The window's initial maximum size, specified as a width/height pair in pixels, such as "1600 1200". Optional. -->
        <!-- <maxSize></maxSize> -->

        <!-- States if the window auto orients to a specific direction (uses the accelerometer) Optional. Default false. -->
        <autoOrients>true</autoOrients>
    </initialWindow>

    <!-- We recommend omitting the supportedProfiles element, -->
    <!-- which in turn permits your application to be deployed to all -->
    <!-- devices supported by AIR. If you wish to restrict deployment -->
    <!-- (i.e., to only mobile devices) then add this element and list -->
    <!-- only the profiles which your application does support. -->
    <!-- <supportedProfiles>desktop extendedDesktop mobileDevice extendedMobileDevice</supportedProfiles> -->

    <!-- The subpath of the standard default installation location to use. Optional. -->
    <!-- <installFolder></installFolder> -->

    <!-- The subpath of the Programs menu to use. (Ignored on operating systems without a Programs menu.) Optional. -->
    <!-- <programMenuFolder></programMenuFolder> -->

    <!-- The icon the system uses for the application. For at least one resolution,
         specify the path to a PNG file included in the AIR package. Optional. -->
    <icon>
        <image16x16>assets/icon16.png</image16x16>
        <image32x32>assets/icon32.png</image32x32>
        <image36x36>assets/icon36.png</image36x36>
        <image48x48>assets/icon48.png</image48x48>
        <image72x72>assets/icon72.png</image72x72>
        <image128x128>assets/icon128.png</image128x128>
    </icon>

    <!-- Whether the application handles the update when a user double-clicks an update version
    of the AIR file (true), or the default AIR application installer handles the update (false).
    Optional. Default false. -->
    <!-- <customUpdateUI></customUpdateUI> -->

    <!-- Whether the application can be launched when the user clicks a link in a web browser.
    Optional. Default false. -->
    <!-- <allowBrowserInvocation></allowBrowserInvocation> -->

    <!-- Listing of file types for which the application can register. Optional. -->
    <!-- <fileTypes> -->

        <!-- Defines one file type. Optional. -->
        <!-- <fileType> -->

            <!-- The name that the system displays for the registered file type. Required. -->
            <!-- <name></name> -->

            <!-- The extension to register. Required. -->
            <!-- <extension></extension> -->

            <!-- The description of the file type. Optional. -->
            <!-- <description></description> -->

            <!-- The MIME content type. -->
            <!-- <contentType></contentType> -->

            <!-- The icon to display for the file type. Optional. -->
            <!-- <icon>
                <image16x16></image16x16>
                <image32x32></image32x32>
                <image48x48></image48x48>
                <image128x128></image128x128>
            </icon> -->

        <!-- </fileType> -->
    <!-- </fileTypes> -->

  <!-- Specify Android specific tags that get passed to AndroidManifest.xml file. -->
  <android>
  <manifestAdditions>
          <![CDATA[
            <manifest android:installLocation="auto">
                <!-- Added for Internet and debugging support -->
                <uses-permission android:name="android.permission.INTERNET"/>
                <!-- Added for Geolocation support -->
                <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
                <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
                <!-- Added for NetworkInfo Support -->
                <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
                <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
                <!-- Added for Camera Support -->
                <uses-permission android:name="android.permission.CAMERA" />
                <!-- Added for Microphone Support -->
                <uses-permission android:name="android.permission.RECORD_AUDIO" />
                <!-- Added to keep Android Device Awake -->
                <uses-permission android:name="android.permission.WAKE_LOCK" />
                <!-- Added to keep Android Device from Locking -->
                <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
                <!-- -->
                <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
                <uses-configuration android:reqFiveWayNav="true"/>
                <supports-screens android:normalScreens="true"/>
                <uses-feature android:required="true" android:name="android.hardware.touchscreen.multitouch"/>
                <application android:enabled="true">
                    <activity android:excludeFromRecents="false">
                        <intent-filter>
                        <action android:name="android.intent.action.MAIN"/>
                        <category android:name="android.intent.category.LAUNCHER"/>
                        </intent-filter>
                    </activity>
                </application>
            </manifest>
          ]]>
      </manifestAdditions>
    </android>
  <!-- End of the schema for adding the android specific tags in AndroidManifest.xml file -->

</application>
Share

Comments (11)

shawnSeptember 27th, 2010 at 3:59 pm

This is actually not the best way to listen for orientation change, there’s a couple of issues:
1. You won’t get the event on startup, and orientation shows UNKNOWN. So if you’re depending on this to size your views, you need to add additional code for a startUp check.

2. There’s specific occasions where the orientation event doesn’t fire, can’t remember exactly but I think it’s going from UPSIDE_DOWN to ROTATED_LEFT, or something.

The best way is the simplest, stage.RESIZE. It always fires, even on startup, and never leaves you wondering what orientation you’re in. And of course, if you really need to know, a simple (width > height) check will tell you if you you’re in landscape or not.

Personally I build my views to be totally liquid, so I don’t really care what the orientation is, i just fill the size I have available. But I could see a situation where you might want to show different UI depending on the orientation.

shawnSeptember 27th, 2010 at 4:04 pm

Also, one other benefit to the stage.RESIZE approach is it makes it super easy to test different layouts while in development, I can literally drag the corner of my app around to test how the app responds to different resolutions/aspect ratio’s, without needing to publish to the device at all.

Jonathan CamposSeptember 27th, 2010 at 4:08 pm

@Shawn Great points. I’m hoping the layout change from upside down to rotated left is just a bug – maybe you have a jira link for that? I personally haven’t seen that rotation issue though in my tests, what device are you using?

I completely agree with making everything flow layout. After years of making just flow layouts, the move to mobile has been much easier. I never really thought in pixels.

I definitely see what you are saying about the resize event. I just added a check into my apps for initial orientation, but you could go off of resize. Great finds!

shawnSeptember 27th, 2010 at 4:22 pm

Ahh I remembered the bug: If you rotate your phone upside down, the stage will re-orient, but you won’t get the orientation event.

According to Joe Ward on the Android Prerelease, the root issue is an Android event dispatching bug. The deviceOrientation property changes, you just don’t get the event.

So, you can either:
1. Use stage resize as your hook, but then look at the deviceOrientation property to size your views.
2. Forget deviceOrientation alltogether, and just use stage.stageWidth and stage.stageHeight (which I think is best, since it end up being super clean, and brings several other benefits)

Jonathan CamposSeptember 27th, 2010 at 4:24 pm

@Shawn it’s great to know. I have a project right now that I switch out renders and whatnot dependent on orientation. I’ll check out the resize event and see if my app responds better. Thanks for the input.

[...] Air for Android: Screen Orientation Mobiloknál már olyan nézetek is bejönnek amit alap esetben nem kell kezelnünk például a képerny? forgatása. Ennek megvalósítására mutat be egy mintát ez a segédlet. [...]

GrahamMarch 23rd, 2011 at 2:30 am

Hi guys I’m new to mobile development and would like to know where I could learn more about creating liquid layouts and effective orientation changes. I’m going off of

http://www.senocular.com/flash/tutorials/windowmoveandresize/

but would like more android specific info.

Cheers :)

jonbcamposMarch 23rd, 2011 at 10:33 am

@Graham
That is a bit more complicated. Using Flex you can use constraints to create liquid layouts. In Flash it is a matter of responding to a stage resize event. Feel free to as a bit more, but this part is hard to just answer directly.

AdamMarch 13th, 2012 at 6:07 pm
    <s:states>
        <s:State name="landscape"/>
        <s:State name="portrait"/>
    </s:states>
   
    <fx:Script>
        <![CDATA[
           
            protected function init():void
            {  
                stage.align = StageAlign.TOP_LEFT;
                stage.scaleMode = StageScaleMode.NO_SCALE;
               
                orient();
               
                stage.addEventListener(Event.RESIZE, resizeHandler);
            }
           
            protected function resizeHandler(event:Event):void
            {
                orient();
            }
           
            protected function orient():void
            {
                if(stage.stageWidth > stage.stageHeight)
                {
                    currentState = "landscape";
                }
                else
                {
                    currentState = "portrait";
                }
            }
           
        ]]>
    </fx:Script>
hayesmakerMarch 15th, 2012 at 4:26 pm

how do u make the app itself load as a landscape app by default.. the stupid simulator always opens as a portrait app! I know there’s a configuration xml to change, but how and where do you find it in Flash Builder 4.6?

jonbcamposMarch 15th, 2012 at 4:36 pm

@hayesmaker Check out the application descriptor and the tag.

Leave a comment

Your comment