Handling orientation changes

Depending on whether you're using Cascades APIs or C APIs, the process of being notified of and responding to orientation changes in your app differs significantly. The following sections describe the steps that occur when a user changes the orientation of the device.

Orientation changes in Cascades

Your app orients itself automatically if it's set to handle orientation changes through the bar-descriptor.xml file (using the Auto-orient setting) or programmatically (using the SupportedDisplayOrientation::All property value). To make sure that the UI looks great for all orientations, the position, size, and number of displayed items may need to be adjusted to fit the width and height of the current orientation. During an orientation change, a sequence of property changes and emitted signals occurs, and you can use these events to adjust the UI or control the behavior of your app.

Here are the steps that occur when the orientation of the device changes:

1. The user starts rotating the device and the displayDirection property is about to change.

  • The displayDirectionAboutToChange() signal is emitted, even if the rotation is 180 degrees (meaning that there is no change to the orientation property).
  • No UI rotation occurs yet, so you can update the UI in response to the displayDirectionAboutToChange() signal.
  • You can stop the orientation change by setting the supportedDisplayOrientation property to a value that's incompatible with the upcoming orientation, such as SupportedDisplayOrientation::CurrentLocked.

2. Rotation of the device continues and the displayDirection property changes.

  • The displayDirectionChanged() signal is emitted.
  • After this point, updates to the UI controls on the screen are visible by the user only after the rotation occurs. For example, if you change the color of a control after this point, the user won't see the change until after the rotation occurs.

3. An actual orientation change of the UI is about to happen (a rotation that is not 180 degrees).

4. The orientation change occurs.

  • After handling the orientationAboutToChange() signal, the visual rotation effect occurs and the updated scene is displayed.
  • The orientation property updates to reflect the new orientation.
  • The orientationChanged() signal is emitted.
  • The app can make more significant or time-consuming changes to the scene in the new orientation.

5. The rotation or orientation change completes.

  • The rotationCompleted() signal is emitted.
  • The app can perform any activities that are needed after either the rotation or orientation change is finished.

Orientation changes in C

The navigator service populates the initial width and height environment variables based on the values that are set in the bar-descriptor.xml file. If the app's orientation is locked to landscape, then the width and height correspond to the device's resolution in landscape orientation. Similarly, if the app's orientation is locked to portrait, then the width and height correspond to the device's resolution in portrait orientation. If the orientation setting in the bar-descriptor.xml file is set to either Auto-orient or Default, the width and height correspond to the orientation of the device when the app starts. For more information, see Setting orientation in the bar-descriptor.xml file.

When the orientation of the device changes, the width and height environment variables aren't updated to reflect the new width and height of the screen. These values always reflect the screen dimensions when the app starts.

The Good Citizen sample app is an example of an app that responds to orientation changes. The bar-descriptor.xml file for this app looks like this:

<initialWindow>
    <autoOrients>true</autoOrients>
    <systemChrome>none</systemChrome>
</initialWindow>

Before you handle orientation changes, you must set up your app window. There are two environment variables that contain the initial width and height of your app. You can retrieve these values by querying the width and height environment variables when your app starts. If the height of the screen is greater than its width, the app is initially running in portrait orientation. Otherwise, the app is running in landscape orientation.

Not applicable

Not applicable

The following code sample illustrates how your app can query the environment variables for its initial width and height:

int screen_resolution[2];

/* Get the screen width */
screen_resolution[0] = atoi(getenv("WIDTH"));

/* Get the screen height */
screen_resolution[1] = atoi(getenv("HEIGHT"));

Your app should fetch these values when it starts and then use those values to create and set up its window, as follows:

screen_create_window(&screen_win, context);
int rc = screen_set_window_property_iv(screen_win,
                                       SCREEN_PROPERTY_SIZE, 
                                       screen_resolution);
if (rc) {
    /* Handle any errors that occur */
    /* ... */
}

rc = screen_set_window_property_iv(screen_win,
                                   SCREEN_PROPERTY_BUFFER_SIZE, 
                                   screen_resolution);
if (rc) {
    /* Handle any errors that occur */
    /* ... */
}

Here are the steps that occur when the orientation of the device changes:

1. The navigator service sends a NAVIGATOR_ORIENTATION_CHECK event to your app.

  • This event is sent only if the orientation is not locked to portrait or landscape orientation in the bar-descriptor.xml file.
  • This event includes parameters that indicate the new orientation (LANDSCAPE or PORTRAIT) and the rotation edge.

