Air for Android: Accelerometer

When I first started playing with AIR for Android immediately I wanted to make the best mobile game ever. That’s right… Wooden Ball in a Cup!

Of course this would be made possible by the Accelerometer built into the Android device. With it I can get controls and user interactions based on how the user interacts with the device itself not just screen controls.

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.


Memory, Power, and Performance Considerations
When first making this test app I felt that with as small as it is that I wouldn’t have to worry about performance concerns. Running the app it was choppy and the refresh rate was terrible.

At the time I was updating the position of the “ball” on every accelerometer update event. So I changed the ball move to be on the enter frame event. BAM! Immediately the app was running smooth. Quick lesson number 1.

Next thing that I did was to cache the ball bitmap. Since I don’t rotate the ball and don’t mess with the balls alpha channel the ball is a perfect candidate for bitmap caching. There was a small visual improvement on this change but the lions share was on the enter frame change.

Like Geolocation the accelerometer has the setRequestedUpdateInterval method. I recommend playing with this interval and finding what makes sense for your application. Too often and you’re killing battery and performance. To infrequent and your updates may not do what you want. Again, play with this interval and see what works for you.

Accelerometer App
The app itself is really simple. Make sure to check out the section that I enable accelerometer support and see how I made sure to check that the device supports the accelerometer before using it. The function that I used to actually move the ball isn’t anything special and just meant to make the movement seem natural. You’ll definitely want to make your own function for your application.

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.AccelerometerEvent;
    import flash.events.Event;
    import flash.sensors.Accelerometer;
    import flash.text.TextField;

    public class Accel extends Sprite
    {

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

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

        //---------------------------------------------------------------------
        //
        //  Private Properties
        //
        //---------------------------------------------------------------------
        private var _ball:Sprite;
        private var _accel:Accelerometer;

        private var _text:TextField;

        private var _accelX:Number;
        private var _accelY:Number;
        private var _accelZ:Number;
        //---------------------------------------------------------------------
        //
        //  Protected Methods
        //
        //---------------------------------------------------------------------
        protected function createChildren():void
        {
            //ball
            if(!_ball)
            {
                _createBall();
                _ball.x = width/2;
                _ball.y = height/2;
                addChild(_ball);
            }
            //text
            if(!_text)
            {
                _text = new TextField();
                _text.x = 10;
                _text.y = 10;
                _text.width = 400;
                _text.height = 600;
                _text.multiline = true;
                addChild(_text);
            }
        }
        //---------------------------------------------------------------------
        //
        //  Handler Methods
        //
        //---------------------------------------------------------------------
        private function _onAddedToStage(event:Event):void
        {
            //removes listener
            removeEventListener(Event.ADDED_TO_STAGE, _onAddedToStage);
            //draws background
            _draw();
            //creates children
            createChildren();
            //creates accelerometer
            _createAccelerometer();
            //listen for enter frame
            addEventListener(Event.ENTER_FRAME, _onEnterFrame);
        }

        private function _onEnterFrame(event:Event):void
        {
            var diffX:Number = _ball.x - (_ball.x + _accelX * 100);
            var diffY:Number = _ball.y - (_ball.y + _accelY * 100);
            //keep in x bounds
            if(_ball.x + diffX < 0)
                _ball.x = 0;
            else if(_ball.x + diffX > 480)
                _ball.x = 480;
            else
                _ball.x += diffX;
            //keep in y bounds
            if(_ball.y - diffY < 0)
                _ball.y = 0;
            else if(_ball.y - diffY > 775)
                _ball.y = 775;
            else
                _ball.y -= diffY;
            //set text
            _setText();
        }

        private function _onAccel_UpdateHandler(event:AccelerometerEvent):void
        {
            _accelX = event.accelerationX;
            _accelY = event.accelerationY;
            _accelZ = event.accelerationZ;
        }

        //---------------------------------------------------------------------
        //
        //  Private Methods
        //
        //---------------------------------------------------------------------
        private function _createBall():void
        {
            _ball = new Sprite();
            _ball.graphics.clear();
            _ball.graphics.beginFill(0x0000FF, 0.8);
            _ball.graphics.drawCircle(0, 0, 20);
            _ball.graphics.endFill();
            _ball.cacheAsBitmap = true;
        }

        private function _draw():void
        {
            graphics.clear();
            graphics.beginFill(0xFFFFFF,1);
            graphics.drawRect(0, 0, 480, 800);
            graphics.endFill();
        }

        private function _createAccelerometer():void
        {
            if(Accelerometer.isSupported)
            {
                _accel = new Accelerometer();
                _accel.setRequestedUpdateInterval(100);
                _accel.addEventListener(AccelerometerEvent.UPDATE, _onAccel_UpdateHandler);
            }
            _setText();
        }

        private function _setText():void
        {
            var s:String = "Accelerometer support: "+Accelerometer.isSupported;
            s += "\naccelerationX: "+_accelX;
            s += "\naccelerationY: "+_accelY;
            s += "\naccelerationZ: "+_accelZ;
            _text.text = s;
        }

    }
}

Application Descriptor File
For accelerometer suppor you don’t have to do anything special to the application descriptor file. I just wanted to make sure to share it so you didn’t think I did anything funny. :)

<?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.Accel</id>

    <!-- Used as the filename for the application. Required. -->
    <filename>Accel</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>Accel</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>400</width>

        <!-- The window's initial height in pixels. Optional. -->
        <height>800</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> -->
    </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></image16x16>
        <image32x32></image32x32>
        <image36x36></image36x36>
        <image48x48></image48x48>
        <image72x72></image72x72>
        <image128x128></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">
                <uses-permission android:name="android.permission.INTERNET"/>
                <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
                <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
                <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
                <!--
                -->
                <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>

Good luck! And look out for Wooden Ball in a Cup!

Share

Comments (4)

[...] This post was mentioned on Twitter by tony murphy and Digital Vizions, Jose Garay. Jose Garay said: Air for Android: Accelerometer http://bit.ly/91hTIK [...]

Brian RinaldiSeptember 1st, 2010 at 12:07 pm

Thanks for the tutorial. I posted a simple Accelerometer tutorial on my site last week as well (along with a simple sample application that let’s you record accelerometer data). http://remotesynthesis.com/post.cfm/using-the-accelerometer-in-air-for-android-sample-application

[...] Resources Over at UnitedMindset.com Jon Campos has an excellent post on using the accelerometer complete with a nice example of using the accelerometer to create a Wooden Ball in a Cup [...]

Leave a comment

Your comment