2. Your app must respond to the NAVIGATOR_ORIENTATION_CHECK event.

  • If your app intends to change its orientation in response to the orientation change of the device, you call navigator_orientation_check_response() with the will_rotate parameter set to true.
  • If your app doesn't intend to change its orientation, you call navigator_orientation_check_response() with the will_rotate parameter set to false.
  • If your app doesn't respond to the NAVIGATOR_ORIENTATION_CHECK event, it is equivalent to calling navigator_orientation_check_response() with the will_rotate parameter set to false.

3. If your app intends to change its orientation, your app receives a NAVIGATOR_ORIENTATION event.

  • Your app receives this event only if you called navigator_orientation_check_response() with the will_rotate parameter set to true.
  • Your app should resize its window buffers to fit the new orientation by calling screen_set_window_property_iv() for the buffer size and source size.
  • After the window buffers are resized, the app should redraw all windows and child windows.

4. Your app informs the navigator service that it's finished handling the orientation change.

5. The navigator service completes the rotation of the device.

  • The navigator service takes care of any other operations that are required (such as rotating other apps).
  • When it's done, the navigator service sends a NAVIGATOR_ORIENTATION_DONE event to your app.

Some window actions can’t be performed while a rotation is happening. These actions include creating new windows or joining window groups. Your app should wait until it receives the NAVIGATOR_ORIENTATION_DONE event to perform these actions.

Handle orientation changes

You can use the OrientationHandler class in QML to handle any orientation changes and adjust the UI. To use this class, add it to a Page in your app using the  attachedObjects property.

attachedObjects: [
    OrientationHandler {
     
    }
]

In OrientationHandler, you add code to handle the orientationAboutToChange() and orientationChanged() signals. Here is an example of how to handle the orientationAboutToChange() signal to modify UI elements in response to an orientation change:

attachedObjects: [
    OrientationHandler {
        onOrientationAboutToChange: {
            if (orientation == UIOrientation.Landscape) {
                // Change the text and padding to reflect a
                // landscape orientation
                orientationtext.text = "Landscape"
                rootContainer.leftPadding = 300 
                rootContainer.rightPadding = 300
                rootContainer.topPadding = 230
            } else {
                // Change the text and padding to reflect a
                // portrait orientation
                orientationtext.text = "Portrait"
                rootContainer.leftPadding = 100 
                rootContainer.rightPadding = 100
                rootContainer.topPadding = 300
            }
        }
    } // end of OrientationHandler
] // end of attachedObjects

You can use the OrientationSupport class in C++. First, the orientationAboutToChange() signal is connected to a slot function called onOrientationAboutToChange:

// Specify that all orientations are allowed
OrientationSupport::instance()
     ->setSupportedDisplayOrientation(SupportedDisplayOrientation::All);

// Connect the orientationAboutToChange() signal to a
// slot function
bool res = connect(
    OrientationSupport::instance(),
    SIGNAL(orientationAboutToChange(
        bb::cascades::UIOrientation::Type)),
    this,
    SLOT(onOrientationAboutToChange(
        bb::cascades::UIOrientation::Type)));

The definition of the slot function specifies the actions to take during an orientation change:

void ApplicationUI::onOrientationAboutToChange(bb::cascades::UIOrientation::Type
                                     uiOrientation)
{
    // Change the text and padding to reflect a landscape
    // orientation
    if(uiOrientation == UIOrientation::Landscape)
    {
        m_label->setText("Landscape");
        rootContainer->setLeftPadding(300);
        rootContainer->setRightPadding(300);
        rootContainer->setTopPadding(230);
    }
     
    // Change the text and padding to reflect a portrait
    // orientation
    else
    {
        m_label->setText("Portrait");
        rootContainer->setLeftPadding(100);
        rootContainer->setRightPadding(100);
        rootContainer->setTopPadding(300);
    }
}

The following code sample shows how to handle navigator orientation events in your app. The code sample is based on the Core Native Screen project template that you can use in the Momentics IDE for BlackBerry, but some of that code has been omitted for brevity.

#include <bps/bps.h>
#include <bps/event.h>
#include <bps/navigator.h>
#include <fcntl.h>
#include <screen/screen.h>

static bool shutdown = false;
int
main(int argc, char **argv)
{
    /* Create screen variables */
    const int usage = SCREEN_USAGE_NATIVE;
    screen_context_t screen_ctx;
    screen_window_t screen_win;
    screen_buffer_t screen_buf = NULL;
    int rect[4] = { 0, 0, 0, 0 };

    /* Retrieve the initial width and height */
    int screen_resolution[2];

    screen_resolution[0] = atoi(getenv("WIDTH"));
    screen_resolution[1] = atoi(getenv("HEIGHT"));

    fprintf(stderr, "Initial resolution: %d x %d\n", screen_resolution[0], screen_resolution[1]);

    /* Set up the window */
    screen_create_context(&screen_ctx, 0);
    screen_create_window(&screen_win, screen_ctx);
    screen_create_window_group(screen_win, get_window_group_id());
    screen_create_window_buffers(screen_win, 1);

    /* Set window properties */
    screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, screen_resolution);
    screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, screen_resolution);
    screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage);
    screen_get_window_property_pv(screen_win, SCREEN_PROPERTY_RENDER_BUFFERS, (void **)&screen_buf);
    screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, rect+2);

    /* Display the window */
    screen_post_window(screen_win, screen_buf, 1, rect, 0);

    /* Initialize the BPS library and request navigator events */
    bps_initialize();
    navigator_request_events(0);

    while (!shutdown) {
        /* Create a variable for the domain of the BPS event */
        int domain;

        /* Retrieve the next available BPS event */
        bps_event_t *event = NULL;
        if (BPS_SUCCESS != bps_get_event(&event, -1)) {
            fprintf(stderr, "bps_get_event() failed\n");
            break;
        }

        /* Determine the domain of the event */
        if (event) {
            domain = bps_event_get_domain(event);

            /* If a navigator event was received, process it */
            if (domain == navigator_get_domain()) {
                switch (bps_event_get_code(event)) {
                    case NAVIGATOR_ORIENTATION_CHECK:
                        /* An orientation check event was received, so
                           respond to it by indicating that the app
                           intends to rotate */
                        fprintf(stderr, "NAVIGATOR_ORIENTATION_CHECK event received\n");
                        fprintf(stderr, "Calling navigator_orientation_check_response()\n");
                        navigator_orientation_check_response(event, true);
                        break;
                    case NAVIGATOR_ORIENTATION:
                        /* An orientation event was received, so determine
                           the new orientation and resize window buffers */
                        fprintf(stderr, "NAVIGATOR_ORIENTATION event received\n");

                        if (navigator_event_get_orientation_mode(event) == NAVIGATOR_LANDSCAPE) {
                            fprintf(stderr, "New orientation: landscape\n");
                        } else {
                            fprintf(stderr, "New orientation: portrait\n");
                        }

                        fprintf(stderr, "Resizing buffers and redrawing windows\n");

                        /* Resize window buffers and redraw all windows as necessary */
                        /* ... */

                        /* Inform the navigator service that app rotation has completed */
                        fprintf(stderr, "App rotation has completed\n");
                        fprintf(stderr, "Calling navigator_done_orientation()\n");
                        navigator_done_orientation(event);
                        break;
                    case NAVIGATOR_ORIENTATION_DONE:
                        /* The navigator service has finished rotating the device */
                        fprintf(stderr, "Navigator service rotation has completed\n");
                        break;
                }
            }
        }
    }

    /* Clean up */
    bps_shutdown();
    screen_destroy_window(screen_win);
    screen_destroy_context(screen_ctx);
    return 0;
}

For a full code sample demonstrating how to respond to orientation changes, see Tutorial: Orientation changes.

If you're using Cascades APIs, you can use a technique to make orientation handling simpler by binding various UI components to the orientationAboutToChange() signal. You do this binding in QML by creating a custom property (for example, newOrientation) and a function that updates this property with the current orientation when it's called. You can use this property as a condition to select or configure UI components.

To ensure that the UI is updated before the orientation change completes, the update function is called whenever the orientationAboutToChange() signal is emitted. The function looks like this:

function updateNewOrientation(orientation) {
    newOrientation = 
        (orientation == UIOrientation.Landscape ?
            "landscape" : "portrait");
}

Here is an example of how this technique is used to change the background color of a Container and the image contained within it:

import bb.cascades 1.2

Page {
    // Set the initial value of the newOrientation property
    // based on the starting orientation of the device
    property string newOrientation: UIOrientation.Landscape ?
                "landscape" : "portrait";

    // Declare the update function
    function updateNewOrientation(orientation) {
        newOrientation = 
            (orientation == UIOrientation.Landscape ?
                "landscape" : "portrait");
    }

    Container {
        // Change background color based on orientation
        background: newOrientation == "landscape" ?
            Color.Green : Color.Blue
        
        ImageView {
            // Load different image resources based on
            // orientation
            imageSource: "asset:///image_" + newOrientation + ".png"
        }
    }
    attachedObjects: [
        OrientationHandler {
              id: handler
            // Call update function to set new orientation
            onOrientationAboutToChange:
                updateNewOrientation(orientation)
        }
    ]
    // Call update function to set initial orientation
    onCreationCompleted: updateNewOrientation(handler.orientation)
}

The image selection here assumes that the app has two assets loaded: image_landscape.png and image_portrait.png.

Not applicable

Not applicable

Last modified: 2015-07-24



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus